From 316bed25f85aa6a5ea88449f7b3e09062e9620cb Mon Sep 17 00:00:00 2001 From: Peter Williams Date: Wed, 13 Apr 2016 13:15:56 -0400 Subject: [PATCH 0001/1195] gtk3: Avoid warnings from newer GObject-introspection imports. --- ipykernel/gui/gtk3embed.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index f70a6f4e4..5cea1adb6 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -14,6 +14,9 @@ import sys # Third-party +import gi +gi.require_version ('Gdk', '3.0') +gi.require_version ('Gtk', '3.0') from gi.repository import GObject, Gtk #----------------------------------------------------------------------------- From 91c61ac16559576fa817d1712db7cf3df1142fdf Mon Sep 17 00:00:00 2001 From: Dave Willmer Date: Mon, 25 Apr 2016 18:25:33 +0100 Subject: [PATCH 0002/1195] Cleanup comm and comm manager classes --- ipykernel/comm/comm.py | 34 +++++----------- ipykernel/comm/manager.py | 84 +++++++++++++-------------------------- ipykernel/ipkernel.py | 11 +++-- 3 files changed, 43 insertions(+), 86 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index cb3e35d70..cf842c7cb 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -17,9 +17,6 @@ class Comm(LoggingConfigurable): """Class for communicating between a Frontend and a Kernel""" - # If this is instantiated by a non-IPython kernel, shell will be None - shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', - allow_none=True) kernel = Instance('ipykernel.kernelbase.Kernel') @default('kernel') @@ -27,18 +24,11 @@ def _default_kernel(self): if Kernel.initialized(): return Kernel.instance() - iopub_socket = Any() - - @default('iopub_socket') - def _default_iopub_socket(self): - return self.kernel.iopub_socket - - session = Instance('jupyter_client.session.Session') + comm_id = Unicode() - @default('session') - def _default_session(self): - if self.kernel is not None: - return self.kernel.session + @default('comm_id') + def _default_comm_id(self): + return uuid.uuid4().hex target_name = Unicode('comm') target_module = Unicode(None, allow_none=True, help="""requirejs module from @@ -57,11 +47,6 @@ def _default_topic(self): _close_callback = Any() _closed = Bool(True) - comm_id = Unicode() - - @default('comm_id') - def _default_comm_id(self): - return uuid.uuid4().hex primary = Bool(True, help="Am I the primary or secondary Comm?") @@ -84,7 +69,7 @@ def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys) data = {} if data is None else data metadata = {} if metadata is None else metadata content = json_clean(dict(data=data, comm_id=self.comm_id, **keys)) - self.session.send(self.iopub_socket, msg_type, + self.kernel.session.send(self.kernel.iopub_socket, msg_type, content, metadata=json_clean(metadata), parent=self.kernel._parent_header, @@ -169,12 +154,13 @@ def handle_close(self, msg): def handle_msg(self, msg): """Handle a comm_msg message""" self.log.debug("handle_msg[%s](%s)", self.comm_id, msg) + shell = self.kernel.shell if self._msg_callback: - if self.shell: - self.shell.events.trigger('pre_execute') + if shell: + shell.events.trigger('pre_execute') self._msg_callback(msg) - if self.shell: - self.shell.events.trigger('post_execute') + if shell: + shell.events.trigger('post_execute') __all__ = ['Comm'] diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 92f860b4f..49bcc40e3 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -6,8 +6,6 @@ import sys from traitlets.config import LoggingConfigurable -from IPython.core.prompts import LazyEvaluate -from IPython.core.getipython import get_ipython from ipython_genutils.importstring import import_item from ipython_genutils.py3compat import string_types @@ -16,35 +14,10 @@ from .comm import Comm -def lazy_keys(dikt): - """Return lazy-evaluated string representation of a dictionary's keys - - Key list is only constructed if it will actually be used. - Used for debug-logging. - """ - return LazyEvaluate(lambda d: list(d.keys())) - - class CommManager(LoggingConfigurable): """Manager for Comms in the Kernel""" - # If this is instantiated by a non-IPython kernel, shell will be None - shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', - allow_none=True) kernel = Instance('ipykernel.kernelbase.Kernel') - - iopub_socket = Any() - - @default('iopub_socket') - def _default_iopub_socket(self): - return self.kernel.iopub_socket - - session = Instance('jupyter_client.session.Session') - - @default('session') - def _default_session(self): - return self.kernel.session - comms = Dict() targets = Dict() @@ -67,14 +40,12 @@ def register_target(self, target_name, f): def unregister_target(self, target_name, f): """Unregister a callable registered with register_target""" - return self.targets.pop(target_name); + return self.targets.pop(target_name) def register_comm(self, comm): """Register a new comm""" comm_id = comm.comm_id - comm.shell = self.shell comm.kernel = self.kernel - comm.iopub_socket = self.iopub_socket self.comms[comm_id] = comm return comm_id @@ -91,13 +62,12 @@ def get_comm(self, comm_id): This will not raise an error, it will log messages if the comm cannot be found. """ - if comm_id not in self.comms: + try: + return self.comms[comm_id] + except KeyError: self.log.warn("No such comm: %s", comm_id) - self.log.debug("Current comms: %s", lazy_keys(self.comms)) - return - # call, because we store weakrefs - comm = self.comms[comm_id] - return comm + if self.log.isEnabledFor(self.log.DEBUG): + self.log.debug("Current comms: %s", self.comms.keys()) # Message handlers def comm_open(self, stream, ident, msg): @@ -107,9 +77,6 @@ def comm_open(self, stream, ident, msg): target_name = content['target_name'] f = self.targets.get(target_name, None) comm = Comm(comm_id=comm_id, - shell=self.shell, - kernel=self.kernel, - iopub_socket=self.iopub_socket, primary=False, target_name=target_name, ) @@ -132,32 +99,37 @@ def comm_open(self, stream, ident, msg): def comm_msg(self, stream, ident, msg): """Handler for comm_msg messages""" - content = msg['content'] - comm_id = content['comm_id'] - comm = self.get_comm(comm_id) - if comm is None: - # no such comm + comm_id, comm = self._get_id_and_comm(msg) + if self._comm_is_not_valid(comm, comm_id): return - try: - comm.handle_msg(msg) - except Exception: - self.log.error("Exception in comm_msg for %s", comm_id, exc_info=True) + + self._safe_handle(msg, comm.handle_msg) def comm_close(self, stream, ident, msg): """Handler for comm_close messages""" - content = msg['content'] - comm_id = content['comm_id'] - comm = self.get_comm(comm_id) - if comm is None: - # no such comm - self.log.debug("No such comm to close: %s", comm_id) + comm_id, comm = self._get_id_and_comm(msg) + if self._comm_is_not_valid(comm, comm_id): return + del self.comms[comm_id] + self._safe_handle(msg, comm.handle_close) + + def _comm_is_not_valid(self, comm, comm_id): + invalid = comm is None + if invalid: + self.log.debug('No such comm: ', str(comm_id)) + return not invalid + + def _get_id_and_comm(self, msg): + content = msg['content'] + comm_id = content['comm_id'] + return (comm_id, self.get_comm(comm_id)) + def _safe_handle(self, msg, callback): try: - comm.handle_close(msg) + callback(msg) except Exception: - self.log.error("Exception handling comm_close for %s", comm_id, exc_info=True) + self.log.error('Exception handling ' + callback.__name__ + ' for %s', msg['content']['comm_id'], exc_info=True) __all__ = ['CommManager'] diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 1ffd70fe8..42ad4ec8b 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -54,8 +54,7 @@ def __init__(self, **kwargs): # TMP - hack while developing self.shell._reply_content = None - self.comm_manager = CommManager(shell=self.shell, parent=self, - kernel=self) + self.comm_manager = CommManager(kernel=self) self.shell.configurables.append(self.comm_manager) comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ] @@ -126,7 +125,7 @@ def set_parent(self, ident, parent): def init_metadata(self, parent): """Initialize metadata. - + Run at the beginning of each execution request. """ md = super(IPythonKernel, self).init_metadata(parent) @@ -137,10 +136,10 @@ def init_metadata(self, parent): 'engine' : self.ident, }) return md - + def finish_metadata(self, parent, metadata, reply_content): """Finish populating metadata. - + Run after completing an execution request. """ # FIXME: remove deprecated ipyparallel-specific code @@ -359,7 +358,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): reply_content.update(shell._reply_content) # reset after use shell._reply_content = None - + # FIXME: deprecate piece for ipyparallel: e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply') reply_content['engine_info'] = e_info From 4a265c06f1f9f4d219845e2864eee2080a9a52cf Mon Sep 17 00:00:00 2001 From: Dave Willmer Date: Mon, 25 Apr 2016 22:48:09 +0100 Subject: [PATCH 0003/1195] Move definitions for clarity --- ipykernel/comm/comm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index cf842c7cb..ccca9b938 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -30,6 +30,8 @@ def _default_kernel(self): def _default_comm_id(self): return uuid.uuid4().hex + primary = Bool(True, help="Am I the primary or secondary Comm?") + target_name = Unicode('comm') target_module = Unicode(None, allow_none=True, help="""requirejs module from which to load comm target.""") @@ -48,8 +50,6 @@ def _default_topic(self): _closed = Bool(True) - primary = Bool(True, help="Am I the primary or secondary Comm?") - def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwargs): if target_name: kwargs['target_name'] = target_name @@ -154,8 +154,8 @@ def handle_close(self, msg): def handle_msg(self, msg): """Handle a comm_msg message""" self.log.debug("handle_msg[%s](%s)", self.comm_id, msg) - shell = self.kernel.shell if self._msg_callback: + shell = self.kernel.shell if shell: shell.events.trigger('pre_execute') self._msg_callback(msg) From 50a620841a9c1d8dcfddf3a9a45986ad05e31342 Mon Sep 17 00:00:00 2001 From: Dave Willmer Date: Tue, 26 Apr 2016 13:32:16 +0100 Subject: [PATCH 0004/1195] Fixes --- ipykernel/comm/manager.py | 10 +++++----- ipykernel/ipkernel.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 49bcc40e3..80ecef35b 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -67,7 +67,7 @@ def get_comm(self, comm_id): except KeyError: self.log.warn("No such comm: %s", comm_id) if self.log.isEnabledFor(self.log.DEBUG): - self.log.debug("Current comms: %s", self.comms.keys()) + self.log.debug("Current comms: %s", list(self.comms.keys())) # Message handlers def comm_open(self, stream, ident, msg): @@ -103,7 +103,7 @@ def comm_msg(self, stream, ident, msg): if self._comm_is_not_valid(comm, comm_id): return - self._safe_handle(msg, comm.handle_msg) + self._safe_handle(msg, comm.handle_msg, name='comm_msg') def comm_close(self, stream, ident, msg): """Handler for comm_close messages""" @@ -112,7 +112,7 @@ def comm_close(self, stream, ident, msg): return del self.comms[comm_id] - self._safe_handle(msg, comm.handle_close) + self._safe_handle(msg, comm.handle_close, name='comm_close') def _comm_is_not_valid(self, comm, comm_id): invalid = comm is None @@ -125,11 +125,11 @@ def _get_id_and_comm(self, msg): comm_id = content['comm_id'] return (comm_id, self.get_comm(comm_id)) - def _safe_handle(self, msg, callback): + def _safe_handle(self, msg, callback, name = ''): try: callback(msg) except Exception: - self.log.error('Exception handling ' + callback.__name__ + ' for %s', msg['content']['comm_id'], exc_info=True) + self.log.error('Exception handling ' + name + ' for %s', msg['content']['comm_id'], exc_info=True) __all__ = ['CommManager'] diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 42ad4ec8b..39ae2f2a2 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -54,7 +54,7 @@ def __init__(self, **kwargs): # TMP - hack while developing self.shell._reply_content = None - self.comm_manager = CommManager(kernel=self) + self.comm_manager = CommManager(parent=self, kernel=self) self.shell.configurables.append(self.comm_manager) comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ] From c0cbe784c66425dc580f72f9d91d3ed07df8e14c Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Thu, 5 May 2016 15:51:34 +0100 Subject: [PATCH 0005/1195] BUG: Avoid using an empty session key. --- ipykernel/inprocess/constants.py | 8 ++++++++ ipykernel/inprocess/ipkernel.py | 3 ++- ipykernel/inprocess/manager.py | 4 +++- 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 ipykernel/inprocess/constants.py diff --git a/ipykernel/inprocess/constants.py b/ipykernel/inprocess/constants.py new file mode 100644 index 000000000..fe07a3406 --- /dev/null +++ b/ipykernel/inprocess/constants.py @@ -0,0 +1,8 @@ +"""Shared constants. +""" + +# Because inprocess communication is not networked, we can use a common Session +# key everywhere. This is not just the empty bytestring to avoid tripping +# certain security checks in the rest of Jupyter that assumes that empty keys +# are insecure. +INPROCESS_KEY = b'inprocess' diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 5b1527a61..c35c7dfb1 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -13,6 +13,7 @@ from ipykernel.ipkernel import IPythonKernel from ipykernel.zmqshell import ZMQInteractiveShell +from .constants import INPROCESS_KEY from .socket import DummySocket from ..iostream import OutStream, BackgroundSocket, IOPubThread @@ -139,7 +140,7 @@ def _default_log(self): @default('session') def _default_session(self): from jupyter_client.session import Session - return Session(parent=self, key=b'') + return Session(parent=self, key=INPROCESS_KEY) @default('shell_class') def _default_shell_class(self): diff --git a/ipykernel/inprocess/manager.py b/ipykernel/inprocess/manager.py index 7f15c8dbf..f46d89d53 100644 --- a/ipykernel/inprocess/manager.py +++ b/ipykernel/inprocess/manager.py @@ -8,6 +8,8 @@ from jupyter_client.manager import KernelManager from jupyter_client.session import Session +from .constants import INPROCESS_KEY + class InProcessKernelManager(KernelManager): """A manager for an in-process kernel. @@ -33,7 +35,7 @@ def _default_blocking_class(self): @default('session') def _default_session(self): # don't sign in-process messages - return Session(key=b'', parent=self) + return Session(key=INPROCESS_KEY, parent=self) #-------------------------------------------------------------------------- # Kernel management methods From ed8d7a19fc6f64b3fc66d5e17ae8f8a59965f0d1 Mon Sep 17 00:00:00 2001 From: Robert Kern Date: Thu, 5 May 2016 15:52:33 +0100 Subject: [PATCH 0006/1195] DOC: Changelog. --- docs/changelog.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 03ba591a2..ed938d558 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,12 @@ Changes in IPython kernel 4.3 --- +4.3.2 +***** + +- Use a nonempty dummy session key for inprocess kernels to avoid security + warnings. + 4.3.1 ***** From 1e0388d4d99facd43d39560c2821eafce1e749fa Mon Sep 17 00:00:00 2001 From: stonebig Date: Sat, 7 May 2016 15:03:55 +0200 Subject: [PATCH 0007/1195] python3.5 official support --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index 03c65f5a5..5ad947b15 100644 --- a/setup.py +++ b/setup.py @@ -72,6 +72,8 @@ 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ], ) From b937cf944a9eb5daeca2b234ae8e343bb65d3a51 Mon Sep 17 00:00:00 2001 From: stonebig Date: Sat, 7 May 2016 17:04:34 +0200 Subject: [PATCH 0008/1195] simplifyu Python 3 classifiers all maintained Python3 are 3.3+ now, --- setup.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/setup.py b/setup.py index 5ad947b15..d2b087248 100644 --- a/setup.py +++ b/setup.py @@ -71,9 +71,6 @@ 'Programming Language :: Python', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', ], ) From 15e93fd32e42e0da86695de59aa1a7ca159d28cf Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 17 May 2016 11:27:07 -0700 Subject: [PATCH 0009/1195] Remove unused import --- ipykernel/pylab/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index 98cc29e68..ea195de48 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -16,7 +16,7 @@ from traitlets.config import Config from traitlets.config.configurable import SingletonConfigurable from traitlets import ( - Dict, Instance, CaselessStrEnum, Set, Bool, Int, TraitError, Unicode + Dict, Instance, Set, Bool, TraitError, Unicode ) from IPython.utils.warn import warn From e6a77119505726e2e3bce632cf46c7bcc1142bb5 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 17 May 2016 11:30:52 -0700 Subject: [PATCH 0010/1195] Enable some deprecation warning (default for now) In order to try to catch some old API usage (IPython.utils.warn) --- .travis.yml | 2 +- setup.cfg | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 72d247b9d..a3c37fab6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ sudo: false before_install: - git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels install: - - pip install -f travis-wheels/wheelhouse --pre -e . codecov + - pip install -f travis-wheels/wheelhouse --pre -e . codecov nose_warnings_filters - pip install -f travis-wheels/wheelhouse ipykernel[test] - python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' script: diff --git a/setup.cfg b/setup.cfg index 3c6e79cf3..0c80d4cc5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,2 +1,13 @@ [bdist_wheel] universal=1 + +[nosetests] +warningfilters= default |.* |DeprecationWarning |ipykernel.* + default |.* |DeprecationWarning |IPython.* + ignore |.*assert.* |DeprecationWarning |.* + ignore |.*observe.* |DeprecationWarning |IPython.* + ignore |.*default.* |DeprecationWarning |IPython.* + ignore |.*default.* |DeprecationWarning |jupyter_client.* + ignore |.*Metada.* |DeprecationWarning |IPython.* + + From 68ed8ebae8c8d78d4a1e22c0013b7b0c7270e207 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Mon, 30 May 2016 18:37:38 -0400 Subject: [PATCH 0011/1195] Rewrite comm handling to make it clearer and fix a logic inversion bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts part of 91c61ac16559576fa817d1712db7cf3df1142fdf (from https://github.com/ipython/ipykernel/pull/126), which introduced a logic bug which broke handling comm messages, widgets, etc. The logic in the refactoring was complicated enough that the invalid-checking function returned the opposite of what it should have returned. Since the original code in each function was only about 10 lines, I think it’s more complicated to introduce three new undocumented functions instead of just repeating similar logic twice. Fixes #137. --- ipykernel/comm/manager.py | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 80ecef35b..15e3b80a1 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -67,6 +67,7 @@ def get_comm(self, comm_id): except KeyError: self.log.warn("No such comm: %s", comm_id) if self.log.isEnabledFor(self.log.DEBUG): + # don't create the list of keys if debug messages aren't enabled self.log.debug("Current comms: %s", list(self.comms.keys())) # Message handlers @@ -99,37 +100,30 @@ def comm_open(self, stream, ident, msg): def comm_msg(self, stream, ident, msg): """Handler for comm_msg messages""" - comm_id, comm = self._get_id_and_comm(msg) - if self._comm_is_not_valid(comm, comm_id): + content = msg['content'] + comm_id = content['comm_id'] + comm = self.get_comm(comm_id) + if comm is None: return - self._safe_handle(msg, comm.handle_msg, name='comm_msg') + try: + comm.handle_msg(msg) + except Exception: + self.log.error('Exception in comm_msg for %s', comm_id, exc_info=True) def comm_close(self, stream, ident, msg): """Handler for comm_close messages""" - comm_id, comm = self._get_id_and_comm(msg) - if self._comm_is_not_valid(comm, comm_id): + content = msg['content'] + comm_id = content['comm_id'] + comm = self.get_comm(comm_id) + if comm is None: return del self.comms[comm_id] - self._safe_handle(msg, comm.handle_close, name='comm_close') - def _comm_is_not_valid(self, comm, comm_id): - invalid = comm is None - if invalid: - self.log.debug('No such comm: ', str(comm_id)) - return not invalid - - def _get_id_and_comm(self, msg): - content = msg['content'] - comm_id = content['comm_id'] - return (comm_id, self.get_comm(comm_id)) - - def _safe_handle(self, msg, callback, name = ''): try: - callback(msg) + comm.handle_close(msg) except Exception: - self.log.error('Exception handling ' + name + ' for %s', msg['content']['comm_id'], exc_info=True) - + self.log.error('Exception in comm_close for %s', comm_id, exc_info=True) __all__ = ['CommManager'] From c0aa1d72bd8964a09b1be71738dba24cfd541a57 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 2 Jun 2016 14:38:56 +0200 Subject: [PATCH 0012/1195] import IOLoop from tornado rather than from zmq --- ipykernel/comm/comm.py | 2 +- ipykernel/kernelapp.py | 5 ++++- ipykernel/kernelbase.py | 2 +- ipykernel/zmqshell.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index ccca9b938..32ef9d262 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -6,7 +6,7 @@ import threading import uuid -from zmq.eventloop.ioloop import IOLoop +from tornado.ioloop import IOLoop from traitlets.config import LoggingConfigurable from ipykernel.kernelbase import Kernel diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 36bc7e69b..729aa5f4c 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -12,8 +12,9 @@ import traceback import logging +from tornado import ioloop import zmq -from zmq.eventloop import ioloop +from zmq.eventloop import ioloop as zmq_ioloop from zmq.eventloop.zmqstream import ZMQStream from IPython.core.application import ( @@ -422,6 +423,8 @@ def initialize(self, argv=None): super(IPKernelApp, self).initialize(argv) if self.subapp is not None: return + # register zmq IOLoop with tornado + zmq_ioloop.install() self.init_blackhole() self.init_connection_file() self.init_poller() diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 5187e1cea..9b441375d 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -14,7 +14,7 @@ from signal import signal, default_int_handler, SIGINT import zmq -from zmq.eventloop import ioloop +from tornado import ioloop from zmq.eventloop.zmqstream import ZMQStream from traitlets.config.configurable import SingletonConfigurable diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index a47fecc1a..184a23944 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -23,7 +23,7 @@ import warnings from threading import local -from zmq.eventloop import ioloop +from tornado import ioloop from IPython.core.interactiveshell import ( InteractiveShell, InteractiveShellABC From 90ebb30d9206939d5b11260533a0de5237b57b8f Mon Sep 17 00:00:00 2001 From: Paul Ivanov Date: Thu, 2 Jun 2016 13:50:19 -0700 Subject: [PATCH 0013/1195] update test instructions Without this patch, anyone following the installation/test instruction will see the following error: ``` nosetests: error: Error reading config file 'setup.cfg': no such option 'warningfilters' ``` <3 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7f1e776f6..9744da286 100644 --- a/README.md +++ b/README.md @@ -12,10 +12,10 @@ After that, all normal `ipython` commands will use this newly-installed version ## Running tests -Ensure you have `nosetests` installed with +Ensure you have `nosetests` and the `nose-warnings-filters` plugin installed with ```bash -pip install nose +pip install nose nose-warnings-filters ``` and then from the root directory From ebd5494f7733f4f9439ab989109258418bf4425c Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 21 Jun 2016 11:53:23 +0100 Subject: [PATCH 0014/1195] update %connect_info output for jupyter it was still referring to ipython entry points. --- ipykernel/zmqshell.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 184a23944..771b50327 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -327,7 +327,7 @@ def connect_info(self, arg_s): In the simplest case, when called from the most recently launched kernel, secondary clients can be connected, simply with: - $> ipython --existing + $> jupyter --existing """ @@ -358,12 +358,12 @@ def connect_info(self, arg_s): print (info + '\n') print ("Paste the above JSON into a file, and connect with:\n" - " $> ipython --existing \n" + " $> jupyter --existing \n" "or, if you are local, you can connect with just:\n" - " $> ipython --existing {0} {1}\n" + " $> jupyter --existing {0} {1}\n" "or even just:\n" - " $> ipython --existing {1}\n" - "if this is the most recent IPython session you have started.".format( + " $> jupyter --existing {1}\n" + "if this is the most recent Jupyter kernel you have started.".format( connection_file, profile_flag ) ) From 27d10b476bc1a030b66d556ee43052ebcc171455 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 23 Jun 2016 10:22:05 +0100 Subject: [PATCH 0015/1195] More Jupyter updates for %connect_info - update security_dir check for jupyter paths - remove no longer relevant mention of profiles --- ipykernel/zmqshell.py | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 771b50327..821fa7ebc 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -50,8 +50,9 @@ ) from IPython.utils.warn import error from ipykernel.displayhook import ZMQShellDisplayHook -from jupyter_client.session import extract_header -from jupyter_client.session import Session + +from jupyter_core.paths import jupyter_runtime_dir +from jupyter_client.session import extract_header, Session #----------------------------------------------------------------------------- # Functions and classes @@ -331,16 +332,6 @@ def connect_info(self, arg_s): """ - from IPython.core.application import BaseIPythonApplication as BaseIPApp - - if BaseIPApp.initialized(): - app = BaseIPApp.instance() - security_dir = app.profile_dir.security_dir - profile = app.profile - else: - profile = 'default' - security_dir = '' - try: connection_file = get_connection_file() info = get_connection_info(unpack=False) @@ -348,11 +339,8 @@ def connect_info(self, arg_s): error("Could not get connection info: %r" % e) return - # add profile flag for non-default profile - profile_flag = "--profile %s" % profile if profile != 'default' else "" - - # if it's in the security dir, truncate to basename - if security_dir == os.path.dirname(connection_file): + # if it's in the default dir, truncate to basename + if jupyter_runtime_dir() == os.path.dirname(connection_file): connection_file = os.path.basename(connection_file) @@ -360,11 +348,11 @@ def connect_info(self, arg_s): print ("Paste the above JSON into a file, and connect with:\n" " $> jupyter --existing \n" "or, if you are local, you can connect with just:\n" - " $> jupyter --existing {0} {1}\n" + " $> jupyter --existing {0}\n" "or even just:\n" - " $> jupyter --existing {1}\n" + " $> jupyter --existing\n" "if this is the most recent Jupyter kernel you have started.".format( - connection_file, profile_flag + connection_file ) ) From 127197b50ffd7b11860c138f0310c92d7780c76c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 30 Jun 2016 10:24:55 -0700 Subject: [PATCH 0016/1195] Remove usage of IPython deprecated APIs. --- ipykernel/iostream.py | 5 +---- ipykernel/parentpoller.py | 5 ++--- ipykernel/zmqshell.py | 11 +++++------ 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 826a744e0..dae05af23 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -7,7 +7,6 @@ from __future__ import print_function import atexit import os -import threading import sys import threading import uuid @@ -23,8 +22,6 @@ from ipython_genutils import py3compat from ipython_genutils.py3compat import unicode_type -from IPython.utils.warn import warn - #----------------------------------------------------------------------------- # Globals #----------------------------------------------------------------------------- @@ -73,7 +70,7 @@ def _setup_pipe_in(self): try: self._pipe_port = pipe_in.bind_to_random_port("tcp://127.0.0.1") except zmq.ZMQError as e: - warn("Couldn't bind IOPub Pipe to 127.0.0.1: %s" % e + + warnings.warn("Couldn't bind IOPub Pipe to 127.0.0.1: %s" % e + "\nsubprocess output will be unavailable." ) self._pipe_flag = False diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 227614d44..0dc9fdb9c 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -15,8 +15,7 @@ from thread import interrupt_main # Py 2 from threading import Thread -from IPython.utils.warn import warn - +import warnings class ParentPollerUnix(Thread): """ A Unix-specific daemon thread that terminates the program immediately @@ -107,7 +106,7 @@ def run(self): os._exit(1) elif result < 0: # wait failed, just give up and stop polling. - warn("""Parent poll failed. If the frontend dies, + warnings.warn("""Parent poll failed. If the frontend dies, the kernel may be left running. Please let us know about your system (bitness, Python, etc.) at ipython-dev@scipy.org""") diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 821fa7ebc..c40c68584 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -48,7 +48,6 @@ from traitlets import ( Instance, Type, Dict, CBool, CBytes, Any, default, observe ) -from IPython.utils.warn import error from ipykernel.displayhook import ZMQShellDisplayHook from jupyter_core.paths import jupyter_runtime_dir @@ -261,7 +260,7 @@ def edit(self, parameter_s='', last_call=['','']): try: filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call) - except MacroToEdit as e: + except MacroToEdit: # TODO: Implement macro editing over 2 processes. print("Macro editing not yet implemented in 2-process model.") return @@ -336,7 +335,7 @@ def connect_info(self, arg_s): connection_file = get_connection_file() info = get_connection_info(unpack=False) except Exception as e: - error("Could not get connection info: %r" % e) + warnings.warn("Could not get connection info: %r" % e) return # if it's in the default dir, truncate to basename @@ -371,9 +370,9 @@ def qtconsole(self, arg_s): bind_kernel() try: - p = connect_qtconsole(argv=arg_split(arg_s, os.name=='posix')) + connect_qtconsole(argv=arg_split(arg_s, os.name=='posix')) except Exception as e: - error("Could not start qtconsole: %r" % e) + warnings.warn("Could not start qtconsole: %r" % e) return @line_magic @@ -512,7 +511,7 @@ def _showtraceback(self, etype, evalue, stb): if dh.topic: topic = dh.topic.replace(b'execute_result', b'error') - exc_msg = dh.session.send(dh.pub_socket, u'error', json_clean(exc_content), dh.parent_header, ident=topic) + dh.session.send(dh.pub_socket, u'error', json_clean(exc_content), dh.parent_header, ident=topic) # FIXME - Hack: store exception info in shell object. Right now, the # caller is reading this info after the fact, we need to fix this logic From bfd2452d22692e7ce9d4b23fe7f70b022d4d8edb Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 30 Jun 2016 15:29:29 -0700 Subject: [PATCH 0017/1195] Remove relying on IPython's NoOpContextManager. I plan on deprecating it as it is mostly use in readline-base code that is going away. --- ipykernel/eventloops.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 11d1ca3f6..299f9cc7a 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -20,7 +20,10 @@ def _notify_stream_qt(kernel, stream): if _use_appnope() and kernel._darwin_app_nap: from appnope import nope_scope as context else: - from IPython.core.interactiveshell import NoOpContext as context + from contextlib import contextmanager + @contextmanager + def context(): + yield def process_stream_events(): while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN: From e6d32716da8b2c005d611fcda16689a1c9a80157 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 1 Jul 2016 14:18:39 +0200 Subject: [PATCH 0018/1195] Remove thread check for comm sends IOPub is threadsafe as of 4.3 --- ipykernel/comm/comm.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 32ef9d262..0d4a4de4c 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -62,10 +62,6 @@ def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwa def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): """Helper for sending a comm message on IOPub""" - if threading.current_thread().name != 'MainThread' and IOLoop.initialized(): - # make sure we never send on a zmq socket outside the main IOLoop thread - IOLoop.instance().add_callback(lambda : self._publish_msg(msg_type, data, metadata, buffers, **keys)) - return data = {} if data is None else data metadata = {} if metadata is None else metadata content = json_clean(dict(data=data, comm_id=self.comm_id, **keys)) From b595dbd3307d59d02aa34cd0227254601e89631e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 1 Jul 2016 10:02:37 -0700 Subject: [PATCH 0019/1195] Remove unused import --- ipykernel/comm/comm.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 0d4a4de4c..7656f9488 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -3,11 +3,8 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import threading import uuid -from tornado.ioloop import IOLoop - from traitlets.config import LoggingConfigurable from ipykernel.kernelbase import Kernel From a23a0de9419c37c72a6365ae9c59227c6fa2f12d Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 6 Jul 2016 16:30:10 +0100 Subject: [PATCH 0020/1195] Simplify detection of errors for execute_reply messages Closes gh-143 I forget when we introduced ExecutionResult to IPython, but this requires a version of IPython with that machinery. --- ipykernel/ipkernel.py | 76 +++++++++++++++++++------------------------ ipykernel/zmqshell.py | 20 +++++------- 2 files changed, 43 insertions(+), 53 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 39ae2f2a2..1ebade4a5 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -5,7 +5,7 @@ import traceback from IPython.core import release -from ipython_genutils.py3compat import builtin_mod, PY3 +from ipython_genutils.py3compat import builtin_mod, PY3, unicode_type, safe_unicode from IPython.utils.tokenutil import token_at_cursor, line_at_cursor from traitlets import Instance, Type, Any, List @@ -51,9 +51,6 @@ def __init__(self, **kwargs): self.shell.display_pub.session = self.session self.shell.display_pub.pub_socket = self.iopub_socket - # TMP - hack while developing - self.shell._reply_content = None - self.comm_manager = CommManager(parent=self, kernel=self) self.shell.configurables.append(self.comm_manager) @@ -195,41 +192,38 @@ def do_execute(self, code, silent, store_history=True, self._forward_input(allow_stdin) reply_content = {} - # FIXME: the shell calls the exception handler itself. - shell._reply_content = None try: - shell.run_cell(code, store_history=store_history, silent=silent) - except: - status = u'error' - # FIXME: this code right now isn't being used yet by default, - # because the run_cell() call above directly fires off exception - # reporting. This code, therefore, is only active in the scenario - # where runlines itself has an unhandled exception. We need to - # uniformize this, for all exception construction to come from a - # single location in the codbase. - etype, evalue, tb = sys.exc_info() - tb_list = traceback.format_exception(etype, evalue, tb) - reply_content.update(shell._showtraceback(etype, evalue, tb_list)) - else: - status = u'ok' + res = shell.run_cell(code, store_history=store_history, silent=silent) finally: self._restore_input() - reply_content[u'status'] = status + if res.error_before_exec is not None: + err = res.error_before_exec + else: + err = res.error_in_exec - # Return the execution counter so clients can display prompts - reply_content['execution_count'] = shell.execution_count - 1 + if res.success: + reply_content[u'status'] = u'ok' + elif isinstance(err, KeyboardInterrupt): + reply_content[u'status'] = u'abort' + else: + reply_content[u'status'] = u'error' + + reply_content.update({ + u'traceback': shell._last_traceback or [], + u'ename': unicode_type(type(err).__name__), + u'evalue': safe_unicode(err), + }) - # FIXME - fish exception info out of shell, possibly left there by - # runlines. We'll need to clean up this logic later. - if shell._reply_content is not None: - reply_content.update(shell._reply_content) - # reset after use - shell._reply_content = None # FIXME: deprecate piece for ipyparallel: - e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute') + e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, + method='execute') reply_content['engine_info'] = e_info + + # Return the execution counter so clients can display prompts + reply_content['execution_count'] = shell.execution_count - 1 + if 'traceback' in reply_content: self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback'])) @@ -348,25 +342,23 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): item_threshold=self.session.item_threshold, ) - except: + except BaseException as e: # invoke IPython traceback formatting shell.showtraceback() - # FIXME - fish exception info out of shell, possibly left there by - # run_code. We'll need to clean up this logic later. - reply_content = {} - if shell._reply_content is not None: - reply_content.update(shell._reply_content) - # reset after use - shell._reply_content = None - - # FIXME: deprecate piece for ipyparallel: - e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply') - reply_content['engine_info'] = e_info + reply_content = { + u'traceback': shell._last_traceback or [], + u'ename': unicode_type(type(e).__name__), + u'evalue': safe_unicode(e), + } + # FIXME: deprecate piece for ipyparallel: + e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply') + reply_content['engine_info'] = e_info self.send_response(self.iopub_socket, u'error', reply_content, ident=self._topic('error')) self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback'])) result_buf = [] + reply_content['status'] = 'error' else: reply_content = {'status' : 'ok'} diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index c40c68584..b209ee6f6 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -493,6 +493,10 @@ def ask_exit(self): ) self.payload_manager.write_payload(payload) + def run_cell(self, *args, **kwargs): + self._last_traceback = None + return super(ZMQInteractiveShell, self).run_cell(*args, **kwargs) + def _showtraceback(self, etype, evalue, stb): # try to preserve ordering of tracebacks and print statements sys.stdout.flush() @@ -511,18 +515,12 @@ def _showtraceback(self, etype, evalue, stb): if dh.topic: topic = dh.topic.replace(b'execute_result', b'error') - dh.session.send(dh.pub_socket, u'error', json_clean(exc_content), dh.parent_header, ident=topic) - - # FIXME - Hack: store exception info in shell object. Right now, the - # caller is reading this info after the fact, we need to fix this logic - # to remove this hack. Even uglier, we need to store the error status - # here, because in the main loop, the logic that sets it is being - # skipped because runlines swallows the exceptions. - exc_content[u'status'] = u'error' - self._reply_content = exc_content - # /FIXME + exc_msg = dh.session.send(dh.pub_socket, u'error', json_clean(exc_content), + dh.parent_header, ident=topic) - return exc_content + # FIXME - Once we rely on Python 3, the traceback is stored on the + # exception object, so we shouldn't need to store it here. + self._last_traceback = stb def set_next_input(self, text, replace=False): """Send the specified text to the frontend to be presented at the next From 3dd9fc29d1904f278adca3d8b5a5b3e6b4600be8 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 6 Jul 2016 11:45:23 -0700 Subject: [PATCH 0021/1195] Don't use IPython.utils.warn (Deprecated). And InlineBackaedConfig was renamed in 0.12 (2011): `https://github.com/ipython/ipython/pull/923` So we can likely remove the warning. --- ipykernel/pylab/config.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index ea195de48..bd3dedf9f 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -13,12 +13,10 @@ # Imports #----------------------------------------------------------------------------- -from traitlets.config import Config from traitlets.config.configurable import SingletonConfigurable from traitlets import ( Dict, Instance, Set, Bool, TraitError, Unicode ) -from IPython.utils.warn import warn #----------------------------------------------------------------------------- # Configurable for inline backend options @@ -41,12 +39,6 @@ class InlineBackendConfig(SingletonConfigurable): class InlineBackend(InlineBackendConfig): """An object to store configuration of the inline backend.""" - def _config_changed(self, name, old, new): - # warn on change of renamed config section - if new.InlineBackendConfig != getattr(old, 'InlineBackendConfig', Config()): - warn("InlineBackendConfig has been renamed to InlineBackend") - super(InlineBackend, self)._config_changed(name, old, new) - # 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. From 961018b5c9bd383e690d675025f98c032df3af06 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 8 Jul 2016 20:48:21 -0700 Subject: [PATCH 0022/1195] Don't use Deprecated features and Turn On DeprecationWarnigns ! --- ipykernel/eventloops.py | 10 +++++++++- setup.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 299f9cc7a..dde402a67 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -6,12 +6,20 @@ import os import sys +import platform import zmq +from distutils.version import LooseVersion as V from traitlets.config.application import Application from IPython.utils import io -from IPython.lib.inputhook import _use_appnope + +def _use_appnope(): + """Should we use appnope for dealing with OS X app nap? + + Checks if we are on OS X 10.9 or greater. + """ + return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9') def _notify_stream_qt(kernel, stream): diff --git a/setup.py b/setup.py index d2b087248..034439f0c 100644 --- a/setup.py +++ b/setup.py @@ -86,7 +86,7 @@ ] extras_require = setuptools_args['extras_require'] = { - 'test:python_version=="2.7"': ['mock'], + 'test:python_version=="2.7"': ['mock', 'nose_warnings_filters'], } if 'setuptools' in sys.modules: From 3fb05c39426d97e554ff36cc68e975a6d3bd6105 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 9 Jul 2016 14:12:31 -0700 Subject: [PATCH 0023/1195] Time tests. Just to have an idea of what takes the most time, in case we ever want to optimise that or, if one day a test is slow figure out if it **was** slow before. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a3c37fab6..d827432bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,9 @@ before_install: - git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels install: - pip install -f travis-wheels/wheelhouse --pre -e . codecov nose_warnings_filters - - pip install -f travis-wheels/wheelhouse ipykernel[test] + - pip install -f travis-wheels/wheelhouse ipykernel[test] nose-timer - python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' script: - - nosetests --with-coverage --cover-package ipykernel ipykernel + - nosetests --with-coverage --with-timer --cover-package ipykernel ipykernel after_success: - codecov From dfd54edf813f0907e036c2553eff4bc360fd68ce Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 11 Jul 2016 23:34:37 -0500 Subject: [PATCH 0024/1195] Don't self-use deprecated API --- ipykernel/pickleutil.py | 9 ++++++--- ipykernel/tests/test_pickleutil.py | 4 ++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index fecb8eb7e..4528e25cb 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -8,7 +8,6 @@ warnings.warn("ipykernel.pickleutil is deprecated. It has moved to ipyparallel.", DeprecationWarning) import copy -import logging import sys from types import FunctionType @@ -21,9 +20,13 @@ from ipython_genutils.importstring import import_item from ipython_genutils.py3compat import string_types, iteritems, buffer_to_bytes, buffer_to_bytes_py2 -from . import codeutil # This registers a hook when it's imported +# This registers a hook when it's imported +try: + from ipyparallel.serialize import codeutil +except ImportError: + # Deprecated since .... + from ipykernel import codeutil -from traitlets.config import Application from traitlets.log import get_logger if py3compat.PY3: diff --git a/ipykernel/tests/test_pickleutil.py b/ipykernel/tests/test_pickleutil.py index 460e4aeb8..c8dccb4ca 100644 --- a/ipykernel/tests/test_pickleutil.py +++ b/ipykernel/tests/test_pickleutil.py @@ -3,8 +3,8 @@ import pickle import nose.tools as nt -from ipykernel import codeutil -from ipykernel.pickleutil import can, uncan + +from ipykernel.pickleutil import can, uncan, codeutil def interactive(f): f.__module__ = '__main__' From 70e096805778cb79eb1729f5bb2f9ddebf85e001 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 13 Jul 2016 19:54:24 -0500 Subject: [PATCH 0025/1195] Make inline the default matplotlib backend uses MPLBACKEND env (matplotlib > 1.5). Trigger extra IPython inline setup when inline backend is loaded. We can't call the simpler `ip.enable_matplotlib()` here because this will be triggered during pyplot import, and enable_matplotlib() imports pyplot which isn't ready yet. --- ipykernel/kernelapp.py | 7 +++++++ ipykernel/pylab/backend_inline.py | 11 +++++++++++ 2 files changed, 18 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 729aa5f4c..70963578e 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -368,6 +368,13 @@ def init_kernel(self): def init_gui_pylab(self): """Enable GUI event loop integration, taking pylab into account.""" + # Register inline backend as default + # this is higher priority than matplotlibrc, + # but lower priority than anything else (mpl.use() for instance). + # This only affects matplotlib >= 1.5 + if not os.environ.get('MPLBACKEND'): + os.environ['MPLBACKEND'] = 'module://ipykernel.pylab.backend_inline' + # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab` # to ensure that any exception is printed straight to stderr. # Normally _showtraceback associates the reply with an execution, diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index 54f465ca3..d53b38607 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -143,3 +143,14 @@ def flush_figures(): # figurecanvas. This is set here to a Agg canvas # See https://github.com/matplotlib/matplotlib/pull/1125 FigureCanvas = FigureCanvasAgg + +def _enable_matplotlib_integration(): + """Enable extra IPython matplotlib integration when we are loaded as the matplotlib backend.""" + from matplotlib import get_backend + from IPython.core.pylabtools import configure_inline_support + ip = get_ipython() + backend = get_backend() + if ip and backend == 'module://%s' % __name__: + configure_inline_support(ip, backend) + +_enable_matplotlib_integration() From 67ffacf144df609e39d041fa4570b7faac6485ef Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 19 Jul 2016 13:42:17 +0100 Subject: [PATCH 0026/1195] Ensure basic displayhook sends valid execute_result messages This fixes a crash I was seeing with the xonsh kernel. Closes gh-161 --- ipykernel/displayhook.py | 9 ++++++++- ipykernel/kernelapp.py | 6 +++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index c41ddaa93..6e263abf2 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -22,6 +22,10 @@ def __init__(self, session, pub_socket): self.pub_socket = pub_socket self.parent_header = {} + def get_execution_count(self): + """This method is replaced in kernelapp""" + return 0 + def __call__(self, obj): if obj is None: return @@ -29,7 +33,10 @@ def __call__(self, obj): builtin_mod._ = obj sys.stdout.flush() sys.stderr.flush() - self.session.send(self.pub_socket, u'execute_result', {u'data':repr(obj)}, + contents = {u'execution_count': self.get_execution_count(), + u'data': {'text/plain': repr(obj)}, + u'metadata': {}} + self.session.send(self.pub_socket, u'execute_result', contents, parent=self.parent_header, ident=self.topic) def set_parent(self, parent): diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 729aa5f4c..fcbe5e479 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -314,7 +314,8 @@ def init_io(self): sys.stderr = outstream_factory(self.session, self.iopub_thread, u'stderr') if self.displayhook_class: displayhook_factory = import_item(str(self.displayhook_class)) - sys.displayhook = displayhook_factory(self.session, self.iopub_socket) + self.displayhook = displayhook_factory(self.session, self.iopub_socket) + sys.displayhook = self.displayhook self.patch_io() @@ -365,6 +366,9 @@ def init_kernel(self): kernel.record_ports(self.ports) self.kernel = kernel + # Allow the displayhook to get the execution count + self.displayhook.get_execution_count = lambda: kernel.execution_count + def init_gui_pylab(self): """Enable GUI event loop integration, taking pylab into account.""" From e4d555a8495d8e318cf56b9a77a97439642bdc38 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 26 Jul 2016 15:11:42 +0200 Subject: [PATCH 0027/1195] note MPLBACKEND in changelog --- docs/changelog.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index ed938d558..42a142e49 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,21 @@ Changes in IPython kernel ========================= +4.4 +--- + +4.4.0 +***** + +`4.4.0 on GitHub `__ + +- Use `MPLBACKEND`_ environment variable to tell matplotlib >= 1.5 use use the inline backend by default. + This is only done if MPLBACKEND is not already set and no backend has been explicitly loaded, + so setting ``MPLBACKEND=Qt4Agg`` or calling ``%matplotlib notebook`` or ``matplotlib.use('Agg')`` + will take precedence. + +.. _MPLBACKEND: http://matplotlib.org/devel/coding_guide.html?highlight=mplbackend#developing-a-new-backend + 4.3 --- From 26178dc99da3b0ac2fe9eb65155892e62e3ae538 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 28 Jul 2016 12:15:37 +0200 Subject: [PATCH 0028/1195] inprocess: cleanup iopub thread on shutdown avoids leaking threads --- ipykernel/inprocess/manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/inprocess/manager.py b/ipykernel/inprocess/manager.py index f46d89d53..ccdaccadf 100644 --- a/ipykernel/inprocess/manager.py +++ b/ipykernel/inprocess/manager.py @@ -46,6 +46,7 @@ def start_kernel(self, **kwds): self.kernel = InProcessKernel(parent=self, session=self.session) def shutdown_kernel(self): + self.kernel.iopub_thread.stop() self._kill_kernel() def restart_kernel(self, now=False, **kwds): From 2f4d5748f4a7656be21121a020dc69d6b4d2ad7e Mon Sep 17 00:00:00 2001 From: Johan Forsberg Date: Thu, 28 Jul 2016 19:11:32 +0200 Subject: [PATCH 0029/1195] Add a --profile flag to install Setting the IPython profile to be used by the kernel. --- ipykernel/kernelspec.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 10d320859..4bc8f5226 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -44,22 +44,22 @@ def make_ipkernel_cmd(mod='ipykernel', executable=None, extra_arguments=None, ** if executable is None: executable = sys.executable extra_arguments = extra_arguments or [] - arguments = [ executable, '-m', mod, '-f', '{connection_file}' ] + arguments = [executable, '-m', mod, '-f', '{connection_file}'] arguments.extend(extra_arguments) return arguments -def get_kernel_dict(): +def get_kernel_dict(extra_arguments): """Construct dict for kernel.json""" return { - 'argv': make_ipkernel_cmd(), + 'argv': make_ipkernel_cmd(extra_arguments=extra_arguments), 'display_name': 'Python %i' % sys.version_info[0], 'language': 'python', } -def write_kernel_spec(path=None, overrides=None): +def write_kernel_spec(path=None, overrides=None, extra_arguments=None): """Write a kernel spec directory to `path` If `path` is not specified, a temporary directory is created. @@ -73,7 +73,8 @@ def write_kernel_spec(path=None, overrides=None): # stage resources shutil.copytree(RESOURCES, path) # write kernel.json - kernel_dict = get_kernel_dict() + kernel_dict = get_kernel_dict(extra_arguments) + if overrides: kernel_dict.update(overrides) with open(pjoin(path, 'kernel.json'), 'w') as f: @@ -82,7 +83,8 @@ def write_kernel_spec(path=None, overrides=None): return path -def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, display_name=None, prefix=None): +def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, display_name=None, + profile=None, prefix=None): """Install the IPython kernelspec for Jupyter Parameters @@ -96,12 +98,14 @@ def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, displ kernel_name: str, optional Specify a name for the kernelspec. This is needed for having multiple IPython kernels for different environments. + display_name: str, optional + Specify the display name for the kernelspec + profile: str, optional + Specify a custom profile to be loaded by the kernel. prefix: str, optional Specify an install prefix for the kernelspec. This is needed to install into a non-default location, such as a conda/virtual-env. - display_name: str, optional - Specify the display name for the kernelspec - + Returns ------- @@ -118,9 +122,13 @@ def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, displ overrides = dict(display_name=display_name) else: overrides = None - path = write_kernel_spec(overrides=overrides) - dest = kernel_spec_manager.install_kernel_spec(path, - kernel_name=kernel_name, user=user, prefix=prefix) + if profile: + extra_arguments = ["--profile", profile] + else: + extra_arguments = None + path = write_kernel_spec(overrides=overrides, extra_arguments=extra_arguments) + dest = kernel_spec_manager.install_kernel_spec( + path, kernel_name=kernel_name, user=user, prefix=prefix) # cleanup afterward shutil.rmtree(path) return dest @@ -151,6 +159,9 @@ def start(self): parser.add_argument('--display-name', type=str, help="Specify the display name for the kernelspec." " This is helpful when you have multiple IPython kernels.") + parser.add_argument('--profile', type=str, + help="Specify an IPython profile to load. " + "This can be used to create custom versions of the kernel.") parser.add_argument('--prefix', type=str, help="Specify an install prefix for the kernelspec." " This is needed to install into a non-default location, such as a conda/virtual-env.") @@ -159,9 +170,8 @@ def start(self): " Shorthand for --prefix='%s'. For use in conda/virtual-envs." % sys.prefix) opts = parser.parse_args(self.argv) try: - dest = install(user=opts.user, kernel_name=opts.name, prefix=opts.prefix, - display_name=opts.display_name, - ) + dest = install(user=opts.user, kernel_name=opts.name, profile=opts.profile, + prefix=opts.prefix, display_name=opts.display_name) except OSError as e: if e.errno == errno.EACCES: print(e, file=sys.stderr) From 627676ec3374e47af7e3b9b284e7210d4e1734cf Mon Sep 17 00:00:00 2001 From: Johan Forsberg Date: Thu, 28 Jul 2016 19:25:20 +0200 Subject: [PATCH 0030/1195] Make extra_arguments optional --- ipykernel/kernelspec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 4bc8f5226..9f5512632 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -50,7 +50,7 @@ def make_ipkernel_cmd(mod='ipykernel', executable=None, extra_arguments=None, ** return arguments -def get_kernel_dict(extra_arguments): +def get_kernel_dict(extra_arguments=None): """Construct dict for kernel.json""" return { 'argv': make_ipkernel_cmd(extra_arguments=extra_arguments), From 337a0d545d2499dbccac7233d67f277d0bf6dc41 Mon Sep 17 00:00:00 2001 From: Johan Forsberg Date: Thu, 28 Jul 2016 19:26:36 +0200 Subject: [PATCH 0031/1195] Add kernelspec test for profile --- ipykernel/tests/test_kernelspec.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index c849436fe..45345b713 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -52,6 +52,18 @@ def test_get_kernel_dict(): assert_kernel_dict(d) +def assert_kernel_dict_with_profile(d): + nt.assert_equal(d['argv'], make_ipkernel_cmd( + extra_arguments=["--profile", "test"])) + nt.assert_equal(d['display_name'], 'Python %i' % sys.version_info[0]) + nt.assert_equal(d['language'], 'python') + + +def test_get_kernel_dict_with_profile(): + d = get_kernel_dict(["--profile", "test"]) + assert_kernel_dict_with_profile(d) + + def assert_is_spec(path): for fname in os.listdir(RESOURCES): dst = pjoin(path, fname) From 62acde9a11cb3119f1d66d97699da54da5f0fd62 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 1 Aug 2016 10:51:16 +0200 Subject: [PATCH 0032/1195] comply with msg spec for connect_reply --- ipykernel/kernelapp.py | 4 +++- ipykernel/tests/test_message_spec.py | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index fcbe5e479..5e1f8bb76 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -363,7 +363,9 @@ def init_kernel(self): profile_dir=self.profile_dir, user_ns=self.user_ns, ) - kernel.record_ports(self.ports) + kernel.record_ports({ + name + '_port': port for name, port in self.ports.items() + }) self.kernel = kernel # Allow the displayhook to get the execution count diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 334a3add7..06c81b0d3 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -164,6 +164,15 @@ def check(self, d): Reference.check(self, d) LanguageInfo().check(d['language_info']) + +class ConnectReply(Reference): + shell_port = Integer() + control_port = Integer() + stdin_port = Integer() + iopub_port = Integer() + hb_port = Integer() + + class CommInfoReply(Reference): comms = Dict() @@ -212,6 +221,7 @@ class HistoryReply(Reference): 'status' : Status(), 'complete_reply' : CompleteReply(), 'kernel_info_reply': KernelInfoReply(), + 'connect_reply': ConnectReply(), 'comm_info_reply': CommInfoReply(), 'is_complete_reply': IsCompleteReply(), 'execute_input' : ExecuteInput(), @@ -424,6 +434,17 @@ def test_kernel_info_request(): validate_message(reply, 'kernel_info_reply', msg_id) +def test_connect_request(): + flush_channels() + msg = KC.session.msg('connect_request') + KC.shell_channel.send(msg) + return msg['header']['msg_id'] + + msg_id = KC.kernel_info() + reply = KC.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'connect_reply', msg_id) + + def test_comm_info_request(): flush_channels() if not hasattr(KC, 'comm_info'): From 3bc6ad10617bd00efffa840c2c57069a0b5dae9a Mon Sep 17 00:00:00 2001 From: Johan Forsberg Date: Mon, 1 Aug 2016 10:58:48 +0200 Subject: [PATCH 0033/1195] Reorder install arguments for backwards compat --- ipykernel/kernelspec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 9f5512632..ff40e6b1f 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -84,7 +84,7 @@ def write_kernel_spec(path=None, overrides=None, extra_arguments=None): def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, display_name=None, - profile=None, prefix=None): + prefix=None, profile=None): """Install the IPython kernelspec for Jupyter Parameters From c5ffa0d3eb63746c06c5a98f08460f20c2715a81 Mon Sep 17 00:00:00 2001 From: Johan Forsberg Date: Mon, 1 Aug 2016 10:59:27 +0200 Subject: [PATCH 0034/1195] Append profile to display name unless specified --- ipykernel/kernelspec.py | 8 +++++--- ipykernel/tests/test_kernelspec.py | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index ff40e6b1f..dcd57d4c1 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -118,12 +118,14 @@ def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, displ # kernel_name is specified and display_name is not # default display_name to kernel_name display_name = kernel_name + overrides = {} if display_name: - overrides = dict(display_name=display_name) - else: - overrides = None + overrides["display_name"] = display_name if profile: extra_arguments = ["--profile", profile] + if not display_name: + # add the profile to the default display name + overrides["display_name"] = 'Python %i [profile=%s]' % (sys.version_info[0], profile) else: extra_arguments = None path = write_kernel_spec(overrides=overrides, extra_arguments=extra_arguments) diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index 45345b713..f1c0c4282 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -119,3 +119,28 @@ def test_install(): assert_is_spec(os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME)) +def test_install_profile(): + system_jupyter_dir = tempfile.mkdtemp() + + with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', + [system_jupyter_dir]): + install(profile="Test") + + spec = os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME, "kernel.json") + with open(spec) as f: + spec = json.load(f) + nt.assert_true(spec["display_name"].endswith(" [profile=Test]")) + nt.assert_equal(spec["argv"][-2:], ["--profile", "Test"]) + + +def test_install_display_name_overrides_profile(): + system_jupyter_dir = tempfile.mkdtemp() + + with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', + [system_jupyter_dir]): + install(display_name="Display", profile="Test") + + spec = os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME, "kernel.json") + with open(spec) as f: + spec = json.load(f) + nt.assert_equal(spec["display_name"], "Display") From 44e271940098d9ae28279569076aeb6fcdb07e4a Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 1 Aug 2016 11:53:47 +0200 Subject: [PATCH 0035/1195] don't print ctrl-C message if --quiet --- ipykernel/kernelapp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index fcbe5e479..bd6968da3 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -288,7 +288,8 @@ def log_connection_info(self): for line in lines: self.log.info(line) # also raw print to the terminal if no parent_handle (`ipython kernel`) - if not self.parent_handle: + # unless log-level is CRITICAL (--quiet) + if not self.parent_handle and self.log_level < logging.CRITICAL: io.rprint(_ctrl_c_message) for line in lines: io.rprint(line) From a9ad5e264c92fc705fa8cae3037740a2db7d7c6d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 5 Aug 2016 10:42:29 -0700 Subject: [PATCH 0036/1195] Fill-in versions numbers --- ipykernel/codeutil.py | 4 ++-- ipykernel/pickleutil.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ipykernel/codeutil.py b/ipykernel/codeutil.py index 1d88b11df..49638d05e 100644 --- a/ipykernel/codeutil.py +++ b/ipykernel/codeutil.py @@ -14,7 +14,7 @@ # Distributed under the terms of the Modified BSD License. import warnings -warnings.warn("ipykernel.codeutil is deprecated. It has moved to ipyparallel.serialize", DeprecationWarning) +warnings.warn("ipykernel.codeutil is deprecated since IPytkernel 4.3.1. It has moved to ipyparallel.serialize", DeprecationWarning) import sys import types @@ -35,4 +35,4 @@ def reduce_code(co): args.insert(1, co.co_kwonlyargcount) return code_ctor, tuple(args) -copyreg.pickle(types.CodeType, reduce_code) \ No newline at end of file +copyreg.pickle(types.CodeType, reduce_code) diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 4528e25cb..205a00687 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -22,9 +22,10 @@ # This registers a hook when it's imported try: + # available since ipyparallel 5.1.1 from ipyparallel.serialize import codeutil except ImportError: - # Deprecated since .... + # Deprecated since ipykernel 4.3.1 from ipykernel import codeutil from traitlets.log import get_logger From 4763bd088c923ee7ef5daae9b4f93f7e2251132e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 5 Aug 2016 14:16:46 -0700 Subject: [PATCH 0037/1195] Fix typo: extra `t` in IPykernel. --- ipykernel/codeutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/codeutil.py b/ipykernel/codeutil.py index 49638d05e..3946a98a2 100644 --- a/ipykernel/codeutil.py +++ b/ipykernel/codeutil.py @@ -14,7 +14,7 @@ # Distributed under the terms of the Modified BSD License. import warnings -warnings.warn("ipykernel.codeutil is deprecated since IPytkernel 4.3.1. It has moved to ipyparallel.serialize", DeprecationWarning) +warnings.warn("ipykernel.codeutil is deprecated since IPykernel 4.3.1. It has moved to ipyparallel.serialize", DeprecationWarning) import sys import types From a2614d71447829cf8a64984776328471e279bab5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 3 Aug 2016 13:00:02 +0200 Subject: [PATCH 0038/1195] ensure status is included in all replies according to the spec --- ipykernel/ipkernel.py | 7 +++++-- ipykernel/kernelbase.py | 9 +++++--- ipykernel/tests/test_message_spec.py | 31 ++++++++++++++++++---------- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 1ebade4a5..230563050 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -205,7 +205,7 @@ def do_execute(self, code, silent, store_history=True, if res.success: reply_content[u'status'] = u'ok' elif isinstance(err, KeyboardInterrupt): - reply_content[u'status'] = u'abort' + reply_content[u'status'] = u'aborted' else: reply_content[u'status'] = u'error' @@ -296,7 +296,10 @@ def do_history(self, hist_access_type, output, raw, session=None, start=None, else: hist = [] - return {'history' : list(hist)} + return { + 'status': 'ok', + 'history' : list(hist), + } def do_shutdown(self, restart): self.shell.exit_now = True diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 9b441375d..e5c831e7f 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -467,13 +467,14 @@ def do_history(self, hist_access_type, output, raw, session=None, start=None, stop=None, n=None, pattern=None, unique=False): """Override in subclasses to access history. """ - return {'history': []} + return {'status': 'ok', 'history': []} def connect_request(self, stream, ident, parent): if self._recorded_ports is not None: content = self._recorded_ports.copy() else: content = {} + content['status'] = 'ok' msg = self.session.send(stream, 'connect_reply', content, parent, ident) self.log.debug("%s", msg) @@ -490,8 +491,10 @@ def kernel_info(self): } def kernel_info_request(self, stream, ident, parent): + content = {'status': 'ok'} + content.update(self.kernel_info) msg = self.session.send(stream, 'kernel_info_reply', - self.kernel_info, parent, ident) + content, parent, ident) self.log.debug("%s", msg) def comm_info_request(self, stream, ident, parent): @@ -507,7 +510,7 @@ def comm_info_request(self, stream, ident, parent): } else: comms = {} - reply_content = dict(comms=comms) + reply_content = dict(comms=comms, status='ok') msg = self.session.send(stream, 'comm_info_reply', reply_content, parent, ident) self.log.debug("%s", msg) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 06c81b0d3..4991ec288 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -103,11 +103,14 @@ def _data_changed(self, name, old, new): assert mime_pat.match(k) nt.assert_is_instance(v, string_types) + # shell replies +class Reply(Reference): + status = Enum((u'ok', u'error'), default_value=u'ok') -class ExecuteReply(Reference): + +class ExecuteReply(Reply): execution_count = Integer() - status = Enum((u'ok', u'error'), default_value=u'ok') def check(self, d): Reference.check(self, d) @@ -117,18 +120,18 @@ def check(self, d): ExecuteReplyError().check(d) -class ExecuteReplyOkay(Reference): - payload = List(Dict()) +class ExecuteReplyOkay(Reply): + status = Enum(('ok',)) user_expressions = Dict() -class ExecuteReplyError(Reference): +class ExecuteReplyError(Reply): ename = Unicode() evalue = Unicode() traceback = List(Unicode()) -class InspectReply(MimeBundle): +class InspectReply(Reply, MimeBundle): found = Bool() @@ -143,17 +146,19 @@ class Status(Reference): execution_state = Enum((u'busy', u'idle', u'starting'), default_value=u'busy') -class CompleteReply(Reference): +class CompleteReply(Reply): matches = List(Unicode()) cursor_start = Integer() cursor_end = Integer() status = Unicode() + class LanguageInfo(Reference): name = Unicode('python') version = Unicode(sys.version.split()[0]) -class KernelInfoReply(Reference): + +class KernelInfoReply(Reply): protocol_version = Version(min='5.0') implementation = Unicode('ipython') implementation_version = Version(min='2.1') @@ -173,9 +178,10 @@ class ConnectReply(Reference): hb_port = Integer() -class CommInfoReply(Reference): +class CommInfoReply(Reply): comms = Dict() + class IsCompleteReply(Reference): status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'), default_value=u'complete') @@ -184,6 +190,7 @@ def check(self, d): if d['status'] == 'incomplete': IsCompleteReplyIncomplete().check(d) + class IsCompleteReplyIncomplete(Reference): indent = Unicode() @@ -195,7 +202,9 @@ class ExecuteInput(Reference): execution_count = Integer() -Error = ExecuteReplyError +class Error(ExecuteReplyError): + """Errors are the same as ExecuteReply, but without status""" + status = None # no status field class Stream(Reference): @@ -211,7 +220,7 @@ class ExecuteResult(MimeBundle): execution_count = Integer() -class HistoryReply(Reference): +class HistoryReply(Reply): history = List(List()) From 0764e22b118cf6473e39ffff4ca520d40aca8232 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 8 Aug 2016 13:20:04 +0200 Subject: [PATCH 0039/1195] Release notes for 4.4 --- docs/changelog.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 42a142e49..9f2da3560 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -13,9 +13,19 @@ Changes in IPython kernel This is only done if MPLBACKEND is not already set and no backend has been explicitly loaded, so setting ``MPLBACKEND=Qt4Agg`` or calling ``%matplotlib notebook`` or ``matplotlib.use('Agg')`` will take precedence. +- Fixes for logging problems caused by 4.3, + where logging could go to the terminal instead of the notebook. +- Add ``--sys-prefix`` and ``--profile`` arguments to :command:`ipython kernel install` +- Allow Comm (Widget) messages to be sent from background threads. +- Select inline matplotlib backend by default if ``%matplotlib`` magic or + ``matplotlib.use()`` are not called explicitly (for matplotlib >= 1.5). +- Fix some longstanding minor deviations from the message protocol + (missing status: ok in a few replies, connect_reply format). +- Remove calls to NoOpContext from IPython, deprecated in 5.0. .. _MPLBACKEND: http://matplotlib.org/devel/coding_guide.html?highlight=mplbackend#developing-a-new-backend + 4.3 --- From d32e98375de41f9770c5701a4e6315ae338572e5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 8 Aug 2016 14:52:43 +0200 Subject: [PATCH 0040/1195] release 4.4 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 05de0058a..3598dbefe 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 3, 0, 'dev') +version_info = (4, 4, 0) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 0) From d9ace89a937736f0c94ecb483045b386664979ad Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 9 Aug 2016 13:05:56 +0200 Subject: [PATCH 0041/1195] catch circular import error configuring matplotlib on import only python 2 affected, caused by unnecessary imports in IPython pylabtools --- ipykernel/pylab/backend_inline.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index d53b38607..63b96935c 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -147,10 +147,17 @@ def flush_figures(): def _enable_matplotlib_integration(): """Enable extra IPython matplotlib integration when we are loaded as the matplotlib backend.""" from matplotlib import get_backend - from IPython.core.pylabtools import configure_inline_support ip = get_ipython() backend = get_backend() if ip and backend == 'module://%s' % __name__: - configure_inline_support(ip, backend) + from IPython.core.pylabtools import configure_inline_support + try: + configure_inline_support(ip, backend) + except ImportError: + # bugs may cause a circular import on Python 2 + def configure_once(*args): + configure_inline_support(ip, backend) + ip.events.unregister('post_run_cell', configure_once) + ip.events.register('post_run_cell', configure_once) _enable_matplotlib_integration() From 2075df2a390a00baabc879911db9fefe8877ca23 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 9 Aug 2016 13:12:30 +0200 Subject: [PATCH 0042/1195] test import matplotlib --- .travis.yml | 4 ++++ ipykernel/tests/test_kernel.py | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/.travis.yml b/.travis.yml index d827432bc..cb12c722f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,10 @@ before_install: install: - pip install -f travis-wheels/wheelhouse --pre -e . codecov nose_warnings_filters - pip install -f travis-wheels/wheelhouse ipykernel[test] nose-timer + - | + if [[ "$TRAVIS_PYTHON_VERSION" != "3.3" ]]; then + pip install matplotlib + fi - python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' script: - nosetests --with-coverage --with-timer --cover-package ipykernel ipykernel diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 809e5191d..f0c0d0183 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -6,6 +6,7 @@ import io import os.path +import pprint import sys import time @@ -28,6 +29,12 @@ def _check_master(kc, expected=True, stream="stdout"): nt.assert_equal(stdout.strip(), repr(expected)) +def _check_status(content): + """If status=error, show the traceback""" + if content['status'] == 'error': + nt.assert_true(False, ''.join(['\n'] + content['traceback'])) + + # printing tests def test_simple_print(): @@ -206,6 +213,7 @@ def test_help_output(): """ipython kernel --help-all works""" tt.help_all_output_test('kernel') + def test_is_complete(): with kernel() as kc: # There are more test cases for this in core - here we just check @@ -224,6 +232,7 @@ def test_is_complete(): assert reply['content']['status'] == 'incomplete' assert reply['content']['indent'] == '' + def test_complete(): with kernel() as kc: execute(u'a = 1', kc=kc) @@ -241,6 +250,22 @@ def test_complete(): nt.assert_equal(match[:2], 'a.') +@dec.skip_without('matplotlib') +def test_matplotlib_inline_on_import(): + with kernel() as kc: + cell = '\n'.join([ + 'import matplotlib, matplotlib.pyplot as plt', + 'backend = matplotlib.get_backend()' + ]) + _, reply = execute(cell, + user_expressions={'backend': 'backend'}, + kc=kc) + _check_status(reply) + backend_bundle = reply['user_expressions']['backend'] + _check_status(backend_bundle) + nt.assert_in('backend_inline', backend_bundle['data']['text/plain']) + + def test_shutdown(): """Kernel exits after polite shutdown_request""" with new_kernel() as kc: From 21761332a95486573712a3917b1fc3ba071451b1 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 9 Aug 2016 13:33:05 +0200 Subject: [PATCH 0043/1195] note circular import changes for 4.4.1 --- docs/changelog.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 9f2da3560..0835d0a75 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,14 @@ Changes in IPython kernel 4.4 --- +4.4.1 +***** + +`4.4.1 on GitHub `__ + +- Fix circular import of matplotlib on Python 2 caused by the inline backend changes in 4.4.0. + + 4.4.0 ***** From ae3ccc2b2a24347d3ea0bf528850d3c48f6ad7ef Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 9 Aug 2016 13:33:20 +0200 Subject: [PATCH 0044/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 3598dbefe..d762c777b 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 4, 0) +version_info = (4, 5, 0, 'dev') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 0) From 8efe67407b18a50a22b48911d71a24c95a490bfa Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 9 Aug 2016 14:22:59 +0200 Subject: [PATCH 0045/1195] Release 4.4.1 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index d762c777b..e84657f5d 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 5, 0, 'dev') +version_info = (4, 4, 1) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 0) From bfae5a42c8cb74b515e09a292e50706985dde6c6 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 9 Aug 2016 14:24:59 +0200 Subject: [PATCH 0046/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index e84657f5d..d762c777b 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 4, 1) +version_info = (4, 5, 0, 'dev') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 0) From 461ef1a647839c5f2221c0606d3eaa98dbcfdf3c Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 11 Aug 2016 13:44:16 +0200 Subject: [PATCH 0047/1195] default to session=0, start=0 instead of None, which is not a valid value according to IPython --- ipykernel/ipkernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 230563050..8d726a4b6 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -280,7 +280,7 @@ def do_inspect(self, code, cursor_pos, detail_level=0): return reply_content - def do_history(self, hist_access_type, output, raw, session=None, start=None, + def do_history(self, hist_access_type, output, raw, session=0, start=0, stop=None, n=None, pattern=None, unique=False): if hist_access_type == 'tail': hist = self.shell.history_manager.get_tail(n, raw=raw, output=output, From d35fcacc248d5c17511b5fae75a697f47a57e5d3 Mon Sep 17 00:00:00 2001 From: Hassan Kibirige Date: Fri, 12 Aug 2016 18:29:22 -0500 Subject: [PATCH 0048/1195] Set the dpi with figure.dpi instead of savefig.dpi This makes changing the dpi more straight forward and controllable within a context. For example, the code below now works as expected. ```python with mpl.rc_context(): mpl.rcParams['figure.dpi'] = 200 mpl.rcParams['figure.figsize'] = (6, 4) ax = plt.subplot(111) ax.scatter([1, 2], [1, 2]) ``` Without this commit and the accompanying commit in `ipython`, the `dpi` would be queried directly from the `rcParams` and this would happen outside the context. With this commit, Matplotlib creates a figure with the correct `dpi` as a property and `ipython` will always use that or modify it accordingly. Also by using `savefig.dpi`, the internal workings of the backend were slipping into userspace. `InlineBackend` uses the same method as used for saving but as far as the user is concerned, what is happening is an image-display not an image-save. --- ipykernel/pylab/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index bd3dedf9f..249389fab 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -50,7 +50,7 @@ class InlineBackend(InlineBackendConfig): 'font.size': 10, # 72 dpi matches SVG/qtconsole # this only affects PNG export, as SVG has no dpi setting - 'savefig.dpi': 72, + 'figure.dpi': 72, # 10pt still needs a little more room on the xlabel: 'figure.subplot.bottom' : .125 }, From 1dcdd7ce789ba1e2e58e362c40434a3641eee21c Mon Sep 17 00:00:00 2001 From: Vlad Frolov Date: Sat, 13 Aug 2016 19:00:58 +0300 Subject: [PATCH 0049/1195] Added missing `errors` attribute to OutStream (fixes #177) --- ipykernel/iostream.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index dae05af23..5c7f96e93 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -205,6 +205,8 @@ def __init__(self, session, pub_thread, name, pipe=None): warnings.warn("pipe argument to OutStream is deprecated and ignored", DeprecationWarning) self.encoding = 'UTF-8' + # This is necessary for compatibility with Python built-in streams + self.errors = None self.session = session if not isinstance(pub_thread, IOPubThread): # Backward-compat: given socket, not thread. Wrap in a thread. From 09416a475bd7963c680eaae34cbb92ffde6f914e Mon Sep 17 00:00:00 2001 From: Jens Hedegaard Nielsen Date: Thu, 18 Aug 2016 20:41:06 +0100 Subject: [PATCH 0050/1195] Add support for ipympl backend Needs matching change in IPython --- ipykernel/eventloops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index dde402a67..6c81cfdcb 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -47,6 +47,7 @@ def process_stream_events(): 'inline': None, 'nbagg': None, 'notebook': None, + 'ipympl': None, None : None, } From 38772d50f10114089bf94d46b765483c148d735f Mon Sep 17 00:00:00 2001 From: Jonas Hauquier Date: Sun, 28 Aug 2016 15:30:55 +0200 Subject: [PATCH 0051/1195] Bugfix for AttributeError: 'Logger' object has no attribute 'DEBUG' in comm manager --- ipykernel/comm/manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 15e3b80a1..c5ac1deb7 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -4,6 +4,7 @@ # Distributed under the terms of the Modified BSD License. import sys +import logging from traitlets.config import LoggingConfigurable @@ -66,7 +67,7 @@ def get_comm(self, comm_id): return self.comms[comm_id] except KeyError: self.log.warn("No such comm: %s", comm_id) - if self.log.isEnabledFor(self.log.DEBUG): + if self.log.isEnabledFor(logging.DEBUG): # don't create the list of keys if debug messages aren't enabled self.log.debug("Current comms: %s", list(self.comms.keys())) From e7c293cefb1d78407aa237229f05a9c858aca1d3 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 31 Aug 2016 12:21:38 +0200 Subject: [PATCH 0052/1195] cast prompt to str in raw_input builtin raw_input does this --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e5c831e7f..eb24b7448 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -684,7 +684,7 @@ def raw_input(self, prompt=''): raise StdinNotImplementedError( "raw_input was called, but this frontend does not support input requests." ) - return self._input_request(prompt, + return self._input_request(str(prompt), self._parent_ident, self._parent_header, password=False, From aaecd1341bb28a4a58df86ee27acf5340247e83c Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 1 Sep 2016 13:49:21 +0200 Subject: [PATCH 0053/1195] fix and test thread local hook registry only the main thread initialized hooks correctly, so other threads would see AttributeError('hooks') on other threads. --- ipykernel/tests/test_zmq_shell.py | 23 +++++++++++++++++++++-- ipykernel/zmqshell.py | 25 ++++++++++++++----------- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index 6743db696..8426fa222 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -5,7 +5,14 @@ # Distributed under the terms of the Modified BSD License. import os +try: + from queue import Queue +except ImportError: + # py2 + from Queue import Queue +from threading import Thread import unittest + from traitlets import Int import zmq @@ -88,12 +95,24 @@ def test_display_publisher_creation(self): self.assertEqual(self.disp_pub.session, self.session) self.assertEqual(self.disp_pub.pub_socket, self.socket) - def test_thread_local_default(self): + def test_thread_local_hooks(self): """ Confirms that the thread_local attribute is correctly initialised with an empty list for the display hooks """ - self.assertEqual(self.disp_pub.thread_local.hooks, []) + self.assertEqual(self.disp_pub._hooks, []) + def hook(msg): + return msg + self.disp_pub.register_hook(hook) + self.assertEqual(self.disp_pub._hooks, [hook]) + + q = Queue() + def set_thread_hooks(): + q.put(self.disp_pub._hooks) + t = Thread(target=set_thread_hooks) + t.start() + thread_hooks = q.get(timeout=10) + self.assertEqual(thread_hooks, []) def test_publish(self): """ diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index b209ee6f6..cb17c70bb 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -68,7 +68,7 @@ class ZMQDisplayPublisher(DisplayPublisher): # thread_local: # An attribute used to ensure the correct output message # is processed. See ipykernel Issue 113 for a discussion. - thread_local = Any() + _thread_local = Any() def set_parent(self, parent): """Set the parent for outbound messages.""" @@ -79,14 +79,17 @@ def _flush_streams(self): sys.stdout.flush() sys.stderr.flush() - @default('thread_local') + @default('_thread_local') def _default_thread_local(self): - """ Initialises the threadlocal attribute and - gives it a 'hooks' attribute. - """ - loc = local() - loc.hooks = [] - return loc + """Initialize our thread local storage""" + return local() + + @property + def _hooks(self): + if not hasattr(self._thread_local, 'hooks'): + # create new list for a new thread + self._thread_local.hooks = [] + return self._thread_local.hooks def publish(self, data, metadata=None, source=None): self._flush_streams() @@ -108,7 +111,7 @@ def publish(self, data, metadata=None, source=None): # Each transform either returns a new # message or None. If None is returned, # the message has been 'used' and we return. - for hook in self.thread_local.hooks: + for hook in self._hooks: msg = hook(msg) if msg is None: return @@ -143,7 +146,7 @@ def register_hook(self, hook): Returning `None` will halt that execution path, and session.send will not be called. """ - self.thread_local.hooks.append(hook) + self._hooks.append(hook) def unregister_hook(self, hook): """ @@ -160,7 +163,7 @@ def unregister_hook(self, hook): found. """ try: - self.thread_local.hooks.remove(hook) + self._hooks.remove(hook) return True except ValueError: return False From 716cb7f839e3bb92425c5eaec01a1f7ced8c1ba1 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 2 Sep 2016 15:24:21 +0200 Subject: [PATCH 0054/1195] Changelog for 4.5 --- docs/changelog.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 0835d0a75..e7bb08f72 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,20 @@ Changes in IPython kernel ========================= +4.5 +--- + +4.5.0 +***** + +`4.5 on GitHub `__ + +- Use figure.dpi instead of savefig.dpi to set DPI for inline figures +- Support ipympl matplotlib backend (requires IPython update as well to fully work) +- Various bugfixes, including fixes for output coming from threads, + and :func:`input` when called with non-string prompts, which stdlib allows. + + 4.4 --- From 1f9d062c11e9f88134e84f8250b6612920834e73 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 2 Sep 2016 16:23:22 +0200 Subject: [PATCH 0055/1195] Release 4.5.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index d762c777b..00a46c9ba 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 5, 0, 'dev') +version_info = (4, 5, 0) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 0) From 0a6898a153c66e2c2ade8cf3e0f199c102fbf33e Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 2 Sep 2016 16:28:52 +0200 Subject: [PATCH 0056/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 00a46c9ba..e5b9a801d 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 5, 0) +version_info = (4, 6, 0, 'dev') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 0) From 0bb226679679da6ef7b8b90a6f44cc0389fbbbd5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 13 Sep 2016 11:50:44 +0200 Subject: [PATCH 0057/1195] KeyboardInterrupt is not abort abort is a separate action, indicating that the request was not processed --- ipykernel/ipkernel.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 8d726a4b6..d28302ca2 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -204,8 +204,6 @@ def do_execute(self, code, silent, store_history=True, if res.success: reply_content[u'status'] = u'ok' - elif isinstance(err, KeyboardInterrupt): - reply_content[u'status'] = u'aborted' else: reply_content[u'status'] = u'error' From 76005cca7c6cf7fdad14c20994e26d67176606ca Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 17 Sep 2016 18:34:01 -0700 Subject: [PATCH 0058/1195] Test on nightly and allow failure --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index cb12c722f..27ea034b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,6 @@ language: python python: + - "nightly" - 3.5 - 3.4 - 3.3 @@ -19,3 +20,6 @@ script: - nosetests --with-coverage --with-timer --cover-package ipykernel ipykernel after_success: - codecov +matrix: + allow_failures: + - python: "nightly" From d9a13eda9c7c72e81d2232fb4bbab06e141a89b2 Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Thu, 6 Oct 2016 08:44:04 +0100 Subject: [PATCH 0059/1195] Convert readthedocs links for their .org -> .io migration for hosted projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per [their blog post of the 27th April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing subdomains’: > Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard. Test Plan: Manually visited all the links I’ve modified. --- docs/conf.py | 4 ++-- docs/index.rst | 2 +- ipykernel/zmqshell.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 83a317bbe..c126d822d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -298,6 +298,6 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { 'https://docs.python.org/': None, - 'ipython': ('https://ipython.readthedocs.org/en/latest', None), - 'jupyter': ('https://jupyter.readthedocs.org/en/latest', None), + 'ipython': ('https://ipython.readthedocs.io/en/latest', None), + 'jupyter': ('https://jupyter.readthedocs.io/en/latest', None), } diff --git a/docs/index.rst b/docs/index.rst index 3dab6ce73..770904d7e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ IPython Kernel Docs =================== This contains minimal version-sensitive documentation for the IPython kernel package. -Most IPython kernel documentation is in the `IPython documentation `_. +Most IPython kernel documentation is in the `IPython documentation `_. Contents: diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index cb17c70bb..9d59e9996 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -563,7 +563,7 @@ def init_virtualenv(self): # Overridden not to do virtualenv detection, because it's probably # not appropriate in a kernel. To use a kernel in a virtualenv, install # it inside the virtualenv. - # http://ipython.readthedocs.org/en/latest/install/kernel_install.html + # https://ipython.readthedocs.io/en/latest/install/kernel_install.html pass From e3366e992e0be4d863a721d11bf3348c96e27ef0 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 17 Oct 2016 11:51:53 -0700 Subject: [PATCH 0060/1195] Update deprecated alias on Python 3. --- ipykernel/tests/test_jsonutil.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 2e4901eb5..794ff6c96 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -5,7 +5,13 @@ # Distributed under the terms of the Modified BSD License. import json -from base64 import decodestring +import sys + +if sys.version_info < (3,): + from base64 import decodestring as decodebytes +else: + from base64 import decodebytes + from datetime import datetime import numbers @@ -73,7 +79,7 @@ def test_encode_images(): encoded = encode_images(fmt) for key, value in iteritems(fmt): # encoded has unicode, want bytes - decoded = decodestring(encoded[key].encode('ascii')) + decoded = decodebytes(encoded[key].encode('ascii')) nt.assert_equal(decoded, value) encoded2 = encode_images(encoded) nt.assert_equal(encoded, encoded2) @@ -85,7 +91,7 @@ def test_encode_images(): nt.assert_equal(encoded3, b64_str) for key, value in iteritems(fmt): # encoded3 has str, want bytes - decoded = decodestring(str_to_bytes(encoded3[key])) + decoded = decodebytes(str_to_bytes(encoded3[key])) nt.assert_equal(decoded, value) def test_lambda(): From cbebf2e55cef3750bac8ba3fd254982eaee581cd Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 21 Oct 2016 12:48:27 +0200 Subject: [PATCH 0061/1195] inprocess DummySockets have a zmq Context and don't show a warning when the register our own DummySocket with deprecated SocketABC --- ipykernel/inprocess/socket.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index 88e3751db..05a101949 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -31,7 +31,8 @@ def send_multipart(self, msg_parts, flags=0, copy=True, track=False): @classmethod def register(cls, other_cls): - warnings.warn("SocketABC is deprecated.", DeprecationWarning) + if other_cls is not DummySocket: + warnings.warn("SocketABC is deprecated.", DeprecationWarning) abc.ABCMeta.register(cls, other_cls) #----------------------------------------------------------------------------- @@ -43,6 +44,9 @@ class DummySocket(HasTraits): queue = Instance(Queue, ()) message_sent = Int(0) # Should be an Event + context = Instance(zmq.Context) + def _context_default(self): + return zmq.Context.instance() #------------------------------------------------------------------------- # Socket interface From 1115b8e164600c4a2fcd9ee1af48c313ae7a51e3 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 21 Oct 2016 12:56:33 +0200 Subject: [PATCH 0062/1195] remove locks from stdout use inproc push/pull to send events to the IOPub thread - moves all read/write on the buffer to the IOPub thread - removes all Locks - adds IOPubThread.schedule method for the call-this-in-the-thread action --- ipykernel/iostream.py | 136 +++++++++++++++++++++++++++++------------- 1 file changed, 95 insertions(+), 41 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 5c7f96e93..4f0c7c4bc 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -6,10 +6,10 @@ from __future__ import print_function import atexit +from binascii import b2a_hex import os import sys import threading -import uuid import warnings from io import StringIO, UnsupportedOperation @@ -35,14 +35,25 @@ class IOPubThread(object): """An object for sending IOPub messages in a background thread - - prevents a blocking main thread - + + Prevents a blocking main thread from delaying output from threads. + IOPubThread(pub_socket).background_socket is a Socket-API-providing object whose IO is always run in a thread. """ def __init__(self, socket, pipe=False): + """Create IOPub thread + + Parameters + ---------- + + socket: zmq.PUB Socket + the socket on which messages will be sent. + pipe: bool + Whether this process should listen for IOPub messages + piped from subprocesses. + """ self.socket = socket self.background_socket = BackgroundSocket(self) self._master_pid = os.getpid() @@ -50,6 +61,9 @@ def __init__(self, socket, pipe=False): self.io_loop = IOLoop() if pipe: self._setup_pipe_in() + self._local = threading.local() + self._events = {} + self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main) self.thread.daemon = True @@ -57,16 +71,51 @@ def _thread_main(self): """The inner loop that's actually run in a thread""" self.io_loop.start() self.io_loop.close() + if hasattr(self._local, 'event_pipe'): + self._local.event_pipe.close() + + def _setup_event_pipe(self): + """Create the PULL socket listening for events that should fire in this thread.""" + ctx = self.socket.context + pipe_in = ctx.socket(zmq.PULL) + pipe_in.linger = 0 + _uuid = b2a_hex(os.urandom(16)).decode('ascii') + iface = self._event_interface = 'inproc://%s' % _uuid + pipe_in.bind(iface) + self._event_puller = ZMQStream(pipe_in, self.io_loop) + self._event_puller.on_recv(self._handle_event) + + @property + def _event_pipe(self): + """thread-local event pipe for signaling events that should be processed in the thread""" + try: + event_pipe = self._local.event_pipe + except AttributeError: + # new thread, new event pipe + ctx = self.socket.context + event_pipe = ctx.socket(zmq.PUSH) + event_pipe.linger = 0 + event_pipe.connect(self._event_interface) + self._local.event_pipe = event_pipe + return event_pipe + + def _handle_event(self, msg): + """Handle an event on the event pipe""" + event_id = msg[0] + event_f = self._events.pop(event_id) + event_f() + def _setup_pipe_in(self): - """setup listening pipe for subprocesses""" + """setup listening pipe for IOPub from forked subprocesses""" ctx = self.socket.context # use UUID to authenticate pipe messages - self._pipe_uuid = uuid.uuid4().bytes + self._pipe_uuid = os.urandom(16) pipe_in = ctx.socket(zmq.PULL) pipe_in.linger = 0 + try: self._pipe_port = pipe_in.bind_to_random_port("tcp://127.0.0.1") except zmq.ZMQError as e: @@ -127,17 +176,27 @@ def close(self): @property def closed(self): return self.socket is None - + + def schedule(self, f): + """Schedule a function to be called in our IO thread. + + If the thread is not running, call immediately. + """ + if self.thread.is_alive(): + event_id = os.urandom(16) + while event_id in self._events: + event_id = os.urandom(16) + self._events[event_id] = f + self._event_pipe.send(event_id) + else: + f() + def send_multipart(self, *args, **kwargs): """send_multipart schedules actual zmq send in my thread. If my thread isn't running (e.g. forked process), send immediately. """ - - if self.thread.is_alive(): - self.io_loop.add_callback(lambda : self._really_send(*args, **kwargs)) - else: - self._really_send(*args, **kwargs) + self.schedule(lambda : self._really_send(*args, **kwargs)) def _really_send(self, msg, *args, **kwargs): """The callback that actually sends messages""" @@ -219,10 +278,8 @@ def __init__(self, session, pub_thread, name, pipe=None): self.topic = b'stream.' + py3compat.cast_bytes(name) self.parent_header = {} self._master_pid = os.getpid() - self._flush_lock = threading.Lock() - self._flush_timeout = None + self._flush_pending = False self._io_loop = pub_thread.io_loop - self._buffer_lock = threading.Lock() self._new_buffer() def _is_master_process(self): @@ -243,18 +300,14 @@ def _schedule_flush(self): call this on write, to indicate that flush should be called soon. """ - with self._flush_lock: - if self._flush_timeout is not None: - return - # None indicates there's no flush scheduled. - # Use a non-None placeholder to indicate that a flush is scheduled - # to avoid races while we wait for _schedule_in_thread below to fire in the io thread. - self._flush_timeout = 'placeholder' - - # add_timeout has to be handed to the io thread with add_callback + if self._flush_pending: + return + self._flush_pending = True + + # add_timeout has to be handed to the io thread via event pipe def _schedule_in_thread(): - self._flush_timeout = self._io_loop.call_later(self.flush_interval, self._flush) - self._io_loop.add_callback(_schedule_in_thread) + self._io_loop.call_later(self.flush_interval, self._flush) + self.pub_thread.schedule(_schedule_in_thread) def flush(self): """trigger actual zmq send @@ -262,10 +315,10 @@ def flush(self): send will happen in the background thread """ if self.pub_thread.thread.is_alive(): - self._io_loop.add_callback(self._flush) # wait for flush to actually get through: + self.pub_thread.schedule(self._flush) evt = threading.Event() - self._io_loop.add_callback(evt.set) + self.pub_thread.schedule(evt.set) evt.wait() else: self._flush() @@ -276,9 +329,8 @@ def _flush(self): _flush should generally be called in the IO thread, unless the thread has been destroyed (e.g. forked subprocess). """ - with self._flush_lock: - self._flush_timeout = None - data = self._flush_buffer() + self._flush_pending = False + data = self._flush_buffer() if data: # FIXME: this disables Session's fork-safe check, # since pub_thread is itself fork-safe. @@ -315,8 +367,8 @@ def write(self, string): string = string.decode(self.encoding, 'replace') is_child = (not self._is_master_process()) - with self._buffer_lock: - self._buffer.write(string) + # only touch the buffer in the IO thread to avoid races + self.pub_thread.schedule(lambda : self._buffer.write(string)) if is_child: # newlines imply flush in subprocesses # mp.Pool cannot be trusted to flush promptly (or ever), @@ -334,14 +386,16 @@ def writelines(self, sequence): self.write(string) def _flush_buffer(self): - """clear the current buffer and return the current buffer data""" - with self._buffer_lock: - data = u'' - if self._buffer is not None: - buf = self._buffer - self._new_buffer() - data = buf.getvalue() - buf.close() + """clear the current buffer and return the current buffer data. + + This should only be called in the IO thread. + """ + data = u'' + if self._buffer is not None: + buf = self._buffer + self._new_buffer() + data = buf.getvalue() + buf.close() return data def _new_buffer(self): From 3e21f96d4cfddc2f1796a16736b747ca9cceeb76 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 25 Oct 2016 09:36:51 -0400 Subject: [PATCH 0063/1195] Improve deprecation warning and set stacklevel --- ipykernel/inprocess/socket.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index 05a101949..37884891f 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -32,7 +32,8 @@ def send_multipart(self, msg_parts, flags=0, copy=True, track=False): @classmethod def register(cls, other_cls): if other_cls is not DummySocket: - warnings.warn("SocketABC is deprecated.", DeprecationWarning) + warnings.warn("SocketABC is deprecated since ipykernel version 4.5.0.", + DeprecationWarning, stacklevel=2) abc.ABCMeta.register(cls, other_cls) #----------------------------------------------------------------------------- From d445c2dba32397f4568ee34ffaeb45346e19f021 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 2 Nov 2016 15:49:39 -0700 Subject: [PATCH 0064/1195] Add a test failing if getpass does not acceptthe stream parameter --- ipykernel/inprocess/tests/test_kernel.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 8f15376f7..0231c8688 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -66,3 +66,11 @@ def test_stdout(self): kc.execute('print("bar")') out, err = assemble_output(kc.iopub_channel) self.assertEqual(out, 'bar\n') + + def test_getpass_stream(self): + "Tests that kernel getpass accept the stream parameter" + kernel = InProcessKernel() + kernel._allow_stdin = True + kernel._input_request = lambda *args, **kwargs : None + + kernel.getpass(stream='non empty') From 4be0a55aef59e8d6eed0e9c68b07b20edb51c26c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 2 Nov 2016 15:44:13 -0700 Subject: [PATCH 0065/1195] Allow ipykernel getpass to take a stream argument. The original getpass.getpass takes it so this restore API compatibility. --- ipykernel/kernelbase.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index eb24b7448..64e6737e4 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -656,7 +656,7 @@ def _no_raw_input(self): raise StdinNotImplementedError("raw_input was called, but this " "frontend does not support stdin.") - def getpass(self, prompt=''): + def getpass(self, prompt='', stream=None): """Forward getpass to frontends Raises @@ -667,6 +667,10 @@ def getpass(self, prompt=''): raise StdinNotImplementedError( "getpass was called, but this frontend does not support input requests." ) + if stream is not None: + import warnings + warnings.warn("The `stream` parameter of `getpass.getpass` will have no effect when using ipykernel", + UserWarning, stacklevel=2) return self._input_request(prompt, self._parent_ident, self._parent_header, From 85da11b3cf89f1bf538d547da8775083db812118 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 4 Nov 2016 14:29:58 -0700 Subject: [PATCH 0066/1195] Allow Comm.kernel to be None We already handle this case and raise a more informative exception in Comm.open() This is another thing causing failures from the recent traitlets change. --- ipykernel/comm/comm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 7656f9488..75cb58999 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -14,7 +14,7 @@ class Comm(LoggingConfigurable): """Class for communicating between a Frontend and a Kernel""" - kernel = Instance('ipykernel.kernelbase.Kernel') + kernel = Instance('ipykernel.kernelbase.Kernel', allow_none=True) @default('kernel') def _default_kernel(self): From f9957b5f86f4a372dec2f4c37666e33995e149dc Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 7 Nov 2016 21:44:31 -0800 Subject: [PATCH 0067/1195] support transient field, update_display_data --- ipykernel/zmqshell.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 9d59e9996..b4cc25e52 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -91,20 +91,42 @@ def _hooks(self): self._thread_local.hooks = [] return self._thread_local.hooks - def publish(self, data, metadata=None, source=None): + def publish(self, data, metadata=None, source=None, transient=None, + update=False, + ): + """Publish a display-data message + + Parameters + ---------- + data: dict + A mime-bundle dict, keyed by mime-type. + metadata: dict, optional + Metadata associated with the data. + transient: dict, optional, keyword-only + Transient data that may only be relevant during a live display, + such as display_id. + Transient data should not be persisted to documents. + update: bool, optional, keyword-only + If True, send an update_display_data message instead of display_data. + """ self._flush_streams() if metadata is None: metadata = {} + if transient is None: + transient = {} self._validate_data(data, metadata) content = {} content['data'] = encode_images(data) content['metadata'] = metadata + content['transient'] = transient + + msg_type = 'update_display_data' if update else 'display_data' # Use 2-stage process to send a message, # in order to put it through the transform # hooks before potentially sending. msg = self.session.msg( - u'display_data', json_clean(content), + msg_type, json_clean(content), parent=self.parent_header ) @@ -117,10 +139,20 @@ def publish(self, data, metadata=None, source=None): return self.session.send( - self.pub_socket, msg, ident=self.topic + self.pub_socket, msg, ident=self.topic, ) def clear_output(self, wait=False): + """Clear output associated with the current execution (cell). + + Parameters + ---------- + wait: bool (default: False) + If True, the output will not be cleared immediately, + instead waiting for the next display before clearing. + This reduces bounce during repeated clear & display loops. + + """ content = dict(wait=wait) self._flush_streams() self.session.send( From 9fba970da3c515de5d11659e166ddde678e09d8c Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Tue, 15 Nov 2016 13:58:35 +0100 Subject: [PATCH 0068/1195] Allow comms to be instanciated outside of a kernel --- ipykernel/comm/comm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 75cb58999..9ffc061ef 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -51,7 +51,7 @@ def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwa if target_name: kwargs['target_name'] = target_name super(Comm, self).__init__(**kwargs) - if self.primary: + if self.kernel is not None and self.primary: # I am primary, open my peer. self.open(data=data, metadata=metadata, buffers=buffers) else: From 6dfc10de6e83eb353f2854149918c6f48c815936 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 16 Nov 2016 12:53:10 +0100 Subject: [PATCH 0069/1195] Prepare to release 4.5.1 accumulated bugfixes --- docs/changelog.rst | 10 ++++++++++ ipykernel/_version.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e7bb08f72..a9acb1f28 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,16 @@ Changes in IPython kernel 4.5 --- +4.5.1 +***** + +`4.5.1 on GitHub `__ + +- Add missing ``stream`` parameter to overridden :func:`getpass` +- Remove locks from iopub thread, which could cause deadlocks during debugging +- Fix regression where KeyboardInterrupt was treated as an aborted request, rather than an error +- Allow instantating Comms outside of the IPython kernel + 4.5.0 ***** diff --git a/ipykernel/_version.py b/ipykernel/_version.py index e5b9a801d..22ed6555e 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 6, 0, 'dev') +version_info = (4, 5, 1) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 0) From 67b22bbccf3d80eee02526ab637c7afb2d34ef71 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 16 Nov 2016 13:55:39 +0100 Subject: [PATCH 0070/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 22ed6555e..e5b9a801d 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 5, 1) +version_info = (4, 6, 0, 'dev') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 0) From 3f7a03d356eee3500261acf9eec6bad2196c2097 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sun, 20 Nov 2016 14:36:03 +0000 Subject: [PATCH 0071/1195] Add a launcher module to ignore the CWD for imports while ipykernel starts After helping someone who'd shadowed a stdlib module with a file in the cwd (jupyter/help#97), I realised that we could fairly easily make the kernel able to start in such situations. This PR adds an ipykernel_launcher module which removes the CWD from sys.path and then launches the kernel. There is already code in IPython which adds the CWD back to sys.path before user code runs. This doesn't get rid of issues with name clashes: modules that we import will shadow modules of the same name in the cwd. So if you wrote math.py, you wouldn't be able to import it. But I think this is easier for users to understand than the kernel completely failing to start, especially if it's not easy for them to see the logs showing the error. --- ipykernel/kernelspec.py | 2 +- ipykernel_launcher.py | 16 ++++++++++++++++ setup.py | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 ipykernel_launcher.py diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index dcd57d4c1..bce8051e0 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -22,7 +22,7 @@ RESOURCES = pjoin(os.path.dirname(__file__), 'resources') -def make_ipkernel_cmd(mod='ipykernel', executable=None, extra_arguments=None, **kw): +def make_ipkernel_cmd(mod='ipykernel_launcher', executable=None, extra_arguments=None, **kw): """Build Popen command list for launching an IPython kernel. Parameters diff --git a/ipykernel_launcher.py b/ipykernel_launcher.py new file mode 100644 index 000000000..009d21d9e --- /dev/null +++ b/ipykernel_launcher.py @@ -0,0 +1,16 @@ +"""Entry point for launching an IPython kernel. + +This is separate from the ipykernel package so we can avoid doing imports until +after removing the cwd from sys.path. +""" + +import sys + +if __name__ == '__main__': + # Remove the CWD from sys.path while we load stuff. + # This is added back by InteractiveShellApp.init_path() + if sys.path[0] == '': + del sys.path[0] + + from ipykernel import kernelapp as app + app.launch_new_instance() diff --git a/setup.py b/setup.py index 034439f0c..1060f3188 100644 --- a/setup.py +++ b/setup.py @@ -55,6 +55,7 @@ version = version_ns['__version__'], scripts = glob(pjoin('scripts', '*')), packages = packages, + py_modules = ['ipykernel_launcher'], package_data = package_data, description = "IPython Kernel for Jupyter", author = 'IPython Development Team', From b8ac48b317190a976ceaf0d97e312f05b3f367bc Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sun, 20 Nov 2016 17:13:57 +0000 Subject: [PATCH 0072/1195] Update kernelspec test --- ipykernel/tests/test_kernelspec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index f1c0c4282..565510fb6 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -35,7 +35,7 @@ def test_make_ipkernel_cmd(): nt.assert_equal(cmd, [ sys.executable, '-m', - 'ipykernel', + 'ipykernel_launcher', '-f', '{connection_file}' ]) From 3090fcdbbb962670cf609456e230b7381123b2d5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 23 Nov 2016 13:29:09 +0100 Subject: [PATCH 0073/1195] don't mark comm as open if no kernel --- ipykernel/comm/comm.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 9ffc061ef..fb33b221e 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -51,11 +51,12 @@ def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwa if target_name: kwargs['target_name'] = target_name super(Comm, self).__init__(**kwargs) - if self.kernel is not None and self.primary: - # I am primary, open my peer. - self.open(data=data, metadata=metadata, buffers=buffers) - else: - self._closed = False + if self.kernel: + if self.primary: + # I am primary, open my peer. + self.open(data=data, metadata=metadata, buffers=buffers) + else: + self._closed = False def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): """Helper for sending a comm message on IOPub""" From fa79b2d48ef044a2731d7a2aa5757764caa988b7 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 23 Nov 2016 13:32:07 +0100 Subject: [PATCH 0074/1195] don't send close message when kernel is None can happen during interpreter cleanup --- ipykernel/comm/comm.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index fb33b221e..172c2b322 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -104,6 +104,10 @@ def close(self, data=None, metadata=None, buffers=None): # only close once return self._closed = True + # nothing to send if we have no kernel + # can be None during interpreter cleanup + if not self.kernel: + return if data is None: data = self._close_data self._publish_msg('comm_close', From 73d5d4d8cf743153a67bf64d809db751beb1e7bf Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 23 Nov 2016 16:51:37 +0100 Subject: [PATCH 0075/1195] changelog for 4.5.2 Just for the Comms-outside-kernels fix --- docs/changelog.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index a9acb1f28..6af56b8ce 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,14 @@ Changes in IPython kernel 4.5 --- +4.5.2 +***** + +`4.5.2 on GitHub `__ + +- Fix bug when instantating Comms outside of the IPython kernel (introduced in 4.5.1). + + 4.5.1 ***** From 8b309570edbb12028bd7218fcf6e0c2307134117 Mon Sep 17 00:00:00 2001 From: Srinivas Reddy Thatiparthy Date: Wed, 18 Jan 2017 10:54:22 +0530 Subject: [PATCH 0076/1195] Add 3.6 target as nightly points to 3.7.0a --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 27ea034b6..5df5e3b5a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: - "nightly" + - 3.6 - 3.5 - 3.4 - 3.3 From de98e309737d287f1f215a582f2d526f6d5a71cd Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 19 Jan 2017 20:43:39 -1000 Subject: [PATCH 0077/1195] make sure IOPubThreads are cleaned up in inprocess tests --- .../inprocess/tests/test_kernelmanager.py | 21 ++++++++++++------- ipykernel/iostream.py | 6 +++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/ipykernel/inprocess/tests/test_kernelmanager.py b/ipykernel/inprocess/tests/test_kernelmanager.py index 420eb4f36..f3e44364b 100644 --- a/ipykernel/inprocess/tests/test_kernelmanager.py +++ b/ipykernel/inprocess/tests/test_kernelmanager.py @@ -14,10 +14,17 @@ class InProcessKernelManagerTestCase(unittest.TestCase): + def setUp(self): + self.km = InProcessKernelManager() + + def tearDown(self): + if self.km.has_kernel: + self.km.shutdown_kernel() + def test_interface(self): """ Does the in-process kernel manager implement the basic KM interface? """ - km = InProcessKernelManager() + km = self.km self.assert_(not km.has_kernel) km.start_kernel() @@ -47,7 +54,7 @@ def test_interface(self): def test_execute(self): """ Does executing code in an in-process kernel work? """ - km = InProcessKernelManager() + km = self.km km.start_kernel() kc = km.client() kc.start_channels() @@ -58,7 +65,7 @@ def test_execute(self): def test_complete(self): """ Does requesting completion from an in-process kernel work? """ - km = InProcessKernelManager() + km = self.km km.start_kernel() kc = km.client() kc.start_channels() @@ -73,7 +80,7 @@ def test_complete(self): def test_inspect(self): """ Does requesting object information from an in-process kernel work? """ - km = InProcessKernelManager() + km = self.km km.start_kernel() kc = km.client() kc.start_channels() @@ -90,18 +97,18 @@ def test_inspect(self): def test_history(self): """ Does requesting history from an in-process kernel work? """ - km = InProcessKernelManager() + km = self.km km.start_kernel() kc = km.client() kc.start_channels() kc.wait_for_ready() - kc.execute('%who') + kc.execute('1') kc.history(hist_access_type='tail', n=1) msg = kc.shell_channel.get_msgs()[-1] self.assertEquals(msg['header']['msg_type'], 'history_reply') history = msg['content']['history'] self.assertEquals(len(history), 1) - self.assertEquals(history[0][2], '%who') + self.assertEquals(history[0][2], '1') if __name__ == '__main__': diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 4f0c7c4bc..b8f78473e 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -70,9 +70,7 @@ def __init__(self, socket, pipe=False): def _thread_main(self): """The inner loop that's actually run in a thread""" self.io_loop.start() - self.io_loop.close() - if hasattr(self._local, 'event_pipe'): - self._local.event_pipe.close() + self.io_loop.close(all_fds=True) def _setup_event_pipe(self): """Create the PULL socket listening for events that should fire in this thread.""" @@ -168,6 +166,8 @@ def stop(self): return self.io_loop.add_callback(self.io_loop.stop) self.thread.join() + if hasattr(self._local, 'event_pipe'): + self._local.event_pipe.close() def close(self): self.socket.close() From ec7320e2a179d718ae18326b2b320aff4ff68c06 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 1 Feb 2017 15:10:58 +0100 Subject: [PATCH 0078/1195] install kernelspec in wheels - wheel kernelspec relies on PATH env to find python (this should be okay when installed to sys.prefix) - can't use universal wheels anymore, since Python version is in kernelspec name and display name --- .gitignore | 2 ++ MANIFEST.in | 2 ++ setup.cfg | 2 +- setup.py | 17 ++++++++++++++++- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 1712ed87f..e9e657075 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ __pycache__ \#*# .#* .coverage + +data_kernelspec diff --git a/MANIFEST.in b/MANIFEST.in index 42edd273d..ce535d5dd 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -20,3 +20,5 @@ global-exclude *.pyc global-exclude *.pyo global-exclude .git global-exclude .ipynb_checkpoints + +prune data_kernelspec diff --git a/setup.cfg b/setup.cfg index 0c80d4cc5..60af4adf1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bdist_wheel] -universal=1 +universal=0 [nosetests] warningfilters= default |.* |DeprecationWarning |ipykernel.* diff --git a/setup.py b/setup.py index 1060f3188..7f3153080 100644 --- a/setup.py +++ b/setup.py @@ -27,8 +27,9 @@ # get on with it #----------------------------------------------------------------------------- -import os from glob import glob +import os +import shutil from distutils.core import setup @@ -85,6 +86,20 @@ 'jupyter_client', 'tornado>=4.0', ] +# setup requres same packages +setuptools_args['setup_requires'] = setuptools_args['install_requires'] +if any(a.startswith(('bdist', 'build')) for a in sys.argv): + from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME + + argv = make_ipkernel_cmd(executable='python') + dest = os.path.join(here, 'data_kernelspec') + if os.path.exists(dest): + shutil.rmtree(dest) + write_kernel_spec(dest, overrides={'argv': argv}) + + setup_args['data_files'] = [ + (pjoin('share', 'jupyter', 'kernels', KERNEL_NAME), glob(pjoin(dest, '*'))), + ] extras_require = setuptools_args['extras_require'] = { 'test:python_version=="2.7"': ['mock', 'nose_warnings_filters'], From 71fb26c76bc71fcc51a5124f40f4b3e242b437c5 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 1 Feb 2017 16:47:15 +0000 Subject: [PATCH 0079/1195] Patch PYTHONPATH when running tests Fixes running tests with packages --user installed. Prompted by jupyter/notebook#2111 --- ipykernel/tests/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ipykernel/tests/__init__.py b/ipykernel/tests/__init__.py index bd839bab7..1a392137c 100644 --- a/ipykernel/tests/__init__.py +++ b/ipykernel/tests/__init__.py @@ -3,6 +3,7 @@ import os import shutil +import sys import tempfile try: @@ -24,7 +25,11 @@ def setup(): global tmp tmp = tempfile.mkdtemp() patchers[:] = [ - patch.dict(os.environ, {'HOME': tmp}), + patch.dict(os.environ, { + 'HOME': tmp, + # Let tests work with --user install when HOME is changed: + 'PYTHONPATH': os.pathsep.join(sys.path), + }), ] for p in patchers: p.start() From ba1082e3c542d4d707dde513c76dec0782f42390 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Thu, 2 Feb 2017 07:33:55 +0000 Subject: [PATCH 0080/1195] Run tests on appveyor (#2) --- .coveragerc | 24 +++++++++++++++++ appveyor.yml | 48 ++++++++++++++++++++++++++++++++++ ipykernel/tests/test_kernel.py | 20 +++++++------- requirements-latest.txt | 4 +++ test-requirements.txt | 8 ++++++ 5 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 .coveragerc create mode 100644 appveyor.yml create mode 100644 requirements-latest.txt create mode 100644 test-requirements.txt diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..abcdb9ca7 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,24 @@ +[run] +branch = True +source = ipykernel + +[report] +omit = *test* +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: + +ignore_errors = True \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 000000000..826044417 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,48 @@ +build: false +shallow_clone: false +skip_branch_with_pr: true +clone_depth: 1 + +environment: + + global: + NOSE_COVER_PACKAGE: "ipykernel" + + matrix: + - python: "C:/Python27-x64" + - python: "C:/Python27" + - python: "C:/Python34-x64" + - python: "C:/Python34" + - python: "C:/Python35-x64" + - python: "C:/Python35" + - python: "C:/Python36-x64" + - python: "C:/Python36" + - python: "C:/Python36-x64" + latest: true + +matrix: + allow_failures: + - latest: true + +cache: + - C:\Users\appveyor\AppData\Local\pip\Cache + +init: + - ps: $Env:path = $Env:python + ";" + $Env:python + "\scripts;" + $Env:path +install: + - ps: pip install --upgrade pip wheel + - ps: pip --version + + - ps: if ($Env:latest -eq 'true') {pip install -r requirements-latest.txt} + - ps: pip install --pre -e . + + - cmd: pip install -r test-requirements.txt + - ps: pip freeze + + - ps: python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' +test_script: + - cmd: coverage run -m nose.core -v --with-timer --timer-top-n 10 ipykernel + +on_success: + - ps: pip install codecov + - cmd: codecov diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index f0c0d0183..0c4a2b87b 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -6,7 +6,6 @@ import io import os.path -import pprint import sys import time @@ -17,8 +16,9 @@ from IPython.paths import locate_profile from ipython_genutils.tempdir import TemporaryDirectory -from .utils import (new_kernel, kernel, TIMEOUT, assemble_output, execute, - flush_channels, wait_for_idle) +from .utils import ( + new_kernel, kernel, TIMEOUT, assemble_output, execute, + flush_channels, wait_for_idle) def _check_master(kc, expected=True, stream="stdout"): @@ -63,7 +63,7 @@ def test_sys_path_profile_dir(): stdout, stderr = assemble_output(kc.iopub_channel) nt.assert_equal(stdout, "''\n") -@dec.knownfailureif(sys.platform == 'win32', "subprocess prints fail on Windows") +@dec.skipif(sys.platform == 'win32', "subprocess prints fail on Windows") def test_subprocess_print(): """printing from forked mp.Process""" with new_kernel() as kc: @@ -114,7 +114,7 @@ def test_subprocess_noprint(): _check_master(kc, expected=True, stream="stderr") -@dec.knownfailureif(sys.platform == 'win32', "subprocess prints fail on Windows") +@dec.skipif(sys.platform == 'win32', "subprocess prints fail on Windows") def test_subprocess_error(): """error in mp.Process doesn't crash""" with new_kernel() as kc: @@ -199,13 +199,15 @@ def test_save_history(): @dec.skip_without('faulthandler') def test_smoke_faulthandler(): with kernel() as kc: + # Note: faulthandler.register is not available on windows. code = u'\n'.join([ - 'import faulthandler, signal', + 'import sys', + 'import faulthandler', + 'import signal', 'faulthandler.enable()', - 'faulthandler.register(signal.SIGTERM)', - ]) + 'if not sys.platform.startswith("win32"):', + ' faulthandler.register(signal.SIGTERM)']) _, reply = execute(code, kc=kc) - print(_) nt.assert_equal(reply['status'], 'ok', reply.get('traceback', '')) diff --git a/requirements-latest.txt b/requirements-latest.txt new file mode 100644 index 000000000..1a95c6a91 --- /dev/null +++ b/requirements-latest.txt @@ -0,0 +1,4 @@ +git+https://github.com/ipython/ipython.git#egg=ipython +git+https://github.com/ipython/traitlets.git#egg=traitlets +git+https://github.com/tornadoweb/tornado.git#tornado +git+https://github.com/jupyter/jupyter_client.git#egg=jupyter_client diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 000000000..352bc1b29 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,8 @@ +nose +nose-timer +coverage +mock +nose_warnings_filters +numpy +matplotlib +faulthandler ; python_version < "3" From b89f1420c0c9adf2b253c597f30267e56788811c Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 2 Feb 2017 10:50:50 +0100 Subject: [PATCH 0081/1195] rely on pip build order instead of setup_requires --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7f3153080..a1d7397ee 100644 --- a/setup.py +++ b/setup.py @@ -86,8 +86,7 @@ 'jupyter_client', 'tornado>=4.0', ] -# setup requres same packages -setuptools_args['setup_requires'] = setuptools_args['install_requires'] + if any(a.startswith(('bdist', 'build')) for a in sys.argv): from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME From d9a24589e63b96955b4d3ce33ae95428e2227047 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Thu, 2 Feb 2017 13:42:53 +0000 Subject: [PATCH 0082/1195] reduce the build matrix --- appveyor.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 826044417..7526149c2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,18 +11,8 @@ environment: matrix: - python: "C:/Python27-x64" - python: "C:/Python27" - - python: "C:/Python34-x64" - - python: "C:/Python34" - - python: "C:/Python35-x64" - - python: "C:/Python35" - python: "C:/Python36-x64" - python: "C:/Python36" - - python: "C:/Python36-x64" - latest: true - -matrix: - allow_failures: - - latest: true cache: - C:\Users\appveyor\AppData\Local\pip\Cache From d1ddccc14dcb0402527fd17915095bb95050d17a Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Thu, 2 Feb 2017 13:43:24 +0000 Subject: [PATCH 0083/1195] replicate the commands used in .travis --- appveyor.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 7526149c2..67cab289e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -23,10 +23,9 @@ install: - ps: pip install --upgrade pip wheel - ps: pip --version - - ps: if ($Env:latest -eq 'true') {pip install -r requirements-latest.txt} - - ps: pip install --pre -e . - - - cmd: pip install -r test-requirements.txt + - ps: pip install --pre -e . coverage nose_warnings_filters + - ps: pip install ipykernel[test] nose-timer + - ps: pip install matplolib numpy - ps: pip freeze - ps: python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' From 70b807c84baf73b74194451d7eb0d72f6a040599 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Thu, 2 Feb 2017 13:43:48 +0000 Subject: [PATCH 0084/1195] use the same test command as used in travis-ci --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 67cab289e..9ec6dba1c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,7 +30,7 @@ install: - ps: python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' test_script: - - cmd: coverage run -m nose.core -v --with-timer --timer-top-n 10 ipykernel + - cmd: nosetests --with-coverage --with-timer --cover-package=ipykernel ipykernel on_success: - ps: pip install codecov From 59ccced4f4c2e712ec9eb26b0974be50d4947849 Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Thu, 2 Feb 2017 13:44:03 +0000 Subject: [PATCH 0085/1195] remove unused files --- .coveragerc | 24 ------------------------ requirements-latest.txt | 4 ---- test-requirements.txt | 8 -------- 3 files changed, 36 deletions(-) delete mode 100644 .coveragerc delete mode 100644 requirements-latest.txt delete mode 100644 test-requirements.txt diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index abcdb9ca7..000000000 --- a/.coveragerc +++ /dev/null @@ -1,24 +0,0 @@ -[run] -branch = True -source = ipykernel - -[report] -omit = *test* -# Regexes for lines to exclude from consideration -exclude_lines = - # Have to re-enable the standard pragma - pragma: no cover - - # Don't complain about missing debug-only code: - def __repr__ - if self\.debug - - # Don't complain if tests don't hit defensive assertion code: - raise AssertionError - raise NotImplementedError - - # Don't complain if non-runnable code isn't run: - if 0: - if __name__ == .__main__.: - -ignore_errors = True \ No newline at end of file diff --git a/requirements-latest.txt b/requirements-latest.txt deleted file mode 100644 index 1a95c6a91..000000000 --- a/requirements-latest.txt +++ /dev/null @@ -1,4 +0,0 @@ -git+https://github.com/ipython/ipython.git#egg=ipython -git+https://github.com/ipython/traitlets.git#egg=traitlets -git+https://github.com/tornadoweb/tornado.git#tornado -git+https://github.com/jupyter/jupyter_client.git#egg=jupyter_client diff --git a/test-requirements.txt b/test-requirements.txt deleted file mode 100644 index 352bc1b29..000000000 --- a/test-requirements.txt +++ /dev/null @@ -1,8 +0,0 @@ -nose -nose-timer -coverage -mock -nose_warnings_filters -numpy -matplotlib -faulthandler ; python_version < "3" From c9af107a843a19fccb149d9ea2df8b5b91fa926e Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Thu, 2 Feb 2017 13:44:29 +0000 Subject: [PATCH 0086/1195] do not sent coverage report (for now) --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 9ec6dba1c..d3c9ee530 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -34,4 +34,4 @@ test_script: on_success: - ps: pip install codecov - - cmd: codecov + #- cmd: codecov From 0aa98438cde5e899fc6e191a5e54d894cb786b1a Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Thu, 2 Feb 2017 13:46:19 +0000 Subject: [PATCH 0087/1195] fix typo --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index d3c9ee530..bdb083962 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,7 +25,7 @@ install: - ps: pip install --pre -e . coverage nose_warnings_filters - ps: pip install ipykernel[test] nose-timer - - ps: pip install matplolib numpy + - ps: pip install matplotlib numpy - ps: pip freeze - ps: python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' From 0bd91b8c505fe87f98605d2c266e08847580613b Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Thu, 2 Feb 2017 13:48:10 +0000 Subject: [PATCH 0088/1195] we probably do not need this variable --- appveyor.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index bdb083962..eaa223226 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,9 +5,6 @@ clone_depth: 1 environment: - global: - NOSE_COVER_PACKAGE: "ipykernel" - matrix: - python: "C:/Python27-x64" - python: "C:/Python27" From bd0432c8ec42ecae88523ebe4df6be059b796cdd Mon Sep 17 00:00:00 2001 From: Ioannis Tziakos Date: Thu, 2 Feb 2017 17:08:57 +0000 Subject: [PATCH 0089/1195] execute codecov --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index eaa223226..76f5f742d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,4 +31,4 @@ test_script: on_success: - ps: pip install codecov - #- cmd: codecov + - cmd: codecov From 9898b1a761cb43dd830f64d011728ef9c2c53bbc Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 7 Feb 2017 11:10:32 +0100 Subject: [PATCH 0090/1195] skip explicit kernelspec install on Travis rely on install from wheel --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5df5e3b5a..6b177a2c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,15 +10,15 @@ sudo: false before_install: - git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels install: - - pip install -f travis-wheels/wheelhouse --pre -e . codecov nose_warnings_filters + - pip install -f travis-wheels/wheelhouse --pre . - pip install -f travis-wheels/wheelhouse ipykernel[test] nose-timer - | if [[ "$TRAVIS_PYTHON_VERSION" != "3.3" ]]; then pip install matplotlib fi - - python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' script: - - nosetests --with-coverage --with-timer --cover-package ipykernel ipykernel + - jupyter kernelspec list + - nosetests --with-coverage --with-timer --cover-package ipykernel ipykernel after_success: - codecov matrix: From 9b6083cebf128f2ee6fe9d5dc214b3bb95b8f428 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 7 Feb 2017 11:13:27 +0100 Subject: [PATCH 0091/1195] install from wheel and skip travis-wheels, which is obsolete now that manylinux wheels are a thing --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6b177a2c7..c636c1a1e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,13 +7,13 @@ python: - 3.3 - 2.7 sudo: false -before_install: - - git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels install: - - pip install -f travis-wheels/wheelhouse --pre . - - pip install -f travis-wheels/wheelhouse ipykernel[test] nose-timer - | - if [[ "$TRAVIS_PYTHON_VERSION" != "3.3" ]]; then + pip install --upgrade setuptools pip + python setup.py bdist_wheel && pip install --pre dist/*.whl + pip install ipykernel[test] nose-timer + - | + if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" || "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then pip install matplotlib fi script: From f7a116cc7297800ed1c942e6bce620754a8ceee3 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 7 Feb 2017 11:14:46 +0100 Subject: [PATCH 0092/1195] add nose_warnings_filters and nose-timer to all test requirements rather than just Python 2.7 --- .travis.yml | 4 ++-- setup.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index c636c1a1e..75ca1faaf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,8 @@ sudo: false install: - | pip install --upgrade setuptools pip - python setup.py bdist_wheel && pip install --pre dist/*.whl - pip install ipykernel[test] nose-timer + python setup.py bdist_wheel + pip install --pre dist/*.whl ipykernel[test] - | if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" || "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then pip install matplotlib diff --git a/setup.py b/setup.py index a1d7397ee..e935879ca 100644 --- a/setup.py +++ b/setup.py @@ -101,7 +101,8 @@ ] extras_require = setuptools_args['extras_require'] = { - 'test:python_version=="2.7"': ['mock', 'nose_warnings_filters'], + 'test:python_version=="2.7"': ['mock'], + 'test': ['nose_warnings_filters', 'nose-timer'], } if 'setuptools' in sys.modules: From cb6732fabf04b69cde85383344728726687f536c Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 7 Feb 2017 11:49:08 +0100 Subject: [PATCH 0093/1195] pip install . rather than bdist_wheel still builds and installs with wheel --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 75ca1faaf..220c87c0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,7 @@ sudo: false install: - | pip install --upgrade setuptools pip - python setup.py bdist_wheel - pip install --pre dist/*.whl ipykernel[test] + pip install --pre . ipykernel[test] - | if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" || "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then pip install matplotlib From 241bc42e9f92879b4a9adb8eacc2de2abb980d73 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 7 Feb 2017 11:51:46 +0100 Subject: [PATCH 0094/1195] install test after package --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 220c87c0f..9b9be35e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,8 @@ sudo: false install: - | pip install --upgrade setuptools pip - pip install --pre . ipykernel[test] + pip install --pre . + pip install ipykernel[test] - | if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" || "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then pip install matplotlib From 52251cbaf5f88a3203ae117ab4a10005969a355c Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 7 Feb 2017 12:06:44 +0100 Subject: [PATCH 0095/1195] install codecov --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9b9be35e4..32869e485 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ install: - | pip install --upgrade setuptools pip pip install --pre . - pip install ipykernel[test] + pip install ipykernel[test] codecov - | if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" || "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then pip install matplotlib From af28fcd38eb9dd10075bea995b46913fbba9b253 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 7 Feb 2017 11:59:38 +0100 Subject: [PATCH 0096/1195] build kernelspec on install as well --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index e935879ca..998a3744b 100644 --- a/setup.py +++ b/setup.py @@ -87,7 +87,7 @@ 'tornado>=4.0', ] -if any(a.startswith(('bdist', 'build')) for a in sys.argv): +if any(a.startswith(('bdist', 'build', 'install')) for a in sys.argv): from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME argv = make_ipkernel_cmd(executable='python') From b99955d4abc7a0919c0e4e17455fbb70eecefff6 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 13 Feb 2017 16:57:35 +0100 Subject: [PATCH 0097/1195] publish busy/idle for aborted requests follow spec: all requests should get busy/idle messages on IOPub during processing of a request it's ambiguous whether aborted requests are 'processed', but any code that assumes requests get busy/idle could get tripped up by the absence of these messages, and the abort is rare, so making this case as ordinary as possible is probably best. --- ipykernel/kernelbase.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 64e6737e4..3710d2eef 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -643,8 +643,10 @@ def _abort_queue(self, stream): status = {'status' : 'aborted'} md = {'engine' : self.ident} md.update(status) + self._publish_status('busy', parent=msg) reply_msg = self.session.send(stream, reply_type, metadata=md, content=status, parent=msg, ident=idents) + self._publish_status('idle', parent=msg) self.log.debug("%s", reply_msg) # We need to wait a bit for requests to come in. This can probably # be set shorter for true asynchronous clients. From 9d67fbb597ba9f22e8dfe0e9003843d1a81e553e Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Fri, 24 Feb 2017 16:18:34 +0000 Subject: [PATCH 0098/1195] Make 'Unknown message' log less scary See gh-64 With optional parts of the spec, and future additions of message types, the kernel will sometimes get a message that it doesn't know how to handle. This is not generally an error, so we don't need the log message to be so shouty about it. --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 3710d2eef..127199ee4 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -220,7 +220,7 @@ def dispatch_shell(self, stream, msg): handler = self.shell_handlers.get(msg_type, None) if handler is None: - self.log.error("UNKNOWN MESSAGE TYPE: %r", msg_type) + self.log.warn("Unknown message type: %r", msg_type) else: self.log.debug("%s: %s", msg_type, msg) self.pre_handler_hook() From 78bd840db5deeece3f408d6fc2c8555d7d1f888a Mon Sep 17 00:00:00 2001 From: Martin Bergtholdt Date: Wed, 22 Mar 2017 17:41:11 +0100 Subject: [PATCH 0099/1195] fix enable IPython.lib.guisupport.is_event_loop_running_XXX() for ipython kernels --- ipykernel/inprocess/ipkernel.py | 4 +++- ipykernel/zmqshell.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index c35c7dfb1..d4990ed92 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -172,7 +172,9 @@ def enable_gui(self, gui=None): from ipykernel.eventloops import enable_gui if not gui: gui = self.kernel.gui - return enable_gui(gui, kernel=self.kernel) + enable_gui(gui, kernel=self.kernel) + self.active_eventloop = gui + def enable_matplotlib(self, gui=None): """Enable matplotlib integration for the kernel.""" diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index b4cc25e52..97329f828 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -476,11 +476,11 @@ def _update_exit_now(self, change): # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no # interactive input being read; we provide event loop support in ipkernel - @staticmethod - def enable_gui(gui): + def enable_gui(self, gui): from .eventloops import enable_gui as real_enable_gui try: real_enable_gui(gui) + self.active_eventloop = gui except ValueError as e: raise UsageError("%s" % e) From 162f65f2af6c1ae5d52c4e58d9b851d6b08ce013 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 27 Mar 2017 10:42:48 +0200 Subject: [PATCH 0100/1195] Disable parent poller if parent PID=1 Parent polling doesn't work for PID 1, the kernel will just exit immediately. --- ipykernel/kernelapp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index ae15f75e2..a050eb5e2 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -165,7 +165,8 @@ def init_poller(self): if sys.platform == 'win32': if self.interrupt or self.parent_handle: self.poller = ParentPollerWindows(self.interrupt, self.parent_handle) - elif self.parent_handle: + elif self.parent_handle and self.parent_handle != 1: + # PID 1 is special. Parent polling doesn't work if ppid == 1 to start with. self.poller = ParentPollerUnix() def _bind_socket(self, s, port): From 688cfe11e41acca8fbc744d2fc1a45185c7cea9f Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 27 Mar 2017 10:43:36 +0200 Subject: [PATCH 0101/1195] log parent poller-triggered exit event --- ipykernel/parentpoller.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 0dc9fdb9c..446f656df 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -15,6 +15,8 @@ from thread import interrupt_main # Py 2 from threading import Thread +from traitlets.log import get_logger + import warnings class ParentPollerUnix(Thread): @@ -32,6 +34,7 @@ def run(self): while True: try: if os.getppid() == 1: + get_logger().warning("Parent appears to have exited, shutting down.") os._exit(1) time.sleep(1.0) except OSError as e: @@ -103,6 +106,7 @@ def run(self): interrupt_main() elif handle == self.parent_handle: + get_logger().warning("Parent appears to have exited, shutting down.") os._exit(1) elif result < 0: # wait failed, just give up and stop polling. From 70d2ec0beddde6a119ee3017b30e216ce9664319 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 27 Mar 2017 11:31:31 +0200 Subject: [PATCH 0102/1195] inherit IOStream from TextIOBase ensures all expected APIs are present and raise appropriate errors for undefined methods, rather than AttributeError. --- ipykernel/iostream.py | 27 ++++-------------------- ipykernel/tests/test_io.py | 42 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 23 deletions(-) create mode 100644 ipykernel/tests/test_io.py diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index b8f78473e..4e99e9676 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -11,7 +11,7 @@ import sys import threading import warnings -from io import StringIO, UnsupportedOperation +from io import StringIO, UnsupportedOperation, TextIOBase import zmq from zmq.eventloop.ioloop import IOLoop @@ -249,7 +249,7 @@ def send_multipart(self, *args, **kwargs): return self.io_thread.send_multipart(*args, **kwargs) -class OutStream(object): +class OutStream(TextIOBase): """A file like object that publishes the stream to a 0MQ PUB socket. Output is handed off to an IO Thread @@ -257,15 +257,14 @@ class OutStream(object): # The time interval between automatic flushes, in seconds. flush_interval = 0.2 - topic=None + topic = None + encoding = 'UTF-8' def __init__(self, session, pub_thread, name, pipe=None): if pipe is not None: warnings.warn("pipe argument to OutStream is deprecated and ignored", DeprecationWarning) - self.encoding = 'UTF-8' # This is necessary for compatibility with Python built-in streams - self.errors = None self.session = session if not isinstance(pub_thread, IOPubThread): # Backward-compat: given socket, not thread. Wrap in a thread. @@ -339,24 +338,6 @@ def _flush(self): content = {u'name':self.name, u'text':data} self.session.send(self.pub_thread, u'stream', content=content, parent=self.parent_header, ident=self.topic) - - def isatty(self): - return False - - def __next__(self): - raise IOError('Read not supported on a write only stream.') - - if not py3compat.PY3: - next = __next__ - - def read(self, size=-1): - raise IOError('Read not supported on a write only stream.') - - def readline(self, size=-1): - raise IOError('Read not supported on a write only stream.') - - def fileno(self): - raise UnsupportedOperation("IOStream has no fileno.") def write(self, string): if self.pub_thread is None: diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py new file mode 100644 index 000000000..b6fa718f1 --- /dev/null +++ b/ipykernel/tests/test_io.py @@ -0,0 +1,42 @@ +"""Test IO capturing functionality""" + +import io + +import zmq + +from jupyter_client.session import Session +from ipykernel.iostream import IOPubThread, OutStream + +import nose.tools as nt + +def test_io_api(): + """Test that wrapped stdout has the same API as a normal TextIO object""" + session = Session() + ctx = zmq.Context() + pub = ctx.socket(zmq.PUB) + thread = IOPubThread(pub) + thread.start() + + stream = OutStream(session, thread, 'stdout') + + # cleanup unused zmq objects before we start testing + thread.stop() + thread.close() + ctx.term() + + assert stream.errors is None + assert not stream.isatty() + with nt.assert_raises(io.UnsupportedOperation): + stream.detach() + with nt.assert_raises(io.UnsupportedOperation): + next(stream) + with nt.assert_raises(io.UnsupportedOperation): + stream.read() + with nt.assert_raises(io.UnsupportedOperation): + stream.readline() + with nt.assert_raises(io.UnsupportedOperation): + stream.seek() + with nt.assert_raises(io.UnsupportedOperation): + stream.tell() + + \ No newline at end of file From 3697eb50b72a429cbb4d3011bf4dac8ae27c33d8 Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Tue, 28 Mar 2017 10:33:46 -0700 Subject: [PATCH 0103/1195] Add changelog 4.6.0 --- docs/changelog.rst | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 6af56b8ce..e7a24a8a2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,25 @@ Changes in IPython kernel ========================= +4.6 +--- + +4.6.0 +***** + +`4.6.0 on GitHub `__ + +- Add busy/idle messages on IOPub during processing of every kernel request +- Add active event loop setting to GUI, which enables the correct response + to IPython's `is_event_loop_running_xxx` +- Add Python 3.6 support +- Modify `OutStream` to inherit from `TextIOBase` instead of object to improve + API support and error reporting +- Fix IPython kernel death messages at start, such as "Kernel Restarting..." + and "Kernel appears to have died", when parent-poller handles PID 1 +- Various bugfixes + + 4.5 --- @@ -9,7 +28,7 @@ Changes in IPython kernel `4.5.2 on GitHub `__ -- Fix bug when instantating Comms outside of the IPython kernel (introduced in 4.5.1). +- Fix bug when instantiating Comms outside of the IPython kernel (introduced in 4.5.1). 4.5.1 @@ -20,7 +39,7 @@ Changes in IPython kernel - Add missing ``stream`` parameter to overridden :func:`getpass` - Remove locks from iopub thread, which could cause deadlocks during debugging - Fix regression where KeyboardInterrupt was treated as an aborted request, rather than an error -- Allow instantating Comms outside of the IPython kernel +- Allow instantiating Comms outside of the IPython kernel 4.5.0 ***** From ae000f16d972303dae3de931b8f278d1dd7cc77d Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Tue, 28 Mar 2017 11:22:49 -0700 Subject: [PATCH 0104/1195] Add ipykernel_launcher and transient to changelog @takluyver --- docs/changelog.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index e7a24a8a2..89306c2ea 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,18 @@ Changes in IPython kernel `4.6.0 on GitHub `__ +- Add to API `DisplayPublisher.publish` two new fully backward-compatible + keyword-args: + - `update: bool` + - `transient: dict` +- Add a new dict, `transient`, to message spec for `publish`. For a display + data message, `transient` contains data that shouldn't be persisted to files + or documents. Add a `display_id` to this `transient` dict by + `display(obj, display_id=...)` +- Add `ipykernel_launcher` module which removes the current working directory + from `sys.path` before launching the kernel. This helps to reduce the cases + where the kernel won't start because there's a `random.py` (or similar) + module in the current working directory. - Add busy/idle messages on IOPub during processing of every kernel request - Add active event loop setting to GUI, which enables the correct response to IPython's `is_event_loop_running_xxx` From 824f061b9a047fd0a7dc5487b1851796d90c4612 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 29 Mar 2017 13:08:26 +0200 Subject: [PATCH 0105/1195] clarify PID 1 comment --- ipykernel/kernelapp.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index a050eb5e2..59190c019 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -166,7 +166,9 @@ def init_poller(self): if self.interrupt or self.parent_handle: self.poller = ParentPollerWindows(self.interrupt, self.parent_handle) elif self.parent_handle and self.parent_handle != 1: - # PID 1 is special. Parent polling doesn't work if ppid == 1 to start with. + # PID 1 (init) is special and will never go away, + # only be reassigned. + # Parent polling doesn't work if ppid == 1 to start with. self.poller = ParentPollerUnix() def _bind_socket(self, s, port): From d2115c0f118628aac8b2917e89c865c772636ba5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 29 Mar 2017 13:04:21 +0200 Subject: [PATCH 0106/1195] run appveyor build in cmd, not ps ps seems to die when there are warning messages, which is odd currently triggered when installing tornado 4.5b1 --- appveyor.yml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 76f5f742d..b80cf8ee4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,20 +15,21 @@ cache: - C:\Users\appveyor\AppData\Local\pip\Cache init: - - ps: $Env:path = $Env:python + ";" + $Env:python + "\scripts;" + $Env:path + - cmd: set PATH=%python%;%python%\scripts;%PATH% install: - - ps: pip install --upgrade pip wheel - - ps: pip --version - - - ps: pip install --pre -e . coverage nose_warnings_filters - - ps: pip install ipykernel[test] nose-timer - - ps: pip install matplotlib numpy - - ps: pip freeze - - - ps: python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' + - cmd: | + pip install --upgrade pip wheel + pip --version + - cmd: | + pip install --pre -e . coverage nose_warnings_filters + pip install ipykernel[test] nose-timer + - cmd: | + pip install matplotlib numpy + pip freeze + - cmd: python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' test_script: - cmd: nosetests --with-coverage --with-timer --cover-package=ipykernel ipykernel on_success: - - ps: pip install codecov + - cmd: pip install codecov - cmd: codecov From 2dd50305ab9a87ef177caef312a77edb08da9a46 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 29 Mar 2017 13:35:29 +0200 Subject: [PATCH 0107/1195] "quotes..." --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index b80cf8ee4..939ea8f80 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -26,7 +26,7 @@ install: - cmd: | pip install matplotlib numpy pip freeze - - cmd: python -c 'import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)' + - cmd: python -c "import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)" test_script: - cmd: nosetests --with-coverage --with-timer --cover-package=ipykernel ipykernel From afc7abdf6eea05e2bc515d9ad8c2e5860334e4f1 Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Wed, 29 Mar 2017 05:53:16 -0700 Subject: [PATCH 0108/1195] Edit changelog per @minrk review --- docs/changelog.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 89306c2ea..91077200f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,24 +7,25 @@ Changes in IPython kernel 4.6.0 ***** -`4.6.0 on GitHub `__ +`4.6.0 on GitHub `__ - Add to API `DisplayPublisher.publish` two new fully backward-compatible keyword-args: - `update: bool` - `transient: dict` -- Add a new dict, `transient`, to message spec for `publish`. For a display - data message, `transient` contains data that shouldn't be persisted to files - or documents. Add a `display_id` to this `transient` dict by - `display(obj, display_id=...)` +- Support new `transient` key in `display_data` messages spec for `publish`. + For a display data message, `transient` contains data that shouldn't be + persisted to files or documents. Add a `display_id` to this `transient` + dict by `display(obj, display_id=...)` - Add `ipykernel_launcher` module which removes the current working directory from `sys.path` before launching the kernel. This helps to reduce the cases where the kernel won't start because there's a `random.py` (or similar) module in the current working directory. -- Add busy/idle messages on IOPub during processing of every kernel request +- Add busy/idle messages on IOPub during processing of aborted requests - Add active event loop setting to GUI, which enables the correct response to IPython's `is_event_loop_running_xxx` -- Add Python 3.6 support +- Include IPython kernelspec in wheels to reduce reliance on "native kernel + spec" in jupyter_client - Modify `OutStream` to inherit from `TextIOBase` instead of object to improve API support and error reporting - Fix IPython kernel death messages at start, such as "Kernel Restarting..." From 840425fbb2815a082d92f89e966bd68777e6e6a4 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 31 Mar 2017 15:39:30 +0200 Subject: [PATCH 0109/1195] add readthedocs.yml needed to ensure IPython is present for building wheel from source --- readthedocs.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 readthedocs.yml diff --git a/readthedocs.yml b/readthedocs.yml new file mode 100644 index 000000000..f8b3b417d --- /dev/null +++ b/readthedocs.yml @@ -0,0 +1,3 @@ +python: + version: 3.5 + pip_install: true From a50a423dcc59e38374b346333548df616169a515 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 31 Mar 2017 15:50:36 +0200 Subject: [PATCH 0110/1195] default_role = literal for markdown-style `literal` to do what people expect in rst --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index c126d822d..d44ecd46a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -89,7 +89,7 @@ # The reST default role (used for this markup: `text`) to use for all # documents. -#default_role = None +default_role = 'literal' # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True From 355f75e56d60ea7fed01d523759850999125bee7 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 31 Mar 2017 16:24:05 +0200 Subject: [PATCH 0111/1195] ipykernel currently implements protocol 5.1 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index e5b9a801d..a415b57fd 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,5 +1,5 @@ version_info = (4, 6, 0, 'dev') __version__ = '.'.join(map(str, version_info)) -kernel_protocol_version_info = (5, 0) +kernel_protocol_version_info = (5, 1) kernel_protocol_version = '%s.%s' % kernel_protocol_version_info From 729d86897634c6856ca75c8e851252a18ad44e7f Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 4 Apr 2017 09:36:25 +0200 Subject: [PATCH 0112/1195] release 4.6.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index a415b57fd..fbb14ee36 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 6, 0, 'dev') +version_info = (4, 6, 0) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 20426cdad71820642dc69b95c30565d155521509 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 4 Apr 2017 09:48:11 +0200 Subject: [PATCH 0113/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index fbb14ee36..2f78cd400 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 6, 0) +version_info = (4, 7, 0, 'dev') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 234493cb5da49162f24968a9605dc9eb0cc45573 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 6 Apr 2017 17:02:28 +0200 Subject: [PATCH 0114/1195] set timezone on `started` metadata when using jupyter_client 5.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit causes deprecation warnings with jupyter_client 5.0 due to the use of naïve datetimes This metadata is part of ipyparallel and deprecated in ipykernel. It should be removed in 5.0. --- ipykernel/ipkernel.py | 4 ++-- ipykernel/kernelbase.py | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index d28302ca2..c9627adcb 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -213,7 +213,7 @@ def do_execute(self, code, silent, store_history=True, u'evalue': safe_unicode(err), }) - # FIXME: deprecate piece for ipyparallel: + # FIXME: deprecated piece for ipyparallel (remove in 5.0): e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='execute') reply_content['engine_info'] = e_info @@ -351,7 +351,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): u'ename': unicode_type(type(e).__name__), u'evalue': safe_unicode(e), } - # FIXME: deprecate piece for ipyparallel: + # FIXME: deprecated piece for ipyparallel (remove in 5.0): e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply') reply_content['engine_info'] = e_info diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 127199ee4..90f33cfbb 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -11,6 +11,13 @@ import uuid from datetime import datetime +try: + # jupyter_client >= 5, use tz-aware now + from jupyter_client.session import utcnow as now +except ImportError: + # jupyter_client < 5, use local now() + now = datetime.now + from signal import signal, default_int_handler, SIGINT import zmq @@ -350,8 +357,10 @@ def init_metadata(self, parent): Run at the beginning of execution requests. """ + # FIXME: `started` is part of ipyparallel + # Remove for ipykernel 5.0 return { - 'started': datetime.now(), + 'started': now(), } def finish_metadata(self, parent, metadata, reply_content): From c8b5c82e5a47f7cf382c1e7ae3917978929c02ce Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 10 Apr 2017 17:20:51 +0200 Subject: [PATCH 0115/1195] Changelog for 4.6.1 --- docs/changelog.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 91077200f..b49d4dcd8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,15 @@ Changes in IPython kernel 4.6 --- +4.6.1 +***** + +`4.6.1 on GitHub `__ + +- Fix eventloop-integration bug preventing Qt windows/widgets from displaying with ipykernel 4.6.0 and IPython ≥ 5.2. +- Avoid deprecation warnings about naive datetimes when working with jupyter_client ≥ 5.0. + + 4.6.0 ***** From a3851d94c571bc6453e00e5a9f83c9aa0dc7116f Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 10 Apr 2017 17:13:11 +0200 Subject: [PATCH 0116/1195] Remove calls to `start_event_loop` from IPython.lib.guisupport These are no-ops in IPython >= 5.2, so the eventloops never start. Replace them with private functions that are guaranteed to actually run the eventloop. --- ipykernel/eventloops.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 6c81cfdcb..d5cc740ad 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -72,11 +72,23 @@ def decorator(func): return decorator +def _loop_qt(app): + """Inner-loop for running the Qt eventloop + + Pulled from guisupport.start_event_loop in IPython < 5.2, + since IPython 5.2 only checks `get_ipython().active_eventloop` is defined, + rather than if the eventloop is actually running. + """ + app._in_event_loop = True + app.exec_() + app._in_event_loop = False + + @register_integration('qt', 'qt4') def loop_qt4(kernel): """Start a kernel with PyQt4 event loop integration.""" - from IPython.lib.guisupport import get_app_qt4, start_event_loop_qt4 + from IPython.lib.guisupport import get_app_qt4 kernel.app = get_app_qt4([" "]) kernel.app.setQuitOnLastWindowClosed(False) @@ -84,7 +96,8 @@ def loop_qt4(kernel): for s in kernel.shell_streams: _notify_stream_qt(kernel, s) - start_event_loop_qt4(kernel.app) + _loop_qt(kernel.app) + @register_integration('qt5') def loop_qt5(kernel): @@ -93,12 +106,23 @@ def loop_qt5(kernel): return loop_qt4(kernel) +def _loop_wx(app): + """Inner-loop for running the Wx eventloop + + Pulled from guisupport.start_event_loop in IPython < 5.2, + since IPython 5.2 only checks `get_ipython().active_eventloop` is defined, + rather than if the eventloop is actually running. + """ + app._in_event_loop = True + app.MainLoop() + app._in_event_loop = False + + @register_integration('wx') def loop_wx(kernel): """Start a kernel with wx event loop support.""" import wx - from IPython.lib.guisupport import start_event_loop_wx if _use_appnope() and kernel._darwin_app_nap: # we don't hook up App Nap contexts for Wx, @@ -143,7 +167,7 @@ def OnInit(self): if not callable(signal.getsignal(signal.SIGINT)): signal.signal(signal.SIGINT, signal.default_int_handler) - start_event_loop_wx(kernel.app) + _loop_wx(kernel.app) @register_integration('tk') From ba41598b1ea032fd111c38299ab697d33a368670 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 11 Apr 2017 18:11:52 +0200 Subject: [PATCH 0117/1195] release 4.6.1 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 2f78cd400..fbcadc4db 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 7, 0, 'dev') +version_info = (4, 6, 1) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 629ac54cae9767310616d47d769665453619ac64 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 11 Apr 2017 18:36:41 +0200 Subject: [PATCH 0118/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index fbcadc4db..2f78cd400 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 6, 1) +version_info = (4, 7, 0, 'dev') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 5971401d9b7f0095b8aa1be6b2d6c41f1e719180 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Wed, 17 May 2017 01:49:09 -0400 Subject: [PATCH 0119/1195] Add asyncio loop integration. Based on https://github.com/ipython/ipykernel/issues/21#issue-82784960 by @peteut. --- ipykernel/eventloops.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index d5cc740ad..0f4098ba4 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -290,6 +290,23 @@ def doi(): sys.excepthook = real_excepthook +@register_integration('asyncio') +def loop_asyncio(kernel): + '''Start a kernel with asyncio event loop support.''' + import asyncio + loop = asyncio.get_event_loop() + + def kernel_handler(): + loop.call_soon(kernel.do_one_iteration) + loop.call_later(kernel._poll_interval, kernel_handler) + + loop.call_soon(kernel_handler) + try: + if not loop.is_running(): + loop.run_forever() + finally: + loop.run_until_complete(loop.shutdown_asyncgens()) + loop.close() def enable_gui(gui, kernel=None): """Enable integration with a given GUI""" From 398d87f82099f8f2ac5b85bd5d91f118509723e1 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 31 Jan 2017 08:46:29 -0800 Subject: [PATCH 0120/1195] Use the new IPython completer API. --- ipykernel/ipkernel.py | 49 ++++++++++++++++++++++++++++++++++++++++- ipykernel/kernelbase.py | 6 +---- setup.cfg | 9 ++------ 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index d28302ca2..bd0280b88 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -2,7 +2,6 @@ import getpass import sys -import traceback from IPython.core import release from ipython_genutils.py3compat import builtin_mod, PY3, unicode_type, safe_unicode @@ -14,6 +13,15 @@ from .zmqshell import ZMQInteractiveShell +try: + from IPython.core.completer import rectify_completions, provisionalcompleter + _use_experimental_60_completion = True +except ImportError: + _use_experimental_60_completion = False + +_EXPERIMENTAL_KEY_NAME = '_jupyter_types_experimental' + + class IPythonKernel(KernelBase): shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True) @@ -246,6 +254,9 @@ def do_execute(self, code, silent, store_history=True, return reply_content def do_complete(self, code, cursor_pos): + if _use_experimental_60_completion: + return self._experimental_do_complete(code, cursor_pos) + # FIXME: IPython completers currently assume single line, # but completion messages give multi-line context # For now, extract line from cell, based on cursor_pos: @@ -261,6 +272,42 @@ def do_complete(self, code, cursor_pos): 'metadata' : {}, 'status' : 'ok'} + def _experimental_do_complete(self, code, cursor_pos): + """ + Experimental completions from IPython, using Jedi. + """ + if cursor_pos is None: + cursor_pos = len(code) + with provisionalcompleter(): + raw_completions = self.shell.Completer.completions(code, cursor_pos) + completions = list(rectify_completions(code, raw_completions)) + + comps = [] + for comp in completions: + comps.append(dict( + start=comp.start, + end=comp.end, + text=comp.text, + type=comp.type, + )) + + if completions: + s = completions[0].start + e = completions[0].end + matches = [c.text for c in completions] + else: + s = cursor_pos + e = cursor_pos + matches = [] + + return {'matches': matches, + 'cursor_end': e, + 'cursor_start': s, + 'metadata': {_EXPERIMENTAL_KEY_NAME: comps}, + 'status': 'ok'} + + + def do_inspect(self, code, cursor_pos, detail_level=0): name = token_at_cursor(code, cursor_pos) info = self.shell.object_inspect(name) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 64e6737e4..0ac50df14 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -128,7 +128,6 @@ def _default_ident(self): def __init__(self, **kwargs): super(Kernel, self).__init__(**kwargs) - # Build dict of handlers for message types self.shell_handlers = {} for msg_type in self.msg_types: @@ -205,8 +204,6 @@ def dispatch_shell(self, stream, msg): self.set_parent(idents, msg) self._publish_status(u'busy') - header = msg['header'] - msg_id = header['msg_id'] msg_type = msg['header']['msg_type'] # Print some info about this message and leave a '--->' marker, so it's @@ -421,12 +418,11 @@ def complete_request(self, stream, ident, parent): content = parent['content'] code = content['code'] cursor_pos = content['cursor_pos'] - + matches = self.do_complete(code, cursor_pos) matches = json_clean(matches) completion_msg = self.session.send(stream, 'complete_reply', matches, parent, ident) - self.log.debug("%s", completion_msg) def do_complete(self, code, cursor_pos): """Override in subclasses to find completions. diff --git a/setup.cfg b/setup.cfg index 0c80d4cc5..8eb6c041b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,12 +2,7 @@ universal=1 [nosetests] -warningfilters= default |.* |DeprecationWarning |ipykernel.* - default |.* |DeprecationWarning |IPython.* - ignore |.*assert.* |DeprecationWarning |.* - ignore |.*observe.* |DeprecationWarning |IPython.* - ignore |.*default.* |DeprecationWarning |IPython.* - ignore |.*default.* |DeprecationWarning |jupyter_client.* - ignore |.*Metada.* |DeprecationWarning |IPython.* +warningfilters= default |.* |DeprecationWarning |ipykernel.* + error |.*invalid.* |DeprecationWarning |matplotlib.* From c42f359c66c1c277a691206fe75b5c5ef4d9f2c1 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Thu, 27 Jul 2017 15:00:02 -0700 Subject: [PATCH 0121/1195] Support image/gif mimetype --- ipykernel/jsonutil.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 3121e53cc..c51f2e012 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -45,6 +45,9 @@ JPEG = b'\xff\xd8' # front of JPEG base64-encoded JPEG64 = b'/9' +# constants for identifying gif data +GIF = b'GIF87a' +GIF89 = b'GIF89a' # front of PDF base64-encoded PDF64 = b'JVBER' @@ -83,6 +86,13 @@ def encode_images(format_dict): if not jpegdata.startswith(JPEG64): jpegdata = encodebytes(jpegdata) encoded['image/jpeg'] = jpegdata.decode('ascii') + + gifdata = format_dict.get('image/gif') + if isinstance(gifdata, bytes): + # make sure we don't double-encode + if not gifdata.startswith(GIF) or not gifdata.startswith(GIF89): + gifdata = encodebytes(gifdata) + encoded['image/gif'] = gifdata.decode('ascii') pdfdata = format_dict.get('application/pdf') if isinstance(pdfdata, bytes): From 7a73274503949afac12932641c3e0a914a01b74e Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Fri, 28 Jul 2017 15:46:09 -0700 Subject: [PATCH 0122/1195] Check against base64-encoded prefix vs. unencoded --- ipykernel/jsonutil.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index c51f2e012..a3d3839e9 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -46,8 +46,8 @@ # front of JPEG base64-encoded JPEG64 = b'/9' # constants for identifying gif data -GIF = b'GIF87a' -GIF89 = b'GIF89a' +GIF_64 = b'R0lGODdh' +GIF89_64 = b'R0lGODlh' # front of PDF base64-encoded PDF64 = b'JVBER' @@ -90,7 +90,7 @@ def encode_images(format_dict): gifdata = format_dict.get('image/gif') if isinstance(gifdata, bytes): # make sure we don't double-encode - if not gifdata.startswith(GIF) or not gifdata.startswith(GIF89): + if not gifdata.startswith((GIF_64, GIF89_64)): gifdata = encodebytes(gifdata) encoded['image/gif'] = gifdata.decode('ascii') From bf8d3907042395669f1ca1d192bc78ae88815f9d Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 2 Aug 2017 13:26:57 +0100 Subject: [PATCH 0123/1195] Update changelog for 4.7 release --- docs/changelog.rst | 12 ++++++++++++ docs/conf.py | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b49d4dcd8..fb35d04c3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,18 @@ Changes in IPython kernel ========================= +4.7 +--- + +4.7.0 +***** + +`4.7.0 on GitHub `__ + +- Add event loop integration for :mod:`asyncio`. +- Use the new IPython completer API. +- Add support for displaying GIF images (mimetype ``image/gif``). + 4.6 --- diff --git a/docs/conf.py b/docs/conf.py index d44ecd46a..4c0361199 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -297,7 +297,7 @@ # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'https://docs.python.org/': None, + 'python': ('https://docs.python.org/3/', None), 'ipython': ('https://ipython.readthedocs.io/en/latest', None), 'jupyter': ('https://jupyter.readthedocs.io/en/latest', None), } From 5d2a618502e0c7e888f43a92d2ba2b217b7300ce Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 2 Aug 2017 10:40:05 -0700 Subject: [PATCH 0124/1195] Do not re-expose provisional_completer and rectify_completions --- ipykernel/ipkernel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index bce2ca7a1..b404170a1 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -14,7 +14,7 @@ try: - from IPython.core.completer import rectify_completions, provisionalcompleter + from IPython.core.completer import rectify_completions as _rectify_completions, provisionalcompleter as _provisionalcompleter _use_experimental_60_completion = True except ImportError: _use_experimental_60_completion = False @@ -278,9 +278,9 @@ def _experimental_do_complete(self, code, cursor_pos): """ if cursor_pos is None: cursor_pos = len(code) - with provisionalcompleter(): + with _provisionalcompleter(): raw_completions = self.shell.Completer.completions(code, cursor_pos) - completions = list(rectify_completions(code, raw_completions)) + completions = list(_rectify_completions(code, raw_completions)) comps = [] for comp in completions: From 80c1c39e5888d74e9ec1df0b57d57207fdfe1b10 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 2 Aug 2017 10:54:32 -0700 Subject: [PATCH 0125/1195] Add flag to deactivate experimental completions --- ipykernel/ipkernel.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index bce2ca7a1..235e9f76e 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -6,7 +6,7 @@ from IPython.core import release from ipython_genutils.py3compat import builtin_mod, PY3, unicode_type, safe_unicode from IPython.utils.tokenutil import token_at_cursor, line_at_cursor -from traitlets import Instance, Type, Any, List +from traitlets import Instance, Type, Any, List, Bool from .comm import CommManager from .kernelbase import Kernel as KernelBase @@ -27,6 +27,8 @@ class IPythonKernel(KernelBase): allow_none=True) shell_class = Type(ZMQInteractiveShell) + use_experimental_completions = Bool(True, help="Deactivate use of experimental IPython completion API").tag(config=True) + user_module = Any() def _user_module_changed(self, name, old, new): if self.shell is not None: @@ -254,7 +256,7 @@ def do_execute(self, code, silent, store_history=True, return reply_content def do_complete(self, code, cursor_pos): - if _use_experimental_60_completion: + if _use_experimental_60_completion and self.use_experimental_completions: return self._experimental_do_complete(code, cursor_pos) # FIXME: IPython completers currently assume single line, From 7209a74adeed0ba35e56b9b46f73cc0656771951 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 8 Aug 2017 18:29:19 +0200 Subject: [PATCH 0126/1195] always b64-encode bytes objects on Python 3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There’s ambiguity in Python 2 bytes objects as to whether they are bytes or text. So we have to guess. If it’s valid text, treat as text, otherwise binary. Upstream can always avoid ambiguity by only passing unicode. Binary data should be passed as base64-encoded unicode objects. --- ipykernel/jsonutil.py | 36 ++++++++++++++++++++++---------- ipykernel/tests/test_jsonutil.py | 31 +++++++++++++-------------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index a3d3839e9..65504aecd 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -3,18 +3,13 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from binascii import b2a_base64 import math import re import types from datetime import datetime import numbers -try: - # base64.encodestring is deprecated in Python 3.x - from base64 import encodebytes -except ImportError: - # Python 2.x - from base64 import encodestring as encodebytes from ipython_genutils import py3compat from ipython_genutils.py3compat import unicode_type, iteritems @@ -71,20 +66,27 @@ def encode_images(format_dict): is base64-encoded. """ + + # no need for handling of ambiguous bytestrings on Python 3, + # where bytes objects always represent binary data and thus + # base64-encoded. + if py3compat.PY3: + return format_dict + encoded = format_dict.copy() pngdata = format_dict.get('image/png') if isinstance(pngdata, bytes): # make sure we don't double-encode if not pngdata.startswith(PNG64): - pngdata = encodebytes(pngdata) + pngdata = b2a_base64(pngdata) encoded['image/png'] = pngdata.decode('ascii') jpegdata = format_dict.get('image/jpeg') if isinstance(jpegdata, bytes): # make sure we don't double-encode if not jpegdata.startswith(JPEG64): - jpegdata = encodebytes(jpegdata) + jpegdata = b2a_base64(jpegdata) encoded['image/jpeg'] = jpegdata.decode('ascii') gifdata = format_dict.get('image/gif') @@ -98,7 +100,7 @@ def encode_images(format_dict): if isinstance(pdfdata, bytes): # make sure we don't double-encode if not pdfdata.startswith(PDF64): - pdfdata = encodebytes(pdfdata) + pdfdata = b2a_base64(pdfdata) encoded['application/pdf'] = pdfdata.decode('ascii') return encoded @@ -151,9 +153,21 @@ def json_clean(obj): if isinstance(obj, atomic_ok): return obj - + if isinstance(obj, bytes): - return obj.decode(DEFAULT_ENCODING, 'replace') + if py3compat.PY3: + # unanmbiguous binary data is base64-encoded + # (this probably should have happened upstream) + return b2a_base64(obj).decode('ascii') + else: + # Python 2 bytestr is ambiguous, + # needs special handling for possible binary bytestrings. + # imperfect workaround: if valid text, assume text. + # otherwise assume binary, base64-encode (py3 behavior). + try: + return obj.decode(DEFAULT_ENCODING) + except ValueError: + return b2a_base64(obj).decode('ascii') if isinstance(obj, container_to_list) or ( hasattr(obj, '__iter__') and hasattr(obj, next_attr_name)): diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 794ff6c96..5a08135c5 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -4,13 +4,8 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from binascii import a2b_base64 import json -import sys - -if sys.version_info < (3,): - from base64 import decodestring as decodebytes -else: - from base64 import decodebytes from datetime import datetime import numbers @@ -19,7 +14,7 @@ from .. import jsonutil from ..jsonutil import json_clean, encode_images -from ipython_genutils.py3compat import unicode_to_str, str_to_bytes, iteritems +from ipython_genutils.py3compat import unicode_to_str class MyInt(object): def __int__(self): @@ -70,28 +65,30 @@ def test_encode_images(): pngdata = b'\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82' jpegdata = b'\xff\xd8\xff\xe0\x00\x10JFIFblahblahjpeg(\xa0\x0f\xff\xd9' pdfdata = b'%PDF-1.\ntrailer<>]>>>>>>' + bindata = b'\xff\xff\xff\xff' fmt = { 'image/png' : pngdata, 'image/jpeg' : jpegdata, - 'application/pdf' : pdfdata + 'application/pdf' : pdfdata, + 'application/unrecognized': bindata, } - encoded = encode_images(fmt) - for key, value in iteritems(fmt): + encoded = json_clean(encode_images(fmt)) + for key, value in fmt.items(): # encoded has unicode, want bytes - decoded = decodebytes(encoded[key].encode('ascii')) + decoded = a2b_base64(encoded[key]) nt.assert_equal(decoded, value) - encoded2 = encode_images(encoded) + encoded2 = json_clean(encode_images(encoded)) nt.assert_equal(encoded, encoded2) + # test that we don't double-encode base64 str b64_str = {} - for key, encoded in iteritems(encoded): + for key, encoded in encoded.items(): b64_str[key] = unicode_to_str(encoded) - encoded3 = encode_images(b64_str) + encoded3 = json_clean(encode_images(b64_str)) nt.assert_equal(encoded3, b64_str) - for key, value in iteritems(fmt): - # encoded3 has str, want bytes - decoded = decodebytes(str_to_bytes(encoded3[key])) + for key, value in fmt.items(): + decoded = a2b_base64(encoded3[key]) nt.assert_equal(decoded, value) def test_lambda(): From 73ff74c31618d55a731b1963732f2ad7a3218b3b Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 8 Aug 2017 20:41:14 +0200 Subject: [PATCH 0127/1195] only allow ascii bytestrings to be interpreted as text on Python 2 use unicode for text! maybe this will fix the weird error on Windows? --- ipykernel/jsonutil.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 65504aecd..64c2521ba 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -162,11 +162,11 @@ def json_clean(obj): else: # Python 2 bytestr is ambiguous, # needs special handling for possible binary bytestrings. - # imperfect workaround: if valid text, assume text. + # imperfect workaround: if ascii, assume text. # otherwise assume binary, base64-encode (py3 behavior). try: - return obj.decode(DEFAULT_ENCODING) - except ValueError: + return obj.decode('ascii') + except UnicodeDecodeError: return b2a_base64(obj).decode('ascii') if isinstance(obj, container_to_list) or ( From af9d4b897d970685ad57b0e7acb53f01387e3361 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 9 Aug 2017 16:31:19 +0200 Subject: [PATCH 0128/1195] call json_clean in displayhook --- ipykernel/displayhook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index 6e263abf2..969cbebe2 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -68,7 +68,7 @@ def write_output_prompt(self): self.msg['content']['execution_count'] = self.prompt_count def write_format_data(self, format_dict, md_dict=None): - self.msg['content']['data'] = encode_images(format_dict) + self.msg['content']['data'] = json_clean(encode_images(format_dict)) self.msg['content']['metadata'] = md_dict def finish_displayhook(self): From 29ef038c9c476a9b1f5bd0ee3417769e7c812627 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 9 Aug 2017 16:56:16 +0200 Subject: [PATCH 0129/1195] update helpstring for use_experimental_completions flag --- ipykernel/ipkernel.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 235e9f76e..1b28b65f6 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -27,7 +27,9 @@ class IPythonKernel(KernelBase): allow_none=True) shell_class = Type(ZMQInteractiveShell) - use_experimental_completions = Bool(True, help="Deactivate use of experimental IPython completion API").tag(config=True) + use_experimental_completions = Bool(True, + help="Set this flag to False to deactivate the use of experimental IPython completion APIs.", + ).tag(config=True) user_module = Any() def _user_module_changed(self, name, old, new): From 9f1c6903e3a53cbca9ce31ba55b7c854082dc6ee Mon Sep 17 00:00:00 2001 From: Min RK Date: Sun, 13 Aug 2017 12:52:16 +0200 Subject: [PATCH 0130/1195] fix a couple of undefined names caused by merges --- ipykernel/displayhook.py | 2 +- ipykernel/jsonutil.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index 969cbebe2..7e90ef100 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -6,7 +6,7 @@ import sys from IPython.core.displayhook import DisplayHook -from ipykernel.jsonutil import encode_images +from ipykernel.jsonutil import encode_images, json_clean from ipython_genutils.py3compat import builtin_mod from traitlets import Instance, Dict, Any from jupyter_client.session import extract_header, Session diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 64c2521ba..df669f7aa 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -93,7 +93,7 @@ def encode_images(format_dict): if isinstance(gifdata, bytes): # make sure we don't double-encode if not gifdata.startswith((GIF_64, GIF89_64)): - gifdata = encodebytes(gifdata) + gifdata = b2a_base64(gifdata) encoded['image/gif'] = gifdata.decode('ascii') pdfdata = format_dict.get('application/pdf') From 42afe03eff5be34737ed88b9b852161132628997 Mon Sep 17 00:00:00 2001 From: iktakahiro Date: Sat, 16 Sep 2017 16:26:06 +0900 Subject: [PATCH 0131/1195] Add activate_matplotlib() to _enable_matplotlib_integration() refs https://github.com/ipython/ipykernel/issues/247 --- ipykernel/pylab/backend_inline.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index 63b96935c..739165e39 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -150,12 +150,14 @@ def _enable_matplotlib_integration(): ip = get_ipython() backend = get_backend() if ip and backend == 'module://%s' % __name__: - from IPython.core.pylabtools import configure_inline_support + from IPython.core.pylabtools import configure_inline_support, activate_matplotlib try: + activate_matplotlib(backend) configure_inline_support(ip, backend) - except ImportError: + except (ImportError, AttributeError): # bugs may cause a circular import on Python 2 def configure_once(*args): + activate_matplotlib(backend) configure_inline_support(ip, backend) ip.events.unregister('post_run_cell', configure_once) ip.events.register('post_run_cell', configure_once) From 829b5e5e3ca21e72ed54f91fd6afa65a0a6ade6c Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 18 Sep 2017 10:05:28 +0100 Subject: [PATCH 0132/1195] Fix is_complete response with cell magics In console interfaces, cell magics end on two blank lines. Closes jupyter/jupyter_console#139 --- ipykernel/ipkernel.py | 2 +- ipykernel/tests/test_kernel.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 39496fc42..4abf88926 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -355,7 +355,7 @@ def do_shutdown(self, restart): return dict(status='ok', restart=restart) def do_is_complete(self, code): - status, indent_spaces = self.shell.input_transformer_manager.check_complete(code) + status, indent_spaces = self.shell.input_splitter.check_complete(code) r = {'status': status} if status == 'incomplete': r['indent'] = ' ' * indent_spaces diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 0c4a2b87b..7840b12a3 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -224,7 +224,7 @@ def test_is_complete(): reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) assert reply['content']['status'] == 'complete' - # SyntaxError should mean it's complete + # SyntaxError kc.is_complete('raise = 2') reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) assert reply['content']['status'] == 'invalid' @@ -234,6 +234,11 @@ def test_is_complete(): assert reply['content']['status'] == 'incomplete' assert reply['content']['indent'] == '' + # Cell magic ends on two blank lines for console UIs + kc.is_complete('%%timeit\na\n\n') + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + assert reply['content']['status'] == 'complete' + def test_complete(): with kernel() as kc: From 66bd2488ec483523e24be256debaf775d62ece10 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 10 Oct 2017 14:03:41 +0100 Subject: [PATCH 0133/1195] %qtconsole launches frontend in new session Closes jupyter/qtconsole#192 --- ipykernel/connect.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index e49ad10b2..a49932456 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -13,7 +13,7 @@ from IPython.core.profiledir import ProfileDir from IPython.paths import get_ipython_dir from ipython_genutils.path import filefind -from ipython_genutils.py3compat import str_to_bytes +from ipython_genutils.py3compat import str_to_bytes, PY3 import jupyter_client from jupyter_client import write_connection_file @@ -169,8 +169,15 @@ def connect_qtconsole(connection_file=None, argv=None, profile=None): "qtconsoleapp.main()" ]) + kwargs = {} + if PY3: + # Launch the Qt console in a separate session & process group, so + # interrupting the kernel doesn't kill it. This kwarg is not on Py2. + kwargs['start_new_session'] = True + return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'), + **kwargs, ) From ab97fb18e7b86d599c3627ce910d7ad8551da74f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 29 Oct 2017 05:38:07 -0500 Subject: [PATCH 0134/1195] Add widget loop function --- ipykernel/eventloops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 0f4098ba4..3a30d8958 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -48,6 +48,7 @@ def process_stream_events(): 'nbagg': None, 'notebook': None, 'ipympl': None, + 'widget': None, None : None, } From 40eddeeed2c04c1d973ce581c99302c86b449b25 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 30 Oct 2017 14:41:07 +0100 Subject: [PATCH 0135/1195] avoid deadlock on IOPub.flush during import The background thread cannot be expected to wake properly in the midst of import, so we cannot reasonably expect to wait for `evt.set` to be called. In this case, flush is still scheduled, but it is not awaited. --- ipykernel/iostream.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 4e99e9676..d0b239490 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -7,11 +7,15 @@ from __future__ import print_function import atexit from binascii import b2a_hex +try: + from importlib import lock_held as import_lock_held +except ImportError: + from imp import lock_held as import_lock_held import os import sys import threading import warnings -from io import StringIO, UnsupportedOperation, TextIOBase +from io import StringIO, TextIOBase import zmq from zmq.eventloop.ioloop import IOLoop @@ -42,6 +46,10 @@ class IOPubThread(object): whose IO is always run in a thread. """ + # timeout for flush to avoid infinite hang + # in case of misbehavior + FLUSH_TIMEOUT = 10 + def __init__(self, socket, pipe=False): """Create IOPub thread @@ -310,21 +318,29 @@ def _schedule_in_thread(): def flush(self): """trigger actual zmq send - + send will happen in the background thread """ if self.pub_thread.thread.is_alive(): - # wait for flush to actually get through: + # request flush on the background thread self.pub_thread.schedule(self._flush) - evt = threading.Event() - self.pub_thread.schedule(evt.set) - evt.wait() + # wait for flush to actually get through, if we can. + # waiting across threads during import can cause deadlocks + # so only wait if import lock is not held + if not import_lock_held(): + evt = threading.Event() + self.pub_thread.schedule(evt.set) + # and give a timeout to avoid + if not evt.wait(self.FLUSH_TIMEOUT): + # write directly to __stderr__ instead of warning because + # if this is happening sys.stderr may be the problem. + print("IOStream.flush timed out", file=sys.__stderr__) else: self._flush() - + def _flush(self): """This is where the actual send happens. - + _flush should generally be called in the IO thread, unless the thread has been destroyed (e.g. forked subprocess). """ From 0e413cc0571c3993bce5da62d658259f710eeff5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 6 Nov 2017 17:11:54 +0100 Subject: [PATCH 0136/1195] define flush_timeout on OutStream --- ipykernel/iostream.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index d0b239490..6d905041f 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -46,10 +46,6 @@ class IOPubThread(object): whose IO is always run in a thread. """ - # timeout for flush to avoid infinite hang - # in case of misbehavior - FLUSH_TIMEOUT = 10 - def __init__(self, socket, pipe=False): """Create IOPub thread @@ -263,6 +259,9 @@ class OutStream(TextIOBase): Output is handed off to an IO Thread """ + # timeout for flush to avoid infinite hang + # in case of misbehavior + flush_timeout = 10 # The time interval between automatic flushes, in seconds. flush_interval = 0.2 topic = None @@ -304,7 +303,7 @@ def closed(self): def _schedule_flush(self): """schedule a flush in the IO thread - + call this on write, to indicate that flush should be called soon. """ if self._flush_pending: @@ -331,7 +330,7 @@ def flush(self): evt = threading.Event() self.pub_thread.schedule(evt.set) # and give a timeout to avoid - if not evt.wait(self.FLUSH_TIMEOUT): + if not evt.wait(self.flush_timeout): # write directly to __stderr__ instead of warning because # if this is happening sys.stderr may be the problem. print("IOStream.flush timed out", file=sys.__stderr__) From f3c41bed40832f26e0585c33a1efe170db06bf5a Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 23 Nov 2017 15:47:28 +0100 Subject: [PATCH 0137/1195] handle KeyboardInterrupt with %gui asyncio when interrupted, re-enter eventloop and don't close it only close the eventloop when we are done for good. Also avoid any cleanup when someone else is already running the eventloop (e.g. tornado 5). --- ipykernel/eventloops.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 3a30d8958..fb1cc31fa 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -302,12 +302,22 @@ def kernel_handler(): loop.call_later(kernel._poll_interval, kernel_handler) loop.call_soon(kernel_handler) - try: - if not loop.is_running(): + # loop is already running (e.g. tornado 5), nothing left to do + if loop.is_running(): + return + while True: + error = None + try: loop.run_forever() - finally: + except KeyboardInterrupt: + continue + except Exception as e: + error = e loop.run_until_complete(loop.shutdown_asyncgens()) loop.close() + if error is not None: + raise error + break def enable_gui(gui, kernel=None): """Enable integration with a given GUI""" From 57ec48c4a4c1a433ef908a8411670c1748474cef Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 23 Nov 2017 16:26:59 +0100 Subject: [PATCH 0138/1195] show tracebacks in execute tests easier debugging when there are errors --- ipykernel/tests/utils.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 434ccec03..472a401b9 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -3,8 +3,11 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import print_function + import atexit import os +import sys from contextlib import contextmanager from subprocess import PIPE, STDOUT @@ -18,9 +21,6 @@ from jupyter_client import manager -#------------------------------------------------------------------------------- -# Globals -#------------------------------------------------------------------------------- STARTUP_TIMEOUT = 60 TIMEOUT = 15 @@ -28,9 +28,7 @@ KM = None KC = None -#------------------------------------------------------------------------------- -# code -#------------------------------------------------------------------------------- + def start_new_kernel(**kwargs): """start a new kernel, and return its Manager and Client @@ -43,6 +41,7 @@ def start_new_kernel(**kwargs): kwargs.update(dict(stdout=stdout, stderr=STDOUT)) return manager.start_new_kernel(startup_timeout=STARTUP_TIMEOUT, **kwargs) + def flush_channels(kc=None): """flush any messages waiting on the queue""" from .test_message_spec import validate_message @@ -76,6 +75,11 @@ def execute(code='', kc=None, **kwargs): validate_message(execute_input, 'execute_input', msg_id) nt.assert_equal(execute_input['content']['code'], code) + # show tracebacks if present for debugging + if reply['content'].get('traceback'): + print('\n'.join(reply['content']['traceback']), file=sys.stderr) + + return msg_id, reply['content'] def start_global_kernel(): From e595d52ce5366f7b821c62cd1e46929e8e73782b Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 23 Nov 2017 16:27:17 +0100 Subject: [PATCH 0139/1195] test interrupting asyncio eventloop --- ipykernel/tests/_asyncio.py | 17 ++++++++++++ ipykernel/tests/test_eventloop.py | 44 +++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 ipykernel/tests/_asyncio.py create mode 100644 ipykernel/tests/test_eventloop.py diff --git a/ipykernel/tests/_asyncio.py b/ipykernel/tests/_asyncio.py new file mode 100644 index 000000000..43e3229fa --- /dev/null +++ b/ipykernel/tests/_asyncio.py @@ -0,0 +1,17 @@ +"""test utilities that use async/await syntax + +a separate file to avoid syntax errors on Python 2 +""" + +import asyncio + + +def async_func(): + """Simple async function to schedule a task on the current eventloop""" + loop = asyncio.get_event_loop() + assert loop.is_running() + + async def task(): + await asyncio.sleep(1) + + loop.create_task(task()) diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py new file mode 100644 index 000000000..c487ee50b --- /dev/null +++ b/ipykernel/tests/test_eventloop.py @@ -0,0 +1,44 @@ +"""Test eventloop integration""" + +import sys +import time + +import IPython.testing.decorators as dec +from .utils import flush_channels, start_new_kernel, execute + +KC = KM = None + + +def setup(): + """start the global kernel (if it isn't running) and return its client""" + global KM, KC + KM, KC = start_new_kernel() + flush_channels(KC) + + +def teardown(): + KC.stop_channels() + KM.shutdown_kernel(now=True) + + +async_code = """ +from ipykernel.tests._asyncio import async_func +async_func() +""" + + +@dec.skipif(sys.version_info < (3, 5), "async/await syntax required") +def test_asyncio_interrupt(): + flush_channels(KC) + msg_id, content = execute('%gui asyncio', KC) + assert content['status'] == 'ok', content + + flush_channels(KC) + msg_id, content = execute(async_code, KC) + assert content['status'] == 'ok', content + + KM.interrupt_kernel() + + flush_channels(KC) + msg_id, content = execute(async_code, KC) + assert content['status'] == 'ok' From e872dbdbbe7609750cdc7583f209f1ce5e433205 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 23 Nov 2017 16:34:04 +0100 Subject: [PATCH 0140/1195] run tests with pytest instead of nose for better reporting on failure --- .travis.yml | 4 ++-- setup.py | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 32869e485..1bdbe107c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python python: - "nightly" - - 3.6 + - 3.6 - 3.5 - 3.4 - 3.3 @@ -18,7 +18,7 @@ install: fi script: - jupyter kernelspec list - - nosetests --with-coverage --with-timer --cover-package ipykernel ipykernel + - pytest --cov ipykernel -v ipykernel after_success: - codecov matrix: diff --git a/setup.py b/setup.py index 998a3744b..d75bf2c6c 100644 --- a/setup.py +++ b/setup.py @@ -102,7 +102,11 @@ extras_require = setuptools_args['extras_require'] = { 'test:python_version=="2.7"': ['mock'], - 'test': ['nose_warnings_filters', 'nose-timer'], + 'test': [ + 'pytest', + 'pytest-cov', + 'nose', # nose because there are still a few nose.tools imports hanging around + ], } if 'setuptools' in sys.modules: From 66b403e15c68823659dd9755256707f52859b659 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 23 Nov 2017 16:48:17 +0100 Subject: [PATCH 0141/1195] replace most (not all) nose.tools asserts with bare asserts, now that we're using pytest only those that were caught by simple find/replace there are more that can be replaced, but there's no great pressure to resolve this now --- ipykernel/inprocess/tests/test_kernel.py | 6 +- .../inprocess/tests/test_kernelmanager.py | 28 +++---- ipykernel/tests/test_connect.py | 18 ++-- ipykernel/tests/test_embed_kernel.py | 27 +++--- ipykernel/tests/test_jsonutil.py | 12 +-- ipykernel/tests/test_kernel.py | 56 ++++++------- ipykernel/tests/test_kernelspec.py | 18 ++-- ipykernel/tests/test_message_spec.py | 52 ++++++------ ipykernel/tests/test_pickleutil.py | 13 ++- ipykernel/tests/test_serialize.py | 83 +++++++++---------- ipykernel/tests/test_start_kernel.py | 12 ++- ipykernel/tests/test_zmq_shell.py | 43 +++++----- ipykernel/tests/utils.py | 5 +- 13 files changed, 175 insertions(+), 198 deletions(-) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 0231c8688..aa7cf6725 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -50,7 +50,7 @@ def test_raw_input(self): self.kc.execute('x = raw_input()') finally: sys.stdin = sys_stdin - self.assertEqual(self.km.kernel.shell.user_ns.get('x'), 'foobar') + assert self.km.kernel.shell.user_ns.get('x') == 'foobar' def test_stdout(self): """ Does the in-process kernel correctly capture IO? @@ -59,13 +59,13 @@ def test_stdout(self): with capture_output() as io: kernel.shell.run_cell('print("foo")') - self.assertEqual(io.stdout, 'foo\n') + assert io.stdout == 'foo\n' kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) kernel.frontends.append(kc) kc.execute('print("bar")') out, err = assemble_output(kc.iopub_channel) - self.assertEqual(out, 'bar\n') + assert out == 'bar\n' def test_getpass_stream(self): "Tests that kernel getpass accept the stream parameter" diff --git a/ipykernel/inprocess/tests/test_kernelmanager.py b/ipykernel/inprocess/tests/test_kernelmanager.py index f3e44364b..9de5994b3 100644 --- a/ipykernel/inprocess/tests/test_kernelmanager.py +++ b/ipykernel/inprocess/tests/test_kernelmanager.py @@ -25,31 +25,31 @@ def test_interface(self): """ Does the in-process kernel manager implement the basic KM interface? """ km = self.km - self.assert_(not km.has_kernel) + assert not km.has_kernel km.start_kernel() - self.assert_(km.has_kernel) - self.assert_(km.kernel is not None) + assert km.has_kernel + assert km.kernel is not None kc = km.client() - self.assert_(not kc.channels_running) + assert not kc.channels_running kc.start_channels() - self.assert_(kc.channels_running) + assert kc.channels_running old_kernel = km.kernel km.restart_kernel() self.assertIsNotNone(km.kernel) - self.assertNotEquals(km.kernel, old_kernel) + assert km.kernel != old_kernel km.shutdown_kernel() - self.assert_(not km.has_kernel) + assert not km.has_kernel self.assertRaises(NotImplementedError, km.interrupt_kernel) self.assertRaises(NotImplementedError, km.signal_kernel, 9) kc.stop_channels() - self.assert_(not kc.channels_running) + assert not kc.channels_running def test_execute(self): """ Does executing code in an in-process kernel work? @@ -60,7 +60,7 @@ def test_execute(self): kc.start_channels() kc.wait_for_ready() kc.execute('foo = 1') - self.assertEquals(km.kernel.shell.user_ns['foo'], 1) + assert km.kernel.shell.user_ns['foo'] == 1 def test_complete(self): """ Does requesting completion from an in-process kernel work? @@ -73,7 +73,7 @@ def test_complete(self): km.kernel.shell.push({'my_bar': 0, 'my_baz': 1}) kc.complete('my_ba', 5) msg = kc.get_shell_msg() - self.assertEqual(msg['header']['msg_type'], 'complete_reply') + assert msg['header']['msg_type'] == 'complete_reply' self.assertEqual(sorted(msg['content']['matches']), ['my_bar', 'my_baz']) @@ -88,7 +88,7 @@ def test_inspect(self): km.kernel.shell.user_ns['foo'] = 1 kc.inspect('foo') msg = kc.get_shell_msg() - self.assertEqual(msg['header']['msg_type'], 'inspect_reply') + assert msg['header']['msg_type'] == 'inspect_reply' content = msg['content'] assert content['found'] text = content['data']['text/plain'] @@ -105,10 +105,10 @@ def test_history(self): kc.execute('1') kc.history(hist_access_type='tail', n=1) msg = kc.shell_channel.get_msgs()[-1] - self.assertEquals(msg['header']['msg_type'], 'history_reply') + assert msg['header']['msg_type'] == 'history_reply' history = msg['content']['history'] - self.assertEquals(len(history), 1) - self.assertEquals(history[0][2], '1') + assert len(history) == 1 + assert history[0][2] == '1' if __name__ == '__main__': diff --git a/ipykernel/tests/test_connect.py b/ipykernel/tests/test_connect.py index e0b812cb0..739eb12bd 100644 --- a/ipykernel/tests/test_connect.py +++ b/ipykernel/tests/test_connect.py @@ -6,8 +6,6 @@ import json import os -import nose.tools as nt - from traitlets.config import Config from ipython_genutils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory from ipython_genutils.py3compat import str_to_bytes @@ -36,14 +34,14 @@ def test_get_connection_file(): app.initialize() profile_cf = os.path.join(app.connection_dir, cf) - nt.assert_equal(profile_cf, app.abs_connection_file) + assert profile_cf == app.abs_connection_file with open(profile_cf, 'w') as f: f.write("{}") - nt.assert_true(os.path.exists(profile_cf)) - nt.assert_equal(connect.get_connection_file(app), profile_cf) + assert os.path.exists(profile_cf) + assert connect.get_connection_file(app) == profile_cf app.connection_file = cf - nt.assert_equal(connect.get_connection_file(app), profile_cf) + assert connect.get_connection_file(app) == profile_cf def test_get_connection_info(): @@ -52,12 +50,12 @@ def test_get_connection_info(): connect.write_connection_file(cf, **sample_info) json_info = connect.get_connection_info(cf) info = connect.get_connection_info(cf, unpack=True) - - nt.assert_equal(type(json_info), type("")) + assert isinstance(json_info, str) + sub_info = {k:v for k,v in info.items() if k in sample_info} - nt.assert_equal(sub_info, sample_info) + assert sub_info == sample_info info2 = json.loads(json_info) info2['key'] = str_to_bytes(info2['key']) sub_info2 = {k:v for k,v in info.items() if k in sample_info} - nt.assert_equal(sub_info2, sample_info) + assert sub_info2 == sample_info diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 03de53df7..5e60245b0 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -4,19 +4,14 @@ # Distributed under the terms of the Modified BSD License. import os -import shutil import sys -import tempfile import time from contextlib import contextmanager from subprocess import Popen, PIPE -import nose.tools as nt - from jupyter_client import BlockingKernelClient from jupyter_core import paths -from IPython.paths import get_ipython_dir from ipython_genutils import py3compat from ipython_genutils.py3compat import unicode_type @@ -83,20 +78,20 @@ def test_embed_kernel_basic(): msg_id = client.inspect('a') msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - nt.assert_true(content['found']) + assert content['found'] msg_id = client.execute("c=a*2") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - nt.assert_equal(content['status'], u'ok') + assert content['status'] == u'ok' # oinfo c (should be 10) msg_id = client.inspect('c') msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - nt.assert_true(content['found']) + assert content['found'] text = content['data']['text/plain'] - nt.assert_in('10', text) + assert '10' in text def test_embed_kernel_namespace(): """IPython.embed_kernel() inherits calling namespace""" @@ -115,23 +110,23 @@ def test_embed_kernel_namespace(): msg_id = client.inspect('a') msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - nt.assert_true(content['found']) + assert content['found'] text = content['data']['text/plain'] - nt.assert_in(u'5', text) + assert u'5' in text # oinfo b (str) msg_id = client.inspect('b') msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - nt.assert_true(content['found']) + assert content['found'] text = content['data']['text/plain'] - nt.assert_in(u'hi there', text) + assert u'hi there' in text # oinfo c (undefined) msg_id = client.inspect('c') msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - nt.assert_false(content['found']) + assert not content['found'] def test_embed_kernel_reentrant(): """IPython.embed_kernel() can be called multiple times""" @@ -153,9 +148,9 @@ def test_embed_kernel_reentrant(): msg_id = client.inspect('count') msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - nt.assert_true(content['found']) + assert content['found'] text = content['data']['text/plain'] - nt.assert_in(unicode_type(i), text) + assert unicode_type(i) in text # exit from embed_kernel client.execute("get_ipython().exit_now = True") diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 5a08135c5..8db3cf35a 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -55,7 +55,7 @@ def test(): jval = val out = json_clean(val) # validate our cleanup - nt.assert_equal(out, jval) + assert out == jval # and ensure that what we return, indeed encodes cleanly json.loads(json.dumps(out)) @@ -77,19 +77,19 @@ def test_encode_images(): for key, value in fmt.items(): # encoded has unicode, want bytes decoded = a2b_base64(encoded[key]) - nt.assert_equal(decoded, value) + assert decoded == value encoded2 = json_clean(encode_images(encoded)) - nt.assert_equal(encoded, encoded2) + assert encoded == encoded2 # test that we don't double-encode base64 str b64_str = {} for key, encoded in encoded.items(): b64_str[key] = unicode_to_str(encoded) encoded3 = json_clean(encode_images(b64_str)) - nt.assert_equal(encoded3, b64_str) + assert encoded3 == b64_str for key, value in fmt.items(): decoded = a2b_base64(encoded3[key]) - nt.assert_equal(decoded, value) + assert decoded == value def test_lambda(): with nt.assert_raises(ValueError): @@ -107,4 +107,4 @@ def test_exception(): def test_unicode_dict(): data = {u'üniço∂e': u'üniço∂e'} clean = jsonutil.json_clean(data) - nt.assert_equal(data, clean) + assert data == clean diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 7840b12a3..fa3949131 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -26,13 +26,13 @@ def _check_master(kc, expected=True, stream="stdout"): flush_channels(kc) msg_id, content = execute(kc=kc, code="print (sys.%s._is_master_process())" % stream) stdout, stderr = assemble_output(kc.iopub_channel) - nt.assert_equal(stdout.strip(), repr(expected)) + assert stdout.strip() == repr(expected) def _check_status(content): """If status=error, show the traceback""" if content['status'] == 'error': - nt.assert_true(False, ''.join(['\n'] + content['traceback'])) + assert False, ''.join(['\n'] + content['traceback']) # printing tests @@ -43,8 +43,8 @@ def test_simple_print(): iopub = kc.iopub_channel msg_id, content = execute(kc=kc, code="print ('hi')") stdout, stderr = assemble_output(iopub) - nt.assert_equal(stdout, 'hi\n') - nt.assert_equal(stderr, '') + assert stdout == 'hi\n' + assert stderr == '' _check_master(kc, expected=True) @@ -53,7 +53,7 @@ def test_sys_path(): with kernel() as kc: msg_id, content = execute(kc=kc, code="import sys; print (repr(sys.path[0]))") stdout, stderr = assemble_output(kc.iopub_channel) - nt.assert_equal(stdout, "''\n") + assert stdout == "''\n" def test_sys_path_profile_dir(): """test that sys.path doesn't get messed up when `--profile-dir` is specified""" @@ -61,7 +61,7 @@ def test_sys_path_profile_dir(): with new_kernel(['--profile-dir', locate_profile('default')]) as kc: msg_id, content = execute(kc=kc, code="import sys; print (repr(sys.path[0]))") stdout, stderr = assemble_output(kc.iopub_channel) - nt.assert_equal(stdout, "''\n") + assert stdout == "''\n" @dec.skipif(sys.platform == 'win32', "subprocess prints fail on Windows") def test_subprocess_print(): @@ -87,7 +87,7 @@ def test_subprocess_print(): nt.assert_equal(stdout.count("hello"), np, stdout) for n in range(np): nt.assert_equal(stdout.count(str(n)), 1, stdout) - nt.assert_equal(stderr, '') + assert stderr == '' _check_master(kc, expected=True) _check_master(kc, expected=True, stream="stderr") @@ -107,8 +107,8 @@ def test_subprocess_noprint(): msg_id, content = execute(kc=kc, code=code) stdout, stderr = assemble_output(iopub) - nt.assert_equal(stdout, '') - nt.assert_equal(stderr, '') + assert stdout == '' + assert stderr == '' _check_master(kc, expected=True) _check_master(kc, expected=True, stream="stderr") @@ -129,8 +129,8 @@ def test_subprocess_error(): msg_id, content = execute(kc=kc, code=code) stdout, stderr = assemble_output(iopub) - nt.assert_equal(stdout, '') - nt.assert_true("ValueError" in stderr, stderr) + assert stdout == '' + assert "ValueError" in stderr _check_master(kc, expected=True) _check_master(kc, expected=True, stream="stderr") @@ -147,15 +147,15 @@ def test_raw_input(): code = 'print({input_f}("{theprompt}"))'.format(**locals()) msg_id = kc.execute(code, allow_stdin=True) msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT) - nt.assert_equal(msg['header']['msg_type'], u'input_request') + assert msg['header']['msg_type'] == u'input_request' content = msg['content'] - nt.assert_equal(content['prompt'], theprompt) + assert content['prompt'] == theprompt text = "some text" kc.input(text) reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) - nt.assert_equal(reply['content']['status'], 'ok') + assert reply['content']['status'] == 'ok' stdout, stderr = assemble_output(iopub) - nt.assert_equal(stdout, text + "\n") + assert stdout == text + "\n" @dec.skipif(py3compat.PY3) @@ -169,14 +169,14 @@ def test_eval_input(): code = 'print(input("{theprompt}"))'.format(**locals()) msg_id = kc.execute(code, allow_stdin=True) msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT) - nt.assert_equal(msg['header']['msg_type'], u'input_request') + assert msg['header']['msg_type'] == u'input_request' content = msg['content'] - nt.assert_equal(content['prompt'], theprompt) + assert content['prompt'] == theprompt kc.input("1+1") reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) - nt.assert_equal(reply['content']['status'], 'ok') + assert reply['content']['status'] == 'ok' stdout, stderr = assemble_output(iopub) - nt.assert_equal(stdout, "2\n") + assert stdout == "2\n" def test_save_history(): @@ -189,11 +189,11 @@ def test_save_history(): execute(u'b=u"abcþ"', kc=kc) wait_for_idle(kc) _, reply = execute("%hist -f " + file, kc=kc) - nt.assert_equal(reply['status'], 'ok') + assert reply['status'] == 'ok' with io.open(file, encoding='utf-8') as f: content = f.read() - nt.assert_in(u'a=1', content) - nt.assert_in(u'b=u"abcþ"', content) + assert u'a=1' in content + assert u'b=u"abcþ"' in content @dec.skip_without('faulthandler') @@ -248,13 +248,13 @@ def test_complete(): kc.complete(cell) reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) c = reply['content'] - nt.assert_equal(c['status'], 'ok') - nt.assert_equal(c['cursor_start'], cell.find('a.')) - nt.assert_equal(c['cursor_end'], cell.find('a.') + 2) + assert c['status'] == 'ok' + assert c['cursor_start'] == cell.find('a.') + assert c['cursor_end'] == cell.find('a.') + 2 matches = c['matches'] nt.assert_greater(len(matches), 0) for match in matches: - nt.assert_equal(match[:2], 'a.') + assert match[:2] == 'a.' @dec.skip_without('matplotlib') @@ -270,7 +270,7 @@ def test_matplotlib_inline_on_import(): _check_status(reply) backend_bundle = reply['user_expressions']['backend'] _check_status(backend_bundle) - nt.assert_in('backend_inline', backend_bundle['data']['text/plain']) + assert 'backend_inline' in backend_bundle['data']['text/plain'] def test_shutdown(): @@ -285,4 +285,4 @@ def test_shutdown(): time.sleep(.1) else: break - nt.assert_false(km.is_alive()) + assert not km.is_alive() diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index 565510fb6..a3b9771c7 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -7,7 +7,7 @@ import shutil import sys import tempfile - + try: from unittest import mock except ImportError: @@ -42,9 +42,9 @@ def test_make_ipkernel_cmd(): def assert_kernel_dict(d): - nt.assert_equal(d['argv'], make_ipkernel_cmd()) - nt.assert_equal(d['display_name'], 'Python %i' % sys.version_info[0]) - nt.assert_equal(d['language'], 'python') + assert d['argv'] == make_ipkernel_cmd() + assert d['display_name'] == 'Python %i' % sys.version_info[0] + assert d['language'] == 'python' def test_get_kernel_dict(): @@ -55,8 +55,8 @@ def test_get_kernel_dict(): def assert_kernel_dict_with_profile(d): nt.assert_equal(d['argv'], make_ipkernel_cmd( extra_arguments=["--profile", "test"])) - nt.assert_equal(d['display_name'], 'Python %i' % sys.version_info[0]) - nt.assert_equal(d['language'], 'python') + assert d['display_name'] == 'Python %i' % sys.version_info[0] + assert d['language'] == 'python' def test_get_kernel_dict_with_profile(): @@ -83,7 +83,7 @@ def test_write_kernel_spec(): def test_write_kernel_spec_path(): path = os.path.join(tempfile.mkdtemp(), KERNEL_NAME) path2 = write_kernel_spec(path) - nt.assert_equal(path, path2) + assert path == path2 assert_is_spec(path) shutil.rmtree(path) @@ -129,7 +129,7 @@ def test_install_profile(): spec = os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME, "kernel.json") with open(spec) as f: spec = json.load(f) - nt.assert_true(spec["display_name"].endswith(" [profile=Test]")) + assert spec["display_name"].endswith(" [profile=Test]") nt.assert_equal(spec["argv"][-2:], ["--profile", "Test"]) @@ -143,4 +143,4 @@ def test_install_display_name_overrides_profile(): spec = os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME, "kernel.json") with open(spec) as f: spec = json.load(f) - nt.assert_equal(spec["display_name"], "Display") + assert spec["display_name"] == "Display" diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 4991ec288..ebf9d9237 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -49,7 +49,7 @@ class Reference(HasTraits): def check(self, d): """validate a dict against our traits""" for key in self.trait_names(): - nt.assert_in(key, d) + assert key in d # FIXME: always allow None, probably not a good idea if d[key] is None: continue @@ -101,7 +101,7 @@ class MimeBundle(Reference): def _data_changed(self, name, old, new): for k,v in iteritems(new): assert mime_pat.match(k) - nt.assert_is_instance(v, string_types) + assert isinstance(v, string_types) # shell replies @@ -257,9 +257,9 @@ def validate_message(msg, msg_type=None, parent=None): """ RMessage().check(msg) if msg_type: - nt.assert_equal(msg['msg_type'], msg_type) + assert msg['msg_type'] == msg_type if parent: - nt.assert_equal(msg['parent_header']['msg_id'], parent) + assert msg['parent_header']['msg_id'] == parent content = msg['content'] ref = references[msg['msg_type']] ref.check(content) @@ -286,7 +286,7 @@ def test_execute_silent(): # flush status=idle status = KC.iopub_channel.get_msg(timeout=TIMEOUT) validate_message(status, 'status', msg_id) - nt.assert_equal(status['content']['execution_state'], 'idle') + assert status['content']['execution_state'] == 'idle' nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1) count = reply['execution_count'] @@ -296,19 +296,19 @@ def test_execute_silent(): # flush status=idle status = KC.iopub_channel.get_msg(timeout=TIMEOUT) validate_message(status, 'status', msg_id) - nt.assert_equal(status['content']['execution_state'], 'idle') + assert status['content']['execution_state'] == 'idle' nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1) count_2 = reply['execution_count'] - nt.assert_equal(count_2, count) + assert count_2 == count def test_execute_error(): flush_channels() msg_id, reply = execute(code='1/0') - nt.assert_equal(reply['status'], 'error') - nt.assert_equal(reply['ename'], 'ZeroDivisionError') + assert reply['status'] == 'error' + assert reply['ename'] == 'ZeroDivisionError' error = KC.iopub_channel.get_msg(timeout=TIMEOUT) validate_message(error, 'error', msg_id) @@ -325,7 +325,7 @@ def test_execute_inc(): msg_id, reply = execute(code='x=2') count_2 = reply['execution_count'] - nt.assert_equal(count_2, count+1) + assert count_2 == count+1 def test_execute_stop_on_error(): """execute request should not abort execution queue with stop_on_error False""" @@ -341,7 +341,7 @@ def test_execute_stop_on_error(): msg_id = KC.execute(code='print("Hello")') KC.get_shell_msg(timeout=TIMEOUT) reply = KC.get_shell_msg(timeout=TIMEOUT) - nt.assert_equal(reply['content']['status'], 'aborted') + assert reply['content']['status'] == 'aborted' flush_channels() @@ -349,7 +349,7 @@ def test_execute_stop_on_error(): msg_id = KC.execute(code='print("Hello")') KC.get_shell_msg(timeout=TIMEOUT) reply = KC.get_shell_msg(timeout=TIMEOUT) - nt.assert_equal(reply['content']['status'], 'ok') + assert reply['content']['status'] == 'ok' def test_user_expressions(): @@ -370,8 +370,8 @@ def test_user_expressions_fail(): msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname')) user_expressions = reply['user_expressions'] foo = user_expressions['foo'] - nt.assert_equal(foo['status'], 'error') - nt.assert_equal(foo['ename'], 'NameError') + assert foo['status'] == 'error' + assert foo['ename'] == 'NameError' def test_oinfo(): @@ -393,8 +393,8 @@ def test_oinfo_found(): content = reply['content'] assert content['found'] text = content['data']['text/plain'] - nt.assert_in('Type:', text) - nt.assert_in('Docstring:', text) + assert 'Type:' in text + assert 'Docstring:' in text def test_oinfo_detail(): @@ -408,8 +408,8 @@ def test_oinfo_detail(): content = reply['content'] assert content['found'] text = content['data']['text/plain'] - nt.assert_in('Signature:', text) - nt.assert_in('Source:', text) + assert 'Signature:' in text + assert 'Source:' in text def test_oinfo_not_found(): @@ -419,7 +419,7 @@ def test_oinfo_not_found(): reply = KC.get_shell_msg(timeout=TIMEOUT) validate_message(reply, 'inspect_reply', msg_id) content = reply['content'] - nt.assert_false(content['found']) + assert not content['found'] def test_complete(): @@ -432,7 +432,7 @@ def test_complete(): validate_message(reply, 'complete_reply', msg_id) matches = reply['content']['matches'] for name in ('alpha', 'albert'): - nt.assert_in(name, matches) + assert name in matches def test_kernel_info_request(): @@ -469,7 +469,7 @@ def test_single_payload(): " x=range?\n") payload = reply['payload'] next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"] - nt.assert_equal(len(next_input_pls), 1) + assert len(next_input_pls) == 1 def test_is_complete(): flush_channels() @@ -488,7 +488,7 @@ def test_history_range(): reply = KC.get_shell_msg(timeout=TIMEOUT) validate_message(reply, 'history_reply', msg_id) content = reply['content'] - nt.assert_equal(len(content['history']), 1) + assert len(content['history']) == 1 def test_history_tail(): flush_channels() @@ -500,7 +500,7 @@ def test_history_tail(): reply = KC.get_shell_msg(timeout=TIMEOUT) validate_message(reply, 'history_reply', msg_id) content = reply['content'] - nt.assert_equal(len(content['history']), 1) + assert len(content['history']) == 1 def test_history_search(): flush_channels() @@ -512,7 +512,7 @@ def test_history_search(): reply = KC.get_shell_msg(timeout=TIMEOUT) validate_message(reply, 'history_reply', msg_id) content = reply['content'] - nt.assert_equal(len(content['history']), 1) + assert len(content['history']) == 1 # IOPub channel @@ -525,7 +525,7 @@ def test_stream(): stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT) validate_message(stdout, 'stream', msg_id) content = stdout['content'] - nt.assert_equal(content['text'], u'hi\n') + assert content['text'] == u'hi\n' def test_display_data(): @@ -536,4 +536,4 @@ def test_display_data(): display = KC.iopub_channel.get_msg(timeout=TIMEOUT) validate_message(display, 'display_data', parent=msg_id) data = display['content']['data'] - nt.assert_equal(data['text/plain'], u'1') + assert data['text/plain'] == u'1' diff --git a/ipykernel/tests/test_pickleutil.py b/ipykernel/tests/test_pickleutil.py index c8dccb4ca..856f6c883 100644 --- a/ipykernel/tests/test_pickleutil.py +++ b/ipykernel/tests/test_pickleutil.py @@ -1,9 +1,6 @@ -import os import pickle -import nose.tools as nt - from ipykernel.pickleutil import can, uncan, codeutil def interactive(f): @@ -24,7 +21,7 @@ def foo(): pfoo = dumps(foo) bar = loads(pfoo) - nt.assert_equal(foo(), bar()) + assert foo() == bar() def test_generator_closure(): # this only creates a closure on Python 3 @@ -36,7 +33,7 @@ def foo(): pfoo = dumps(foo) bar = loads(pfoo) - nt.assert_equal(foo(), bar()) + assert foo() == bar() def test_nested_closure(): @interactive @@ -48,7 +45,7 @@ def g(): pfoo = dumps(foo) bar = loads(pfoo) - nt.assert_equal(foo(), bar()) + assert foo() == bar() def test_closure(): i = 'i' @@ -58,11 +55,11 @@ def foo(): pfoo = dumps(foo) bar = loads(pfoo) - nt.assert_equal(foo(), bar()) + assert foo() == bar() def test_uncan_bytes_buffer(): data = b'data' canned = can(data) canned.buffers = [memoryview(buf) for buf in canned.buffers] out = uncan(canned) - nt.assert_equal(out, data) + assert out == data diff --git a/ipykernel/tests/test_serialize.py b/ipykernel/tests/test_serialize.py index bc01c387f..849f7208b 100644 --- a/ipykernel/tests/test_serialize.py +++ b/ipykernel/tests/test_serialize.py @@ -6,31 +6,22 @@ import pickle from collections import namedtuple -import nose.tools as nt - from ipykernel.serialize import serialize_object, deserialize_object from IPython.testing import decorators as dec from ipykernel.pickleutil import CannedArray, CannedClass, interactive -from ipython_genutils.py3compat import iteritems -#------------------------------------------------------------------------------- -# Globals and Utilities -#------------------------------------------------------------------------------- def roundtrip(obj): """roundtrip an object through serialization""" bufs = serialize_object(obj) obj2, remainder = deserialize_object(bufs) - nt.assert_equals(remainder, []) + assert remainder == [] return obj2 SHAPES = ((100,), (1024,10), (10,8,6,5), (), (0,)) DTYPES = ('uint8', 'float64', 'int32', [('g', 'float32')], '|S10') -#------------------------------------------------------------------------------- -# Tests -#------------------------------------------------------------------------------- def new_array(shape, dtype): import numpy @@ -44,7 +35,7 @@ def test_roundtrip_simple(): (b'123', 'hello'), ]: obj2 = roundtrip(obj) - nt.assert_equal(obj, obj2) + assert obj == obj2 def test_roundtrip_nested(): for obj in [ @@ -52,7 +43,7 @@ def test_roundtrip_nested(): [range(5),[range(3),(1,[b'whoda'])]], ]: obj2 = roundtrip(obj) - nt.assert_equal(obj, obj2) + assert obj == obj2 def test_roundtrip_buffered(): for obj in [ @@ -61,19 +52,19 @@ def test_roundtrip_buffered(): [b"hello"*501, 1,2,3] ]: bufs = serialize_object(obj) - nt.assert_equal(len(bufs), 2) + assert len(bufs) == 2 obj2, remainder = deserialize_object(bufs) - nt.assert_equal(remainder, []) - nt.assert_equal(obj, obj2) + assert remainder == [] + assert obj == obj2 def test_roundtrip_memoryview(): b = b'asdf' * 1025 view = memoryview(b) bufs = serialize_object(view) - nt.assert_equal(len(bufs), 2) + assert len(bufs) == 2 v2, remainder = deserialize_object(bufs) - nt.assert_equal(remainder, []) - nt.assert_equal(v2.tobytes(), b) + assert remainder == [] + assert v2.tobytes() == b @dec.skip_without('numpy') def test_numpy(): @@ -85,9 +76,9 @@ def test_numpy(): bufs = serialize_object(A) bufs = [memoryview(b) for b in bufs] B, r = deserialize_object(bufs) - nt.assert_equal(r, []) - nt.assert_equal(A.shape, B.shape) - nt.assert_equal(A.dtype, B.dtype) + assert r == [] + assert A.shape == B.shape + assert A.dtype == B.dtype assert_array_equal(A,B) @dec.skip_without('numpy') @@ -103,9 +94,9 @@ def test_recarray(): bufs = serialize_object(A) B, r = deserialize_object(bufs) - nt.assert_equal(r, []) - nt.assert_equal(A.shape, B.shape) - nt.assert_equal(A.dtype, B.dtype) + assert r == [] + assert A.shape == B.shape + assert A.dtype == B.dtype assert_array_equal(A,B) @dec.skip_without('numpy') @@ -117,12 +108,12 @@ def test_numpy_in_seq(): A = new_array(shape, dtype=dtype) bufs = serialize_object((A,1,2,b'hello')) canned = pickle.loads(bufs[0]) - nt.assert_is_instance(canned[0], CannedArray) + assert isinstance(canned[0], CannedArray) tup, r = deserialize_object(bufs) B = tup[0] - nt.assert_equal(r, []) - nt.assert_equal(A.shape, B.shape) - nt.assert_equal(A.dtype, B.dtype) + assert r == [] + assert A.shape == B.shape + assert A.dtype == B.dtype assert_array_equal(A,B) @dec.skip_without('numpy') @@ -134,12 +125,12 @@ def test_numpy_in_dict(): A = new_array(shape, dtype=dtype) bufs = serialize_object(dict(a=A,b=1,c=range(20))) canned = pickle.loads(bufs[0]) - nt.assert_is_instance(canned['a'], CannedArray) + assert isinstance(canned['a'], CannedArray) d, r = deserialize_object(bufs) B = d['a'] - nt.assert_equal(r, []) - nt.assert_equal(A.shape, B.shape) - nt.assert_equal(A.dtype, B.dtype) + assert r == [] + assert A.shape == B.shape + assert A.dtype == B.dtype assert_array_equal(A,B) def test_class(): @@ -148,10 +139,10 @@ class C(object): a=5 bufs = serialize_object(dict(C=C)) canned = pickle.loads(bufs[0]) - nt.assert_is_instance(canned['C'], CannedClass) + assert isinstance(canned['C'], CannedClass) d, r = deserialize_object(bufs) C2 = d['C'] - nt.assert_equal(C2.a, C.a) + assert C2.a == C.a def test_class_oldstyle(): @interactive @@ -160,18 +151,18 @@ class C: bufs = serialize_object(dict(C=C)) canned = pickle.loads(bufs[0]) - nt.assert_is_instance(canned['C'], CannedClass) + assert isinstance(canned['C'], CannedClass) d, r = deserialize_object(bufs) C2 = d['C'] - nt.assert_equal(C2.a, C.a) + assert C2.a == C.a def test_tuple(): tup = (lambda x:x, 1) bufs = serialize_object(tup) canned = pickle.loads(bufs[0]) - nt.assert_is_instance(canned, tuple) + assert isinstance(canned, tuple) t2, r = deserialize_object(bufs) - nt.assert_equal(t2[0](t2[1]), tup[0](tup[1])) + assert t2[0](t2[1]) == tup[0](tup[1]) point = namedtuple('point', 'x y') @@ -179,18 +170,18 @@ def test_namedtuple(): p = point(1,2) bufs = serialize_object(p) canned = pickle.loads(bufs[0]) - nt.assert_is_instance(canned, point) + assert isinstance(canned, point) p2, r = deserialize_object(bufs, globals()) - nt.assert_equal(p2.x, p.x) - nt.assert_equal(p2.y, p.y) + assert p2.x == p.x + assert p2.y == p.y def test_list(): lis = [lambda x:x, 1] bufs = serialize_object(lis) canned = pickle.loads(bufs[0]) - nt.assert_is_instance(canned, list) + assert isinstance(canned, list) l2, r = deserialize_object(bufs) - nt.assert_equal(l2[0](l2[1]), lis[0](lis[1])) + assert l2[0](l2[1]) == lis[0](lis[1]) def test_class_inheritance(): @interactive @@ -203,8 +194,8 @@ class D(C): bufs = serialize_object(dict(D=D)) canned = pickle.loads(bufs[0]) - nt.assert_is_instance(canned['D'], CannedClass) + assert isinstance(canned['D'], CannedClass) d, r = deserialize_object(bufs) D2 = d['D'] - nt.assert_equal(D2.a, D.a) - nt.assert_equal(D2.b, D.b) + assert D2.a == D.a + assert D2.b == D.b diff --git a/ipykernel/tests/test_start_kernel.py b/ipykernel/tests/test_start_kernel.py index f655a23b3..797ddf19c 100644 --- a/ipykernel/tests/test_start_kernel.py +++ b/ipykernel/tests/test_start_kernel.py @@ -1,5 +1,3 @@ -import nose.tools as nt - from .test_embed_kernel import setup_kernel TIMEOUT = 15 @@ -15,19 +13,19 @@ def test_ipython_start_kernel_userns(): content = msg['content'] assert content['found'] text = content['data']['text/plain'] - nt.assert_in(u'123', text) + assert u'123' in text # user_module should be an instance of DummyMod msg_id = client.execute("usermod = get_ipython().user_module") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - nt.assert_equal(content['status'], u'ok') + assert content['status'] == u'ok' msg_id = client.inspect('usermod') msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] - nt.assert_in(u'DummyMod', text) + assert u'DummyMod' in text def test_ipython_start_kernel_no_userns(): # Issue #4188 - user_ns should be passed to shell as None, not {} @@ -39,10 +37,10 @@ def test_ipython_start_kernel_no_userns(): msg_id = client.execute("usermod = get_ipython().user_module") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - nt.assert_equal(content['status'], u'ok') + assert content['status'] == u'ok' msg_id = client.inspect('usermod') msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] - nt.assert_not_in(u'DummyMod', text) + assert u'DummyMod' not in text diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index 8426fa222..9fb5aaa69 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -92,19 +92,19 @@ def test_display_publisher_creation(self): that keyword args get assigned correctly, and override the defaults. """ - self.assertEqual(self.disp_pub.session, self.session) - self.assertEqual(self.disp_pub.pub_socket, self.socket) + assert self.disp_pub.session == self.session + assert self.disp_pub.pub_socket == self.socket def test_thread_local_hooks(self): """ Confirms that the thread_local attribute is correctly initialised with an empty list for the display hooks """ - self.assertEqual(self.disp_pub._hooks, []) + assert self.disp_pub._hooks == [] def hook(msg): return msg self.disp_pub.register_hook(hook) - self.assertEqual(self.disp_pub._hooks, [hook]) + assert self.disp_pub._hooks == [hook] q = Queue() def set_thread_hooks(): @@ -112,7 +112,7 @@ def set_thread_hooks(): t = Thread(target=set_thread_hooks) t.start() thread_hooks = q.get(timeout=10) - self.assertEqual(thread_hooks, []) + assert thread_hooks == [] def test_publish(self): """ @@ -120,10 +120,9 @@ def test_publish(self): `send` by default. """ data = dict(a = 1) - - self.assertEqual(self.session.send_count, 0) + assert self.session.send_count == 0 self.disp_pub.publish(data) - self.assertEqual(self.session.send_count, 1) + assert self.session.send_count == 1 def test_display_hook_halts_send(self): """ @@ -136,13 +135,13 @@ def test_display_hook_halts_send(self): hook = NoReturnDisplayHook() self.disp_pub.register_hook(hook) - self.assertEqual(hook.call_count, 0) - self.assertEqual(self.session.send_count, 0) + assert hook.call_count == 0 + assert self.session.send_count == 0 self.disp_pub.publish(data) - self.assertEqual(hook.call_count, 1) - self.assertEqual(self.session.send_count, 0) + assert hook.call_count == 1 + assert self.session.send_count == 0 def test_display_hook_return_calls_send(self): """ @@ -155,13 +154,13 @@ def test_display_hook_return_calls_send(self): hook = ReturnDisplayHook() self.disp_pub.register_hook(hook) - self.assertEqual(hook.call_count, 0) - self.assertEqual(self.session.send_count, 0) + assert hook.call_count == 0 + assert self.session.send_count == 0 self.disp_pub.publish(data) - self.assertEqual(hook.call_count, 1) - self.assertEqual(self.session.send_count, 1) + assert hook.call_count == 1 + assert self.session.send_count == 1 def test_unregister_hook(self): """ @@ -172,13 +171,13 @@ def test_unregister_hook(self): hook = NoReturnDisplayHook() self.disp_pub.register_hook(hook) - self.assertEqual(hook.call_count, 0) - self.assertEqual(self.session.send_count, 0) + assert hook.call_count == 0 + assert self.session.send_count == 0 self.disp_pub.publish(data) - self.assertEqual(hook.call_count, 1) - self.assertEqual(self.session.send_count, 0) + assert hook.call_count == 1 + assert self.session.send_count == 0 # # After unregistering the `NoReturn` hook, any calls @@ -193,8 +192,8 @@ def test_unregister_hook(self): self.disp_pub.publish(data) self.assertTrue(first) - self.assertEqual(hook.call_count, 1) - self.assertEqual(self.session.send_count, 1) + assert hook.call_count == 1 + assert self.session.send_count == 1 # # If a hook is not installed, `unregister_hook` diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 472a401b9..31ab00b05 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -17,7 +17,6 @@ from Queue import Empty # Py 2 import nose -import nose.tools as nt from jupyter_client import manager @@ -68,12 +67,12 @@ def execute(code='', kc=None, **kwargs): validate_message(reply, 'execute_reply', msg_id) busy = kc.get_iopub_msg(timeout=TIMEOUT) validate_message(busy, 'status', msg_id) - nt.assert_equal(busy['content']['execution_state'], 'busy') + assert busy['content']['execution_state'] == 'busy' if not kwargs.get('silent'): execute_input = kc.get_iopub_msg(timeout=TIMEOUT) validate_message(execute_input, 'execute_input', msg_id) - nt.assert_equal(execute_input['content']['code'], code) + assert execute_input['content']['code'] == code # show tracebacks if present for debugging if reply['content'].get('traceback'): From 7a81ca7c6a23f0c9e2975769ef30e8bed5264967 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 27 Nov 2017 12:01:27 +0100 Subject: [PATCH 0142/1195] process iopub events in order - events is an OrderedDict - process all events when an event arrives avoids fair-queue issues with multiple writes from concurrent threads This means that sometimes an event will arrive with nothing to do, but that's fine --- ipykernel/iostream.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 6d905041f..36b0a40b1 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -7,6 +7,7 @@ from __future__ import print_function import atexit from binascii import b2a_hex +from collections import OrderedDict try: from importlib import lock_held as import_lock_held except ImportError: @@ -66,7 +67,7 @@ def __init__(self, socket, pipe=False): if pipe: self._setup_pipe_in() self._local = threading.local() - self._events = {} + self._events = OrderedDict() self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main) self.thread.daemon = True @@ -87,7 +88,7 @@ def _setup_event_pipe(self): pipe_in.bind(iface) self._event_puller = ZMQStream(pipe_in, self.io_loop) self._event_puller.on_recv(self._handle_event) - + @property def _event_pipe(self): """thread-local event pipe for signaling events that should be processed in the thread""" @@ -103,11 +104,18 @@ def _event_pipe(self): return event_pipe def _handle_event(self, msg): - """Handle an event on the event pipe""" - event_id = msg[0] - event_f = self._events.pop(event_id) - event_f() - + """Handle an event on the event pipe + + Whenever *an* event arrives on the event stream, + *all* waiting events are processed in order. + """ + # freeze event_id list so new writes don't add to it + # while we are processing + event_ids = list(self._events) + for event_id in event_ids: + event_f = self._events.pop(event_id) + event_f() + def _setup_pipe_in(self): """setup listening pipe for IOPub from forked subprocesses""" ctx = self.socket.context @@ -129,7 +137,7 @@ def _setup_pipe_in(self): return self._pipe_in = ZMQStream(pipe_in, self.io_loop) self._pipe_in.on_recv(self._handle_pipe_msg) - + def _handle_pipe_msg(self, msg): """handle a pipe message from a subprocess""" if not self._pipe_flag or not self._is_master_process(): From 77e319a70dbedec13f5f391580c1fba626c2d0b1 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 27 Nov 2017 14:43:18 +0100 Subject: [PATCH 0143/1195] use a deque for iopub events OrderedDict isn't thread-safe --- ipykernel/iostream.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 36b0a40b1..530ebd96b 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -7,7 +7,7 @@ from __future__ import print_function import atexit from binascii import b2a_hex -from collections import OrderedDict +from collections import deque try: from importlib import lock_held as import_lock_held except ImportError: @@ -67,7 +67,7 @@ def __init__(self, socket, pipe=False): if pipe: self._setup_pipe_in() self._local = threading.local() - self._events = OrderedDict() + self._events = deque() self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main) self.thread.daemon = True @@ -106,14 +106,16 @@ def _event_pipe(self): def _handle_event(self, msg): """Handle an event on the event pipe + Content of the message is ignored. + Whenever *an* event arrives on the event stream, *all* waiting events are processed in order. """ - # freeze event_id list so new writes don't add to it + # freeze event count so new writes don't extend the queue # while we are processing - event_ids = list(self._events) - for event_id in event_ids: - event_f = self._events.pop(event_id) + n_events = len(self._events) + for i in range(n_events): + event_f = self._events.popleft() event_f() def _setup_pipe_in(self): @@ -195,11 +197,9 @@ def schedule(self, f): If the thread is not running, call immediately. """ if self.thread.is_alive(): - event_id = os.urandom(16) - while event_id in self._events: - event_id = os.urandom(16) - self._events[event_id] = f - self._event_pipe.send(event_id) + self._events.append(f) + # wake event thread (message content is ignored) + self._event_pipe.send(b'') else: f() From fa814da201bdebd5b16110597604f7dabafec58d Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 27 Nov 2017 14:52:13 +0100 Subject: [PATCH 0144/1195] check for loop.shutdown_asyncgens before calling it new in py36 --- ipykernel/eventloops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index fb1cc31fa..86f21cd08 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -313,7 +313,8 @@ def kernel_handler(): continue except Exception as e: error = e - loop.run_until_complete(loop.shutdown_asyncgens()) + if hasattr(loop, 'shutdown_asyncgens'): + loop.run_until_complete(loop.shutdown_asyncgens()) loop.close() if error is not None: raise error From 3bdc18045087eca0ea66011b1d1ce9c5e843b975 Mon Sep 17 00:00:00 2001 From: Ian Rose Date: Mon, 4 Dec 2017 15:19:53 -0800 Subject: [PATCH 0145/1195] Update help links. --- ipykernel/ipkernel.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 4abf88926..6304131f9 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -72,32 +72,32 @@ def __init__(self, **kwargs): help_links = List([ { - 'text': "Python", - 'url': "http://docs.python.org/%i.%i" % sys.version_info[:2], + 'text': "Python Reference", + 'url': "https://docs.python.org/%i.%i" % sys.version_info[:2], }, { - 'text': "IPython", - 'url': "http://ipython.org/documentation.html", + 'text': "IPython Reference", + 'url': "https://ipython.org/documentation.html", }, { - 'text': "NumPy", - 'url': "http://docs.scipy.org/doc/numpy/reference/", + 'text': "NumPy Reference", + 'url': "https://docs.scipy.org/doc/numpy/reference/", }, { - 'text': "SciPy", - 'url': "http://docs.scipy.org/doc/scipy/reference/", + 'text': "SciPy Reference", + 'url': "https://docs.scipy.org/doc/scipy/reference/", }, { - 'text': "Matplotlib", - 'url': "http://matplotlib.org/contents.html", + 'text': "Matplotlib Reference", + 'url': "https://matplotlib.org/contents.html", }, { - 'text': "SymPy", + 'text': "SymPy Reference", 'url': "http://docs.sympy.org/latest/index.html", }, { - 'text': "pandas", - 'url': "http://pandas.pydata.org/pandas-docs/stable/", + 'text': "pandas Reference", + 'url': "https://pandas.pydata.org/pandas-docs/stable/", }, ]).tag(config=True) From b457aafd966d131f56bbf46fc9160273d8937930 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 5 Dec 2017 05:28:28 -0600 Subject: [PATCH 0146/1195] remove trailing comma --- ipykernel/connect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index a49932456..faaa402ec 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -177,7 +177,7 @@ def connect_qtconsole(connection_file=None, argv=None, profile=None): return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'), - **kwargs, + **kwargs ) From ccde50c42fcdc81c077c6056e0139ec5b3fedd1c Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 5 Dec 2017 06:07:37 -0600 Subject: [PATCH 0147/1195] Add entries to change log --- docs/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index fb35d04c3..782427dc6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,6 +12,10 @@ Changes in IPython kernel - Add event loop integration for :mod:`asyncio`. - Use the new IPython completer API. - Add support for displaying GIF images (mimetype ``image/gif``). +- Allow the kernel to be interrupted without kiling the Qt console. +- Fix is_complete response with cell magics. +- Clean up encoding of bytes objects. + 4.6 --- From 4784178dbf7c270e787c2556a7609694b22e63ba Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 5 Dec 2017 06:16:04 -0600 Subject: [PATCH 0148/1195] docs update --- docs/changelog.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 782427dc6..7fd410d2d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -12,8 +12,8 @@ Changes in IPython kernel - Add event loop integration for :mod:`asyncio`. - Use the new IPython completer API. - Add support for displaying GIF images (mimetype ``image/gif``). -- Allow the kernel to be interrupted without kiling the Qt console. -- Fix is_complete response with cell magics. +- Allow the kernel to be interrupted without killing the Qt console. +- Fix ``is_complete`` response with cell magics. - Clean up encoding of bytes objects. From bd77ce4a61317e85bd78c597ec216790d9008bc7 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 5 Dec 2017 06:17:25 -0600 Subject: [PATCH 0149/1195] docs update --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 7fd410d2d..9d24427f4 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -15,6 +15,7 @@ Changes in IPython kernel - Allow the kernel to be interrupted without killing the Qt console. - Fix ``is_complete`` response with cell magics. - Clean up encoding of bytes objects. +- Clean up help links to use ``https`` and improve display titles. 4.6 From cfaaf6e918e06cfc277e2e9253753d0cea7fbff4 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 6 Dec 2017 10:51:36 +0100 Subject: [PATCH 0150/1195] show 10 slowest tests pytest support for timing tests is builtin! --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1bdbe107c..1c0e6f8fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ install: fi script: - jupyter kernelspec list - - pytest --cov ipykernel -v ipykernel + - pytest --cov ipykernel --durations 10 -v ipykernel after_success: - codecov matrix: From 5b5ca0a1f15355e5f9a1ad9076bd7c0c2ac46922 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 6 Dec 2017 11:05:18 +0100 Subject: [PATCH 0151/1195] install pytest 3.2 on Python 3.3 Requires-Python seems to not be set properly specify pytest versions per python version to get the right version on py33 seems to not work right when unbound on 'test' but bound on 'test:python==3.3' --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d75bf2c6c..4b388859d 100644 --- a/setup.py +++ b/setup.py @@ -102,8 +102,10 @@ extras_require = setuptools_args['extras_require'] = { 'test:python_version=="2.7"': ['mock'], + # pytest 3.3 doesn't work on Python 3.3 + 'test:python_version=="3.3"': ['pytest==3.2.*'], + 'test:python_version!="3.3"': ['pytest>=3.2'], 'test': [ - 'pytest', 'pytest-cov', 'nose', # nose because there are still a few nose.tools imports hanging around ], From 8afcb746b34b3de26f2e7d100ee17a1dbcc37b7e Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 6 Dec 2017 11:14:42 +0100 Subject: [PATCH 0152/1195] pip freeze on travis --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 1c0e6f8fe..c68a67e36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,7 @@ install: if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" || "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then pip install matplotlib fi + - pip freeze script: - jupyter kernelspec list - pytest --cov ipykernel --durations 10 -v ipykernel From 2a36d5c5c3baedb10d35269513bb9df78e73e778 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 6 Dec 2017 04:32:25 -0600 Subject: [PATCH 0153/1195] Add a note about tornado --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 9d24427f4..a5d950d77 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -16,6 +16,7 @@ Changes in IPython kernel - Fix ``is_complete`` response with cell magics. - Clean up encoding of bytes objects. - Clean up help links to use ``https`` and improve display titles. +- Clean up ioloop handling in preparation for tornado 5. 4.6 From aeb5bf8df38214ac402499e1a6b5277227bf16c6 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 6 Dec 2017 11:43:10 +0100 Subject: [PATCH 0154/1195] Windows! --- appveyor.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 939ea8f80..fe9d25315 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -21,14 +21,14 @@ install: pip install --upgrade pip wheel pip --version - cmd: | - pip install --pre -e . coverage nose_warnings_filters - pip install ipykernel[test] nose-timer + pip install --pre -e . + pip install ipykernel[test] - cmd: | pip install matplotlib numpy pip freeze - cmd: python -c "import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)" test_script: - - cmd: nosetests --with-coverage --with-timer --cover-package=ipykernel ipykernel + - cmd: pytest -v --cov ipykernel ipykernel on_success: - cmd: pip install codecov From eab0eb6908bd196f7097d07f66125dc215f89ab6 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 10 Nov 2017 14:13:10 +0100 Subject: [PATCH 0155/1195] update use of tornado current prepare for tornado 5: - use thread-local .current over deprecated global .instance - avoid making IOPubThread loops main thread's instance for a brief period during initialization --- ipykernel/iostream.py | 3 ++- ipykernel/kernelapp.py | 3 ++- ipykernel/kernelbase.py | 5 +++-- ipykernel/zmqshell.py | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 530ebd96b..0d11d9e57 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -63,7 +63,7 @@ def __init__(self, socket, pipe=False): self.background_socket = BackgroundSocket(self) self._master_pid = os.getpid() self._pipe_flag = pipe - self.io_loop = IOLoop() + self.io_loop = IOLoop(make_current=False) if pipe: self._setup_pipe_in() self._local = threading.local() @@ -74,6 +74,7 @@ def __init__(self, socket, pipe=False): def _thread_main(self): """The inner loop that's actually run in a thread""" + self.io_loop.make_current() self.io_loop.start() self.io_loop.close(all_fds=True) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 59190c019..b67d15074 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -473,8 +473,9 @@ def start(self): if self.poller is not None: self.poller.start() self.kernel.start() + self.io_loop = ioloop.IOLoop.current() try: - ioloop.IOLoop.instance().start() + self.io_loop.start() except KeyboardInterrupt: pass diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 184a98829..199af5c5e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -49,7 +49,7 @@ class Kernel(SingletonConfigurable): @observe('eventloop') def _update_eventloop(self, change): """schedule call to eventloop from IOLoop""" - loop = ioloop.IOLoop.instance() + loop = ioloop.IOLoop.current() loop.add_callback(self.enter_eventloop) session = Instance(Session, allow_none=True) @@ -272,6 +272,7 @@ def enter_eventloop(self): def start(self): """register dispatchers for streams""" + self.io_loop = ioloop.IOLoop.current() if self.control_stream: self.control_stream.on_recv(self.dispatch_control, copy=False) @@ -530,7 +531,7 @@ def shutdown_request(self, stream, ident, parent): self._at_shutdown() # call sys.exit after a short delay - loop = ioloop.IOLoop.instance() + loop = ioloop.IOLoop.current() loop.add_timeout(time.time()+0.1, loop.stop) def do_shutdown(self, restart): diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 97329f828..7d25d4dfc 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -469,8 +469,8 @@ def _default_exiter(self): def _update_exit_now(self, change): """stop eventloop when exit_now fires""" if change['new']: - loop = ioloop.IOLoop.instance() - loop.add_timeout(time.time() + 0.1, loop.stop) + loop = self.kernel.io_loop + loop.call_later(0.1, loop.stop) keepkernel_on_exit = None From 0f6715761c1892f07ceb3b25da13e98bb72e2db9 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 6 Dec 2017 06:28:05 -0600 Subject: [PATCH 0156/1195] Release 4.7.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 2f78cd400..645a51eff 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 7, 0, 'dev') +version_info = (4, 7, 0) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 24b92317236d643087f5006a30bfb244e082348b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 6 Dec 2017 06:28:28 -0600 Subject: [PATCH 0157/1195] Back to dev version --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 645a51eff..97a8e4a1b 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 7, 0) +version_info = (4, 8, 0, 'dev0') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 72a3c566d2e27dd08d4f367a89aa702e9922593d Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 15 Dec 2017 10:43:41 +0100 Subject: [PATCH 0158/1195] add exit and implement for asyncio, qt still todo: wx, tk, gtk, cocoa --- ipykernel/eventloops.py | 39 +++++++++++++++++++++++++++++++++++---- ipykernel/zmqshell.py | 4 ++++ 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 86f21cd08..60ea036e7 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -49,7 +49,7 @@ def process_stream_events(): 'notebook': None, 'ipympl': None, 'widget': None, - None : None, + None: None, } def register_integration(*toolkitnames): @@ -68,6 +68,17 @@ def register_integration(*toolkitnames): def decorator(func): for name in toolkitnames: loop_map[name] = func + + func.exit_hook = lambda kernel: None + + def exit_decorator(exit_func): + """@func.exit is now a decorator + + to register a function to be called on exit + """ + func.exit_hook = exit_func + + func.exit = exit_decorator return func return decorator @@ -100,6 +111,11 @@ def loop_qt4(kernel): _loop_qt(kernel.app) +@loop_qt4.exit +def loop_qt4_exit(kernel): + kernel.app.exit() + + @register_integration('qt5') def loop_qt5(kernel): """Start a kernel with PyQt5 event loop integration.""" @@ -107,6 +123,11 @@ def loop_qt5(kernel): return loop_qt4(kernel) +@loop_qt5.exit +def loop_qt5_exit(kernel): + kernel.app.exit() + + def _loop_wx(app): """Inner-loop for running the Wx eventloop @@ -296,15 +317,15 @@ def loop_asyncio(kernel): '''Start a kernel with asyncio event loop support.''' import asyncio loop = asyncio.get_event_loop() + # loop is already running (e.g. tornado 5), nothing left to do + if loop.is_running(): + return def kernel_handler(): loop.call_soon(kernel.do_one_iteration) loop.call_later(kernel._poll_interval, kernel_handler) loop.call_soon(kernel_handler) - # loop is already running (e.g. tornado 5), nothing left to do - if loop.is_running(): - return while True: error = None try: @@ -320,6 +341,16 @@ def kernel_handler(): raise error break + +@loop_asyncio.exit +def exit_asyncio(kernel): + """Exit hook for asyncio""" + import asyncio + loop = asyncio.get_event_loop() + if loop.is_running(): + loop.call_soon(loop.stop) + + def enable_gui(gui, kernel=None): """Enable integration with a given GUI""" if gui not in loop_map: diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 7d25d4dfc..3cdf6e6e3 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -471,6 +471,10 @@ def _update_exit_now(self, change): if change['new']: loop = self.kernel.io_loop loop.call_later(0.1, loop.stop) + if self.kernel.eventloop: + exit_hook = getattr(self.kernel.eventloop, 'exit_hook', None) + if exit_hook: + exit_hook(self.kernel) keepkernel_on_exit = None From 158cbb760f0f86fd981191f32aaad9a9eb110fb5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 15 Dec 2017 13:54:53 +0100 Subject: [PATCH 0159/1195] add eventloop_macos Removes reliance on matplotlib for Cocoa eventloop integration mostly copied from pt_inputhooks/osx, with changed wake logic: - wake on timer instead of stdin FD - fix stopping of CFRunLoop when no NSApp is running --- ipykernel/_eventloop_macos.py | 153 ++++++++++++++++++++++++++++++++++ ipykernel/eventloops.py | 51 +++--------- 2 files changed, 164 insertions(+), 40 deletions(-) create mode 100644 ipykernel/_eventloop_macos.py diff --git a/ipykernel/_eventloop_macos.py b/ipykernel/_eventloop_macos.py new file mode 100644 index 000000000..fbae6ac0e --- /dev/null +++ b/ipykernel/_eventloop_macos.py @@ -0,0 +1,153 @@ +"""Eventloop hook for OS X + +Calls NSApp / CoreFoundation APIs via ctypes. +""" + +# cribbed heavily from IPython.terminal.pt_inputhooks.osx +# obj-c boilerplate from appnope, used under BSD 2-clause + +import ctypes +import ctypes.util +from threading import Event + +objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) + +void_p = ctypes.c_void_p + +objc.objc_getClass.restype = void_p +objc.sel_registerName.restype = void_p +objc.objc_msgSend.restype = void_p +objc.objc_msgSend.argtypes = [void_p, void_p] + +msg = objc.objc_msgSend + + +def _utf8(s): + """ensure utf8 bytes""" + if not isinstance(s, bytes): + s = s.encode('utf8') + return s + + +def n(name): + """create a selector name (for ObjC methods)""" + return objc.sel_registerName(_utf8(name)) + + +def C(classname): + """get an ObjC Class by name""" + return objc.objc_getClass(_utf8(classname)) + + +# end obj-c boilerplate from appnope + +# CoreFoundation C-API calls we will use: +CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation')) + +CFAbsoluteTimeGetCurrent = CoreFoundation.CFAbsoluteTimeGetCurrent +CFAbsoluteTimeGetCurrent.restype = ctypes.c_double + +CFRunLoopGetCurrent = CoreFoundation.CFRunLoopGetCurrent +CFRunLoopGetCurrent.restype = void_p + +CFRunLoopGetMain = CoreFoundation.CFRunLoopGetMain +CFRunLoopGetMain.restype = void_p + +CFRunLoopStop = CoreFoundation.CFRunLoopStop +CFRunLoopStop.restype = None +CFRunLoopStop.argtypes = [void_p] + +CFRunLoopTimerCreate = CoreFoundation.CFRunLoopTimerCreate +CFRunLoopTimerCreate.restype = void_p +CFRunLoopTimerCreate.argtypes = [ + void_p, # allocator (NULL) + ctypes.c_double, # fireDate + ctypes.c_double, # interval + ctypes.c_int, # flags (0) + ctypes.c_int, # order (0) + void_p, # callout + void_p, # context +] + +CFRunLoopAddTimer = CoreFoundation.CFRunLoopAddTimer +CFRunLoopAddTimer.restype = None +CFRunLoopAddTimer.argtypes = [ void_p, void_p, void_p ] + +kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, 'kCFRunLoopCommonModes') + + +def _NSApp(): + """Return the global NSApplication instance (NSApp)""" + return msg(C('NSApplication'), n('sharedApplication')) + + +def _wake(NSApp): + """Wake the Application""" + event = msg(C('NSEvent'), + n('otherEventWithType:location:modifierFlags:' + 'timestamp:windowNumber:context:subtype:data1:data2:'), + 15, # Type + 0, # location + 0, # flags + 0, # timestamp + 0, # window + None, # context + 0, # subtype + 0, # data1 + 0, # data2 + ) + msg(NSApp, n('postEvent:atStart:'), void_p(event), True) + + +_triggered = Event() + + +def stop(timer=None, loop=None): + """Callback to fire when there's input to be read""" + _triggered.set() + NSApp = _NSApp() + # if NSApp is not running, stop CFRunLoop directly, + # otherwise stop and wake NSApp + if msg(NSApp, n('isRunning')): + msg(NSApp, n('stop:'), NSApp) + _wake(NSApp) + else: + CFRunLoopStop(CFRunLoopGetCurrent()) + + +_c_callback_func_type = ctypes.CFUNCTYPE(None, void_p, void_p) +_c_stop_callback = _c_callback_func_type(stop) + + +def _stop_after(delay): + """Register callback to stop eventloop after a delay""" + timer = CFRunLoopTimerCreate( + None, # allocator + CFAbsoluteTimeGetCurrent() + delay, # fireDate + 0, # interval + 0, # flags + 0, # order + _c_stop_callback, + None, + ) + CFRunLoopAddTimer( + CFRunLoopGetMain(), + timer, + kCFRunLoopCommonModes, + ) + + +def mainloop(duration=1): + """run the Cocoa eventloop for the specified duration (seconds)""" + + _triggered.clear() + NSApp = _NSApp() + _stop_after(duration) + msg(NSApp, n('run')) + if not _triggered.is_set(): + # app closed without firing callback, + # probably due to last window being closed. + # Run the loop manually in this case, + # since there may be events still to process (ipython/ipython#9734) + CoreFoundation.CFRunLoopRun() + diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 60ea036e7..63541a63b 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -245,62 +245,27 @@ def loop_cocoa(kernel): """Start the kernel, coordinating with the Cocoa CFRunLoop event loop via the matplotlib MacOSX backend. """ - import matplotlib - if matplotlib.__version__ < '1.1.0': - kernel.log.warn( - "MacOSX backend in matplotlib %s doesn't have a Timer, " - "falling back on Tk for CFRunLoop integration. Note that " - "even this won't work if Tk is linked against X11 instead of " - "Cocoa (e.g. EPD). To use the MacOSX backend in the kernel, " - "you must use matplotlib >= 1.1.0, or a native libtk." - ) - return loop_tk(kernel) - - from matplotlib.backends.backend_macosx import TimerMac, show - - # scale interval for sec->ms - poll_interval = int(1000*kernel._poll_interval) + from ._eventloop_macos import mainloop, stop real_excepthook = sys.excepthook def handle_int(etype, value, tb): """don't let KeyboardInterrupts look like crashes""" + # wake the eventloop when we get a signal + stop() if etype is KeyboardInterrupt: io.raw_print("KeyboardInterrupt caught in CFRunLoop") else: real_excepthook(etype, value, tb) - # add doi() as a Timer to the CFRunLoop - def doi(): - # restore excepthook during IPython code - sys.excepthook = real_excepthook - kernel.do_one_iteration() - # and back: - sys.excepthook = handle_int - - t = TimerMac(poll_interval) - t.add_callback(doi) - t.start() - - # but still need a Poller for when there are no active windows, - # during which time mainloop() returns immediately - poller = zmq.Poller() - if kernel.control_stream: - poller.register(kernel.control_stream.socket, zmq.POLLIN) - for stream in kernel.shell_streams: - poller.register(stream.socket, zmq.POLLIN) - - while True: + while not kernel.shell.exit_now: try: # double nested try/except, to properly catch KeyboardInterrupt # due to pyzmq Issue #130 try: # don't let interrupts during mainloop invoke crash_handler: sys.excepthook = handle_int - show.mainloop() + mainloop(kernel._poll_interval) sys.excepthook = real_excepthook - # use poller if mainloop returned (no windows) - # scale by extra factor of 10, since it's a real poll - poller.poll(10*poll_interval) kernel.do_one_iteration() except: raise @@ -312,6 +277,12 @@ def doi(): sys.excepthook = real_excepthook +@loop_cocoa.exit +def loop_cocoa_exit(kernel): + from ._eventloop_macos import stop + stop() + + @register_integration('asyncio') def loop_asyncio(kernel): '''Start a kernel with asyncio event loop support.''' From c2f90eca7cfba9621918782e3c10aff29615ad90 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 15 Dec 2017 14:05:52 +0100 Subject: [PATCH 0160/1195] implement exit hooks for wx, tk, gtk --- ipykernel/eventloops.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 63541a63b..5051c33b2 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -192,6 +192,12 @@ def OnInit(self): _loop_wx(kernel.app) +@loop_wx.exit +def loop_wx_exit(kernel): + import wx + wx.Exit() + + @register_integration('tk') def loop_tk(kernel): """Start a kernel with the Tk event loop.""" @@ -222,6 +228,11 @@ def start(self): kernel.timer.start() +@loop_tk.exit +def loop_tk_exit(kernel): + kernel.timer.app.destroy() + + @register_integration('gtk') def loop_gtk(kernel): """Start the kernel, coordinating with the GTK event loop""" @@ -229,6 +240,12 @@ def loop_gtk(kernel): gtk_kernel = GTKEmbed(kernel) gtk_kernel.start() + kernel._gtk = gtk_kernel + + +@loop_gtk.exit +def loop_gtk_exit(kernel): + kernel._gtk.stop() @register_integration('gtk3') @@ -238,6 +255,12 @@ def loop_gtk3(kernel): gtk_kernel = GTKEmbed(kernel) gtk_kernel.start() + kernel._gtk = gtk_kernel + + +@loop_gtk3.exit +def loop_gtk3_exit(kernel): + kernel._gtk.stop() @register_integration('osx') @@ -314,7 +337,7 @@ def kernel_handler(): @loop_asyncio.exit -def exit_asyncio(kernel): +def loop_asyncio_exit(kernel): """Exit hook for asyncio""" import asyncio loop = asyncio.get_event_loop() From eb4130a5b39f948df81032bb698dea0672fdcdf7 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 3 Jan 2018 16:42:05 -0500 Subject: [PATCH 0161/1195] Change registration of the qt eventloop after a similar change in IPython See ipython/ipython#10756 --- ipykernel/eventloops.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 5051c33b2..47b6d726d 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -96,7 +96,7 @@ def _loop_qt(app): app._in_event_loop = False -@register_integration('qt', 'qt4') +@register_integration('qt4') def loop_qt4(kernel): """Start a kernel with PyQt4 event loop integration.""" @@ -116,7 +116,7 @@ def loop_qt4_exit(kernel): kernel.app.exit() -@register_integration('qt5') +@register_integration('qt', 'qt5') def loop_qt5(kernel): """Start a kernel with PyQt5 event loop integration.""" os.environ['QT_API'] = 'pyqt5' From cee021e247240fb059926a16cce170c114799328 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 5 Jan 2018 17:28:10 +0100 Subject: [PATCH 0162/1195] pin tornado < 5 on travis and appveyor while we work out what's causing failures --- .travis.yml | 2 ++ appveyor.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index c68a67e36..295663be6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,8 @@ install: - | pip install --upgrade setuptools pip pip install --pre . + # temp fix: pin tornado < 5 + pip install 'tornado<5' pip install ipykernel[test] codecov - | if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" || "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then diff --git a/appveyor.yml b/appveyor.yml index fe9d25315..3812f5e75 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,6 +25,7 @@ install: pip install ipykernel[test] - cmd: | pip install matplotlib numpy + pip install "tornado<5" pip freeze - cmd: python -c "import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)" test_script: From dc54a6e8aa7db377f1a0c7f7e41a1f4c9a413c66 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 15 Jan 2018 19:14:04 -0800 Subject: [PATCH 0163/1195] unpin tornado --- .travis.yml | 2 -- appveyor.yml | 1 - 2 files changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 295663be6..c68a67e36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,8 +11,6 @@ install: - | pip install --upgrade setuptools pip pip install --pre . - # temp fix: pin tornado < 5 - pip install 'tornado<5' pip install ipykernel[test] codecov - | if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" || "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then diff --git a/appveyor.yml b/appveyor.yml index 3812f5e75..fe9d25315 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -25,7 +25,6 @@ install: pip install ipykernel[test] - cmd: | pip install matplotlib numpy - pip install "tornado<5" pip freeze - cmd: python -c "import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)" test_script: From 1bb6a1fb84c903d11ff80adbd048954b631dff3e Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 15 Jan 2018 19:49:10 -0800 Subject: [PATCH 0164/1195] only enter eventloop when there is one rather than calling enter_eventloop again when the eventloop is disabled (eventloop = None) --- ipykernel/kernelbase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 199af5c5e..22f4655b9 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -50,7 +50,8 @@ class Kernel(SingletonConfigurable): def _update_eventloop(self, change): """schedule call to eventloop from IOLoop""" loop = ioloop.IOLoop.current() - loop.add_callback(self.enter_eventloop) + if change.new is not None: + loop.add_callback(self.enter_eventloop) session = Instance(Session, allow_none=True) profile_dir = Instance('IPython.core.profiledir.ProfileDir', allow_none=True) From f5d5ffc27a4af446819513c80bf221177d162704 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 15 Jan 2018 19:50:15 -0800 Subject: [PATCH 0165/1195] run pre/post handler hooks around eventloop dispatch ensures signal handlers are properly restored after exiting eventloop --- ipykernel/kernelbase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 22f4655b9..cf75e108e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -257,7 +257,7 @@ def enter_eventloop(self): # which may be skipped by entering the eventloop stream.flush(zmq.POLLOUT) # restore default_int_handler - signal(SIGINT, default_int_handler) + self.pre_handler_hook() while self.eventloop is not None: try: self.eventloop(self) @@ -269,6 +269,7 @@ def enter_eventloop(self): # eventloop exited cleanly, this means we should stop (right?) self.eventloop = None break + self.post_handler_hook() self.log.info("exiting eventloop") def start(self): From 0caaa5523f760eb9be5130c779df09adfc38c369 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 17 Jan 2018 12:26:36 +0000 Subject: [PATCH 0166/1195] Add changelog for v 4.8 --- docs/changelog.rst | 15 +++++++++++++++ docs/conf.py | 3 +++ docs/requirements.txt | 1 + readthedocs.yml | 1 + 4 files changed, 20 insertions(+) create mode 100644 docs/requirements.txt diff --git a/docs/changelog.rst b/docs/changelog.rst index a5d950d77..6d4b5e258 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,21 @@ Changes in IPython kernel ========================= +4.8 +--- + +4.8.0 +***** + +`4.8.0 on GitHub `__ + +- Cleanly shutdown integrated event loops when shutting down the kernel. + (:ghpull:`290`) +- ``%gui qt`` now uses Qt 5 by default rather than Qt 4, following a similar + change in terminal IPython. (:ghpull:`293`) +- Fix event loop integration for :mod:`asyncio` when run with Tornado 5, + which uses asyncio where available. (:ghpull:`296`) + 4.7 --- diff --git a/docs/conf.py b/docs/conf.py index 4c0361199..d7a215ba3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,8 +33,11 @@ extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + 'sphinxcontrib_github_alt', ] +github_project_url = "https://github.com/ipython/ipykernel" + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..623e487ab --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +sphinxcontrib_github_alt diff --git a/readthedocs.yml b/readthedocs.yml index f8b3b417d..a2bc2646f 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -1,3 +1,4 @@ python: version: 3.5 pip_install: true +requirements_file: docs/requirements.txt From 0b875e67532621c958054c9db51d4cb9f6a53861 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 17 Jan 2018 12:27:03 +0000 Subject: [PATCH 0167/1195] Fix formatting to silence a Sphinx warning --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 6d4b5e258..40f675c63 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -53,8 +53,10 @@ Changes in IPython kernel - Add to API `DisplayPublisher.publish` two new fully backward-compatible keyword-args: + - `update: bool` - `transient: dict` + - Support new `transient` key in `display_data` messages spec for `publish`. For a display data message, `transient` contains data that shouldn't be persisted to files or documents. Add a `display_id` to this `transient` From 572e1fe7b3a89b966cd5676ed3deb7d46d7dfa66 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 17 Jan 2018 17:30:46 +0000 Subject: [PATCH 0168/1195] release 4.8.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 97a8e4a1b..df91ce140 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 8, 0, 'dev0') +version_info = (4, 8, 0) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 48682485958eb0978aa9ece37774e6d6a56b7eff Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 17 Jan 2018 17:38:20 +0000 Subject: [PATCH 0169/1195] Fix exclusion of built docs from sdist --- MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index ce535d5dd..90bc61e08 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -10,7 +10,7 @@ exclude docs/\#* graft examples # docs subdirs we want to skip -prune docs/build +prune docs/_build prune docs/gh-pages prune docs/dist From c094816a73fdc6ed51a49704487e2c30e298503e Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Wed, 17 Jan 2018 17:39:29 +0000 Subject: [PATCH 0170/1195] Back to development --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index df91ce140..2ae4c1b00 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 8, 0) +version_info = (4, 9, 0, 'dev0') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 85cbe6b86fbcad920abbd7801b5ba20d490d7705 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 22 Jan 2018 15:27:25 +0100 Subject: [PATCH 0171/1195] set ROUTER_HANDOVER where available workaround libzmq bug when reconnecting sockets with the same identity --- ipykernel/kernelapp.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index b67d15074..5ae5930e8 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -248,6 +248,14 @@ def init_sockets(self): self.control_port = self._bind_socket(self.control_socket, self.control_port) self.log.debug("control ROUTER Channel on port: %i" % self.control_port) + if hasattr(zmq, 'ROUTER_HANDOVER'): + # set router-handover to workaround zeromq reconnect problems + # in certain rare circumstances + # see ipython/ipykernel#270 and zeromq/libzmq#2892 + self.shell_socket.router_handover = \ + self.control_socket.router_handover = \ + self.stdin_socket.router_handover = 1 + self.init_iopub(context) def init_iopub(self, context): From 0eae593bde76257a0a4dffcd05c1718af85a2162 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 8 Feb 2018 13:19:15 +0100 Subject: [PATCH 0172/1195] avoid absolute paths in data_files absolute paths cause problems for installs from sdist --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 4b388859d..de0561a08 100644 --- a/setup.py +++ b/setup.py @@ -97,7 +97,8 @@ write_kernel_spec(dest, overrides={'argv': argv}) setup_args['data_files'] = [ - (pjoin('share', 'jupyter', 'kernels', KERNEL_NAME), glob(pjoin(dest, '*'))), + (pjoin('share', 'jupyter', 'kernels', KERNEL_NAME), + glob(pjoin('data_kernelspec', '*'))), ] extras_require = setuptools_args['extras_require'] = { From 91952e7110aff0ee687ee35527d15e362233364f Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 8 Feb 2018 17:59:16 +0100 Subject: [PATCH 0173/1195] changelog for 4.8.1 --- docs/changelog.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 40f675c63..d23d187d0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,17 @@ Changes in IPython kernel 4.8 --- +4.8.1 +***** + +`4.8.1 on GitHub `__ + +- set zmq.ROUTER_HANDOVER socket option when available + to workaround libzmq reconnect bug (:ghpull:`300`). +- Fix sdists including absolute paths for kernelspec files, + which prevented installation from sdist on Windows + (:ghpull:`306`). + 4.8.0 ***** From a3c406f197e779689648b50e22a7275887ad91bf Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 8 Feb 2018 17:59:22 +0100 Subject: [PATCH 0174/1195] 4.8.1 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 2ae4c1b00..c008bbae4 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 9, 0, 'dev0') +version_info = (4, 8, 1) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 2baf8160b23bb4a0678da2033099eff9423316ae Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 8 Feb 2018 18:02:15 +0100 Subject: [PATCH 0175/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index c008bbae4..b222d9d17 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 8, 1) +version_info = (4, 9, 0, 'dev') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 1ad4c0647c5ca5e2dfac0288dd2459d4d9ffbeba Mon Sep 17 00:00:00 2001 From: Min RK Date: Sat, 10 Feb 2018 19:51:29 +0100 Subject: [PATCH 0176/1195] ensure qt stream processing starts in a clean state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adding a listener on stream.FD won’t reliably wake the eventloop if there are already events waiting to be processed. We must check socket.EVENTS and process any waiting events before zmq.FD will be written-to again. --- ipykernel/eventloops.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 47b6d726d..ad783b141 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -41,6 +41,11 @@ def process_stream_events(): fd = stream.getsockopt(zmq.FD) notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) notifier.activated.connect(process_stream_events) + # there may already be events waiting, + # which won't wake zmq's edge-triggered FD + # make sure that waiting events are processed immediately + # so we start in a clean state + process_stream_events() # mapping of keys to loop functions loop_map = { From 574bd818a9b204f01aeafeadfa0c7792c1906e96 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 14 Feb 2018 13:02:00 +0100 Subject: [PATCH 0177/1195] more detailed comment about zmq.FD --- ipykernel/eventloops.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index ad783b141..68db43997 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -41,11 +41,12 @@ def process_stream_events(): fd = stream.getsockopt(zmq.FD) notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) notifier.activated.connect(process_stream_events) - # there may already be events waiting, - # which won't wake zmq's edge-triggered FD - # make sure that waiting events are processed immediately - # so we start in a clean state process_stream_events() + # there may already be unprocessed events waiting. + # these events will not wake zmq's edge-triggered FD + # since edge-triggered notification only occurs on new i/o activity. + # process all the waiting events immediately + # so we start in a clean state ensuring that any new i/o events will notify. # mapping of keys to loop functions loop_map = { From d4755f690e45b3e2a492dcbe70176c1895b1f6fe Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 14 Feb 2018 13:03:48 +0100 Subject: [PATCH 0178/1195] schedule first process_events on the eventoloop with a zero-timeout single-shot timer (closest I could get to .call_soon()) rather than calling it immediately, which might block in unexpected ways by processing events while we are still hooking things up. --- ipykernel/eventloops.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 68db43997..68b0b5cfb 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -41,12 +41,17 @@ def process_stream_events(): fd = stream.getsockopt(zmq.FD) notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) notifier.activated.connect(process_stream_events) - process_stream_events() # there may already be unprocessed events waiting. # these events will not wake zmq's edge-triggered FD # since edge-triggered notification only occurs on new i/o activity. # process all the waiting events immediately # so we start in a clean state ensuring that any new i/o events will notify. + # schedule first call on the eventloop as soon as it's running, + # so we don't block here processing events + timer = QtCore.QTimer(kernel.app) + timer.setSingleShot(True) + timer.timeout.connect(process_stream_events) + timer.start(0) # mapping of keys to loop functions loop_map = { From 3558b5762348d49005bef09507e82ffcb833f472 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 19 Feb 2018 17:18:19 +0100 Subject: [PATCH 0179/1195] changelog for 4.8.2 --- docs/changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index d23d187d0..41ff78d4f 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,13 @@ Changes in IPython kernel 4.8 --- +4.8.2 +***** + +`4.8.2 on GitHub `__ + +- Fix compatibility issue with qt eventloop and pyzmq 17 (:ghpull:`307`). + 4.8.1 ***** From a7bed3e5f8ead279866f3d241ae4cf444dfd8e9b Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 19 Feb 2018 17:18:33 +0100 Subject: [PATCH 0180/1195] 4.8.2 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index b222d9d17..7922e071d 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 9, 0, 'dev') +version_info = (4, 8, 2) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From c9450dc2f3ab9a81d9acede3963c2ecb80fd18d0 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 19 Feb 2018 17:19:52 +0100 Subject: [PATCH 0181/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7922e071d..b222d9d17 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 8, 2) +version_info = (4, 9, 0, 'dev') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 01dcc763b1dff7b35464774473f9b13c8e6e4845 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 8 Mar 2018 13:51:07 -0800 Subject: [PATCH 0182/1195] kernelapp: Flush stdout/stderr before replacing --- ipykernel/kernelapp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 5ae5930e8..c729c5369 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -322,7 +322,9 @@ def init_io(self): """Redirect input streams and set a display hook.""" if self.outstream_class: outstream_factory = import_item(str(self.outstream_class)) + sys.stdout.flush() sys.stdout = outstream_factory(self.session, self.iopub_thread, u'stdout') + sys.stderr.flush() sys.stderr = outstream_factory(self.session, self.iopub_thread, u'stderr') if self.displayhook_class: displayhook_factory = import_item(str(self.displayhook_class)) From b2de6f03c40c1a3748c5bb25a0b40019319415f4 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sat, 31 Mar 2018 18:19:13 +0200 Subject: [PATCH 0183/1195] Override writable method on OutStream Closes jupyter/notebook#3349 --- ipykernel/iostream.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 0d11d9e57..b1a62557a 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -390,6 +390,9 @@ def writelines(self, sequence): for string in sequence: self.write(string) + def writable(self): + return True + def _flush_buffer(self): """clear the current buffer and return the current buffer data. From 7a1b8747fa3589333c47d1127106c590275b72f8 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sun, 1 Apr 2018 20:29:27 +0200 Subject: [PATCH 0184/1195] Don't try to upgrade pip on Appveyor (it's causing errors) --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index fe9d25315..bafd5abb6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,7 +18,7 @@ init: - cmd: set PATH=%python%;%python%\scripts;%PATH% install: - cmd: | - pip install --upgrade pip wheel + pip install --upgrade wheel pip --version - cmd: | pip install --pre -e . From 573d7ba80be839f1caef86b6d53fcee323c6c447 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Mon, 2 Apr 2018 11:12:47 +0200 Subject: [PATCH 0185/1195] Upgrade pip on appveyor using python -m --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index bafd5abb6..61bca330b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,7 +18,7 @@ init: - cmd: set PATH=%python%;%python%\scripts;%PATH% install: - cmd: | - pip install --upgrade wheel + python -m pip install --upgrade pip wheel pip --version - cmd: | pip install --pre -e . From 98d6ae0ea65047b1a9cff3802e539e60727b89db Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 11 Apr 2018 10:17:18 -0700 Subject: [PATCH 0186/1195] include the pytest cache in the gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e9e657075..e483a4411 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ __pycache__ .coverage data_kernelspec +.pytest_cache From 70c6441688927da456ec6f7fba432ee88a5da63a Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 12 Mar 2018 18:39:14 -0700 Subject: [PATCH 0187/1195] kernelapp: Preserve stdout and stderr in kernel --- ipykernel/iostream.py | 26 +++++++++++++++++++++++++- ipykernel/kernelapp.py | 13 +++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index b1a62557a..7dc519a40 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -276,7 +276,7 @@ class OutStream(TextIOBase): topic = None encoding = 'UTF-8' - def __init__(self, session, pub_thread, name, pipe=None): + def __init__(self, session, pub_thread, name, pipe=None, echo=None): if pipe is not None: warnings.warn("pipe argument to OutStream is deprecated and ignored", DeprecationWarning) @@ -296,6 +296,13 @@ def __init__(self, session, pub_thread, name, pipe=None): self._flush_pending = False self._io_loop = pub_thread.io_loop self._new_buffer() + self.echo = None + + if echo: + if hasattr(echo, 'read') and hasattr(echo, 'write'): + self.echo = echo + else: + raise ValueError("echo argument must be a file like object") def _is_master_process(self): return os.getpid() == self._master_pid @@ -353,6 +360,15 @@ def _flush(self): unless the thread has been destroyed (e.g. forked subprocess). """ self._flush_pending = False + + if self.echo is not None: + try: + self.echo.flush() + except OSError as e: + if self.echo is not sys.__stderr__: + print("Flush failed: {}".format(e), + file=sys.__stderr__) + data = self._flush_buffer() if data: # FIXME: this disables Session's fork-safe check, @@ -364,6 +380,14 @@ def _flush(self): parent=self.parent_header, ident=self.topic) def write(self, string): + if self.echo is not None: + try: + self.echo.write(string) + except OSError as e: + if self.echo is not sys.__stderr__: + print("Write failed: {}".format(e), + file=sys.__stderr__) + if self.pub_thread is None: raise ValueError('I/O operation on closed file') else: diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index c729c5369..e0d729e2b 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -139,6 +139,7 @@ def abs_connection_file(self): # streams, etc. no_stdout = Bool(False, help="redirect stdout to the null device").tag(config=True) no_stderr = Bool(False, help="redirect stderr to the null device").tag(config=True) + quiet = Bool(True, help="Only send stdout/stderr to output stream").tag(config=True) outstream_class = DottedObjectName('ipykernel.iostream.OutStream', help="The importstring for the OutStream factory").tag(config=True) displayhook_class = DottedObjectName('ipykernel.displayhook.ZMQDisplayHook', @@ -323,9 +324,17 @@ def init_io(self): if self.outstream_class: outstream_factory = import_item(str(self.outstream_class)) sys.stdout.flush() - sys.stdout = outstream_factory(self.session, self.iopub_thread, u'stdout') + + e_stdout = None if self.quiet else sys.__stdout__ + e_stderr = None if self.quiet else sys.__stderr__ + + sys.stdout = outstream_factory(self.session, self.iopub_thread, + u'stdout', + echo=e_stdout) sys.stderr.flush() - sys.stderr = outstream_factory(self.session, self.iopub_thread, u'stderr') + sys.stderr = outstream_factory(self.session, self.iopub_thread, + u'stderr', + echo=e_stderr) if self.displayhook_class: displayhook_factory = import_item(str(self.displayhook_class)) self.displayhook = displayhook_factory(self.session, self.iopub_socket) From 4942afe71d8b475c16574e33878f8955325fab78 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 1 May 2018 13:15:20 -0700 Subject: [PATCH 0188/1195] update log.warn (deprecated) to log.warning --- ipykernel/comm/manager.py | 2 +- ipykernel/kernelbase.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index c5ac1deb7..358eb4092 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -66,7 +66,7 @@ def get_comm(self, comm_id): try: return self.comms[comm_id] except KeyError: - self.log.warn("No such comm: %s", comm_id) + self.log.warning("No such comm: %s", comm_id) if self.log.isEnabledFor(logging.DEBUG): # don't create the list of keys if debug messages aren't enabled self.log.debug("Current comms: %s", list(self.comms.keys())) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index cf75e108e..72547fa30 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -225,7 +225,7 @@ def dispatch_shell(self, stream, msg): handler = self.shell_handlers.get(msg_type, None) if handler is None: - self.log.warn("Unknown message type: %r", msg_type) + self.log.warning("Unknown message type: %r", msg_type) else: self.log.debug("%s: %s", msg_type, msg) self.pre_handler_hook() @@ -563,7 +563,7 @@ def do_is_complete(self, code): #--------------------------------------------------------------------------- def apply_request(self, stream, ident, parent): - self.log.warn("""apply_request is deprecated in kernel_base, moving to ipyparallel.""") + self.log.warning("apply_request is deprecated in kernel_base, moving to ipyparallel.") try: content = parent[u'content'] bufs = parent[u'buffers'] @@ -595,7 +595,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): def abort_request(self, stream, ident, parent): """abort a specific msg by id""" - self.log.warn("abort_request is deprecated in kernel_base. It os only part of IPython parallel") + self.log.warning("abort_request is deprecated in kernel_base. It os only part of IPython parallel") msg_ids = parent['content'].get('msg_ids', None) if isinstance(msg_ids, string_types): msg_ids = [msg_ids] @@ -611,7 +611,7 @@ def abort_request(self, stream, ident, parent): def clear_request(self, stream, idents, parent): """Clear our namespace.""" - self.log.warn("clear_request is deprecated in kernel_base. It os only part of IPython parallel") + self.log.warning("clear_request is deprecated in kernel_base. It os only part of IPython parallel") content = self.do_clear() self.session.send(stream, 'clear_reply', ident=idents, parent=parent, content = content) @@ -728,7 +728,7 @@ def _input_request(self, prompt, ident, parent, password=False): try: ident, reply = self.session.recv(self.stdin_socket, 0) except Exception: - self.log.warn("Invalid Message:", exc_info=True) + self.log.warning("Invalid Message:", exc_info=True) except KeyboardInterrupt: # re-raise KeyboardInterrupt, to truncate traceback raise KeyboardInterrupt From 56cc27da4e9d0859f0b9c904abb2f3e18c9d41d5 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 12 May 2018 15:57:59 -0700 Subject: [PATCH 0189/1195] start testing on 3.7 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c68a67e36..59e20d4ed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,7 @@ language: python python: - "nightly" + - "3.7-dev" - 3.6 - 3.5 - 3.4 From 726b95325c9d729dd6db9c236d962b4803c79a16 Mon Sep 17 00:00:00 2001 From: Todd Date: Fri, 8 Jun 2018 16:57:23 -0400 Subject: [PATCH 0190/1195] Include LICENSE file in wheels The license requires that all copies of the software include the license. This makes sure the license is included in the wheels. See the wheel documentation [here](https://wheel.readthedocs.io/en/stable/#including-the-license-in-the-generated-wheel-file) for more information. --- setup.cfg | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.cfg b/setup.cfg index e627d292f..05f7972bf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,9 @@ [bdist_wheel] universal=0 +[metadata] +license_file = COPYING.md + [nosetests] warningfilters= default |.* |DeprecationWarning |ipykernel.* error |.*invalid.* |DeprecationWarning |matplotlib.* From adadd1c5c3ba9cd9499686121b46046a87690c9d Mon Sep 17 00:00:00 2001 From: telamonian Date: Tue, 28 Aug 2018 17:27:03 -0400 Subject: [PATCH 0191/1195] Adds metadata to help display matplotlib figures legibly --- ipykernel/pylab/backend_inline.py | 37 ++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index 739165e39..273177ad6 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -7,6 +7,7 @@ import matplotlib from matplotlib.backends.backend_agg import new_figure_manager, FigureCanvasAgg # analysis: ignore +from matplotlib import colors from matplotlib._pylab_helpers import Gcf from IPython.core.getipython import get_ipython @@ -33,7 +34,10 @@ def show(close=None, block=None): close = InlineBackend.instance().close_figures try: for figure_manager in Gcf.get_all_fig_managers(): - display(figure_manager.canvas.figure) + display( + figure_manager.canvas.figure, + metadata=_fetch_figure_metadata(figure_manager.canvas.figure) + ) finally: show._to_draw = [] # only call close('all') if any to close @@ -72,7 +76,7 @@ def draw_if_interactive(): if not hasattr(fig, 'show'): # Queue up `fig` for display - fig.show = lambda *a: display(fig) + fig.show = lambda *a: display(fig, metadata=_fetch_figure_metadata(fig)) # If matplotlib was manually set to non-interactive mode, this function # should be a no-op (otherwise we'll generate duplicate plots, since a user @@ -124,7 +128,7 @@ def flush_figures(): active = set([fm.canvas.figure for fm in Gcf.get_all_fig_managers()]) for fig in [ fig for fig in show._to_draw if fig in active ]: try: - display(fig) + display(fig, metadata=_fetch_figure_metadata(fig)) except Exception as e: # safely show traceback if in IPython, else raise ip = get_ipython() @@ -163,3 +167,30 @@ def configure_once(*args): ip.events.register('post_run_cell', configure_once) _enable_matplotlib_integration() + +def _fetch_figure_metadata(fig): + """Get some metadata to help with displaying a figure.""" + # determine if a background is needed for legibility + if _is_transparent(fig.get_facecolor()): + # the background is transparent + ticksLight = _is_light([label.get_color() + for axes in fig.axes + for axis in (axes.xaxis, axes.yaxis) + for label in axis.get_ticklabels()]) + if ticksLight.size and (ticksLight == ticksLight[0]).all(): + # there are one or more tick labels, all with the same lightness + return {'needs_background': 'dark' if ticksLight[0] else 'light'} + + return None + +def _is_light(color): + """Determines if a color (or each of a sequence of colors) is light (as + opposed to dark). Based on ITU BT.601 luminance formula (see + https://stackoverflow.com/a/596241).""" + rgbaArr = colors.to_rgba_array(color) + return rgbaArr[:,:3].dot((.299, .587, .114)) > .5 + +def _is_transparent(color): + """Determine transparency from alpha.""" + rgba = colors.to_rgba(color) + return rgba[3] < .5 From 4c62d38cbbb7b4241b61888a52254b3e0492b1a6 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 29 Aug 2018 05:42:32 -0500 Subject: [PATCH 0192/1195] Switch to 3.4+ --- .travis.yml | 1 - setup.py | 8 +++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 59e20d4ed..3b3c66c0a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ python: - 3.6 - 3.5 - 3.4 - - 3.3 - 2.7 sudo: false install: diff --git a/setup.py b/setup.py index de0561a08..d456acff7 100644 --- a/setup.py +++ b/setup.py @@ -16,8 +16,8 @@ import sys v = sys.version_info -if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,3)): - error = "ERROR: %s requires Python version 2.7 or 3.3 or above." % name +if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,4)): + error = "ERROR: %s requires Python version 2.7 or 3.4 or above." % name print(error, file=sys.stderr) sys.exit(1) @@ -103,10 +103,8 @@ extras_require = setuptools_args['extras_require'] = { 'test:python_version=="2.7"': ['mock'], - # pytest 3.3 doesn't work on Python 3.3 - 'test:python_version=="3.3"': ['pytest==3.2.*'], - 'test:python_version!="3.3"': ['pytest>=3.2'], 'test': [ + 'pytest', 'pytest-cov', 'nose', # nose because there are still a few nose.tools imports hanging around ], From c4bb66a7073fd9acedaee12d26fd45be39c14653 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 29 Aug 2018 06:21:18 -0500 Subject: [PATCH 0193/1195] Add python_requires --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index d456acff7..cd99584dd 100644 --- a/setup.py +++ b/setup.py @@ -101,6 +101,9 @@ glob(pjoin('data_kernelspec', '*'))), ] + +setuptools_args['python_requires'] = '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' + extras_require = setuptools_args['extras_require'] = { 'test:python_version=="2.7"': ['mock'], 'test': [ From 3d0f052c4d3d5a26aa72e2f2e93a7ef6ec1290a2 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 29 Aug 2018 06:39:06 -0500 Subject: [PATCH 0194/1195] Update for 4.9 release --- docs/changelog.rst | 14 ++++++++++++++ ipykernel/_version.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 41ff78d4f..79a415a0e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,20 @@ Changes in IPython kernel ========================= +4.9 +--- + +4.9.0 +***** + +`4.9.0 on GitHub `__ + +- Flush stdout/stderr in KernelApp before replacing (:ghpull:`314`) +- Allow preserving stdout and stderr in KernelApp (:ghpull:`315`) +- Override writable method on OutStream (:ghpull:`316`) +- Add metadata to help display matplotlib figures legibly (:ghpull:`336`) + + 4.8 --- diff --git a/ipykernel/_version.py b/ipykernel/_version.py index b222d9d17..4ffba6a93 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 9, 0, 'dev') +version_info = (4, 9, 0) __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 93397d6dce8d3b5366e204951972c76926953692 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 29 Aug 2018 06:39:55 -0500 Subject: [PATCH 0195/1195] Add note about py33 --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 79a415a0e..38e2048c8 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,7 @@ Changes in IPython kernel `4.9.0 on GitHub `__ +- Python 3.3 is no longer supported (:ghpull:`336`) - Flush stdout/stderr in KernelApp before replacing (:ghpull:`314`) - Allow preserving stdout and stderr in KernelApp (:ghpull:`315`) - Override writable method on OutStream (:ghpull:`316`) From c1ce285dd93670701dc12f590932a47508e5e41c Mon Sep 17 00:00:00 2001 From: Min RK Date: Sat, 1 Sep 2018 10:05:21 +0200 Subject: [PATCH 0196/1195] unconditional setuptools in setup.py - simplifies logic - add bdist_egg_disabled to disable implicit eggs - add setup_requires to ensure we have what we need for setup.py building kernelspec from sdist --- setup.py | 92 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 50 insertions(+), 42 deletions(-) diff --git a/setup.py b/setup.py index cd99584dd..aef1b76d0 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,19 @@ import os import shutil -from distutils.core import setup +from setuptools import setup +from setuptools.command.bdist_egg import bdist_egg + + +class bdist_egg_disabled(bdist_egg): + """Disabled version of bdist_egg + + Prevents setup.py install from performing setuptools' default easy_install, + which it should never ever do. + """ + def run(self): + sys.exit("Aborting implicit building of eggs. Use `pip install .` to install from source.") + pjoin = os.path.join here = os.path.abspath(os.path.dirname(__file__)) @@ -52,20 +64,39 @@ setup_args = dict( - name = name, - version = version_ns['__version__'], - scripts = glob(pjoin('scripts', '*')), - packages = packages, - py_modules = ['ipykernel_launcher'], - package_data = package_data, - description = "IPython Kernel for Jupyter", - author = 'IPython Development Team', - author_email = 'ipython-dev@scipy.org', - url = 'http://ipython.org', - license = 'BSD', - platforms = "Linux, Mac OS X, Windows", - keywords = ['Interactive', 'Interpreter', 'Shell', 'Web'], - classifiers = [ + name=name, + version=version_ns['__version__'], + cmdclass={ + 'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled, + }, + scripts=glob(pjoin('scripts', '*')), + packages=packages, + py_modules=['ipykernel_launcher'], + package_data=package_data, + description="IPython Kernel for Jupyter", + author='IPython Development Team', + author_email='ipython-dev@scipy.org', + url='https://ipython.org', + license='BSD', + platforms="Linux, Mac OS X, Windows", + keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + setup_requires=['ipython', 'jupyter_core>=4.2'], + install_requires=[ + 'ipython>=4.0.0', + 'traitlets>=4.1.0', + 'jupyter_client', + 'tornado>=4.0', + ], + extras_require={ + 'test:python_version=="2.7"': ['mock'], + 'test': [ + 'pytest', + 'pytest-cov', + 'nose', # nose because there are still a few nose.tools imports hanging around + ], + }, + classifiers=[ 'Intended Audience :: Developers', 'Intended Audience :: System Administrators', 'Intended Audience :: Science/Research', @@ -76,17 +107,6 @@ ], ) -if 'develop' in sys.argv or any(a.startswith('bdist') for a in sys.argv): - import setuptools - -setuptools_args = {} -install_requires = setuptools_args['install_requires'] = [ - 'ipython>=4.0.0', - 'traitlets>=4.1.0', - 'jupyter_client', - 'tornado>=4.0', -] - if any(a.startswith(('bdist', 'build', 'install')) for a in sys.argv): from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME @@ -97,24 +117,12 @@ write_kernel_spec(dest, overrides={'argv': argv}) setup_args['data_files'] = [ - (pjoin('share', 'jupyter', 'kernels', KERNEL_NAME), - glob(pjoin('data_kernelspec', '*'))), + ( + pjoin('share', 'jupyter', 'kernels', KERNEL_NAME), + glob(pjoin('data_kernelspec', '*')), + ) ] -setuptools_args['python_requires'] = '>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*' - -extras_require = setuptools_args['extras_require'] = { - 'test:python_version=="2.7"': ['mock'], - 'test': [ - 'pytest', - 'pytest-cov', - 'nose', # nose because there are still a few nose.tools imports hanging around - ], -} - -if 'setuptools' in sys.modules: - setup_args.update(setuptools_args) - if __name__ == '__main__': setup(**setup_args) From 0c54f69c69d3aeccd0fe857a91f1a75636ce7be0 Mon Sep 17 00:00:00 2001 From: Min RK Date: Sat, 1 Sep 2018 10:13:05 +0200 Subject: [PATCH 0197/1195] add release process to CONTRIBUTING.md --- CONTRIBUTING.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b73dbf31..70def38d9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,60 @@ # Contributing -We follow the [IPython Contributing Guide](https://github.com/ipython/ipython/blob/master/CONTRIBUTING.md). +Welcome! + +For contributing tips, follow the [Jupyter Contributing Guide](https://jupyter.readthedocs.io/en/latest/contributor/content-contributor.html). +Please make sure to follow the [Jupyter Code of Conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md). + +## Installing ipykernel for development + +ipykernel is a pure Python package, so setting up for development is the same as most other Python projects: + +```bash +# clone the repo +git clone https://github.com/ipython/ipykernel +cd ipykernel +# do a 'development' or 'editable' install with pip: +pip install -e . +``` + +## Releasing ipykernel + +Releasing ipykernel is *almost* standard for a Python package: + +- set version for release +- make and publish tag +- publish release to PyPI +- set version back to development + +The one extra step for ipykernel is that we need to make separate wheels for Python 2 and 3 +because the bundled kernelspec has different contents for Python 2 and 3. + +The full release process is available below: + +```bash +# make sure version is set in ipykernel/_version.py +VERSION="4.9.0" +# commit the version and make a release tag +git add ipykernel/_version.py +git commit -m "release $VERSION" +git tag -am "release $VERSION" $VERSION + +# push the changes to the repo +git push +git push --tags + +# publish the release to PyPI +# note the extra `python2 setup.py bdist_wheel` for creating +# the wheel for Python 2 +pip install --upgrade twine +git clean -xfd +python3 setup.py sdist bdist_wheel +python2 setup.py bdist_wheel # the extra step! +twine upload dist/* + +# set the version back to '.dev' in ipykernel/_version.py +# e.g. 4.10.0.dev if we just released 4.9.0 +git add ipykernel/_version.py +git commit -m "back to dev" +git push +``` From f9c2dececb62e8e858a53e1554138016d248d4ab Mon Sep 17 00:00:00 2001 From: Min RK Date: Sat, 1 Sep 2018 10:19:49 +0200 Subject: [PATCH 0198/1195] add non-empty long description --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index aef1b76d0..d6ca1fc26 100644 --- a/setup.py +++ b/setup.py @@ -78,6 +78,7 @@ def run(self): author_email='ipython-dev@scipy.org', url='https://ipython.org', license='BSD', + long_description="The IPython kernel for Jupyter", platforms="Linux, Mac OS X, Windows", keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', From 70dab6d00db26d262b9099d77217c067aa425238 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 3 Sep 2018 10:05:29 +0200 Subject: [PATCH 0199/1195] update setuptools on appveyor maybe this is why it's installing an incompatible IPython? --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 61bca330b..7cdcc34ef 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,7 +18,7 @@ init: - cmd: set PATH=%python%;%python%\scripts;%PATH% install: - cmd: | - python -m pip install --upgrade pip wheel + python -m pip install --upgrade setuptools pip wheel pip --version - cmd: | pip install --pre -e . From 153c0fef86944f267a5e7b998dc6d8acab3af1e1 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 4 Sep 2018 14:06:32 +0200 Subject: [PATCH 0200/1195] move setup_requires to pyproject.toml since setup_requires just doesn't work --- MANIFEST.in | 1 + pyproject.toml | 8 ++++++++ setup.py | 1 - 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 pyproject.toml diff --git a/MANIFEST.in b/MANIFEST.in index 90bc61e08..7bcad2d18 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ include COPYING.md include CONTRIBUTING.md include README.md +include pyproject.toml # Documentation graft docs diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..e220b587e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,8 @@ +[build-system] +requires=[ + "setuptools", + "wheel", + "ipython>=5", + "jupyter_core>=4.2", + "jupyter_client", +] diff --git a/setup.py b/setup.py index d6ca1fc26..46a3c05ea 100644 --- a/setup.py +++ b/setup.py @@ -82,7 +82,6 @@ def run(self): platforms="Linux, Mac OS X, Windows", keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', - setup_requires=['ipython', 'jupyter_core>=4.2'], install_requires=[ 'ipython>=4.0.0', 'traitlets>=4.1.0', From d8abd56f327f0589958216964a5b03698e878a2c Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 24 May 2018 16:27:45 +0200 Subject: [PATCH 0201/1195] async support allow handlers to return anything acceptable to `gen.maybe_future` this should include tornado/asyncio coroutines and Futures --- ipykernel/ipkernel.py | 15 ++++++++++++--- ipykernel/kernelbase.py | 39 ++++++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 6304131f9..129d16326 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -6,13 +6,13 @@ from IPython.core import release from ipython_genutils.py3compat import builtin_mod, PY3, unicode_type, safe_unicode from IPython.utils.tokenutil import token_at_cursor, line_at_cursor +from tornado import gen from traitlets import Instance, Type, Any, List, Bool from .comm import CommManager from .kernelbase import Kernel as KernelBase from .zmqshell import ZMQInteractiveShell - try: from IPython.core.completer import rectify_completions as _rectify_completions, provisionalcompleter as _provisionalcompleter _use_experimental_60_completion = True @@ -193,10 +193,11 @@ def execution_count(self): @execution_count.setter def execution_count(self, value): - # Ignore the incrememnting done by KernelBase, in favour of our shell's + # Ignore the incrementing done by KernelBase, in favour of our shell's # execution counter. pass + @gen.coroutine def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): shell = self.shell # we'll need this a lot here @@ -204,8 +205,16 @@ def do_execute(self, code, silent, store_history=True, self._forward_input(allow_stdin) reply_content = {} + if hasattr(shell, 'run_cell_async'): + run_cell = shell.run_cell_async + else: + # older IPython, + # use blocking run_cell and wrap it in coroutine + @gen.coroutine + def run_cell(*args, **kwargs): + return shell.run_cell(*args, **kwargs) try: - res = shell.run_cell(code, store_history=store_history, silent=silent) + res = yield run_cell(code, store_history=store_history, silent=silent) finally: self._restore_input() diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 72547fa30..b8a524db9 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -22,6 +22,7 @@ import zmq from tornado import ioloop +from tornado import gen from zmq.eventloop.zmqstream import ZMQStream from traitlets.config.configurable import SingletonConfigurable @@ -145,6 +146,7 @@ def __init__(self, **kwargs): for msg_type in self.control_msg_types: self.control_handlers[msg_type] = getattr(self, msg_type) + @gen.coroutine def dispatch_control(self, msg): """dispatch control requests""" idents,msg = self.session.feed_identities(msg, copy=False) @@ -168,7 +170,7 @@ def dispatch_control(self, msg): self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type) else: try: - handler(self.control_stream, idents, msg) + yield gen.maybe_future(handler(self.control_stream, idents, msg)) except Exception: self.log.error("Exception in control handler:", exc_info=True) @@ -195,6 +197,7 @@ def should_handle(self, stream, msg, idents): return False return True + @gen.coroutine def dispatch_shell(self, stream, msg): """dispatch shell requests""" # flush control requests first @@ -230,7 +233,7 @@ def dispatch_shell(self, stream, msg): self.log.debug("%s: %s", msg_type, msg) self.pre_handler_hook() try: - handler(stream, idents, msg) + yield gen.maybe_future(handler(stream, idents, msg)) except Exception: self.log.error("Exception in message handler:", exc_info=True) finally: @@ -370,6 +373,7 @@ def finish_metadata(self, parent, metadata, reply_content): """ return metadata + @gen.coroutine def execute_request(self, stream, ident, parent): """handle an execute_request""" @@ -395,8 +399,12 @@ def execute_request(self, stream, ident, parent): self.execution_count += 1 self._publish_execute_input(code, parent, self.execution_count) - reply_content = self.do_execute(code, silent, store_history, - user_expressions, allow_stdin) + reply_content = yield gen.maybe_future( + self.do_execute( + code, silent, store_history, + user_expressions, allow_stdin, + ) + ) # Flush output before sending the reply. sys.stdout.flush() @@ -426,12 +434,13 @@ def do_execute(self, code, silent, store_history=True, """ raise NotImplementedError + @gen.coroutine def complete_request(self, stream, ident, parent): content = parent['content'] code = content['code'] cursor_pos = content['cursor_pos'] - matches = self.do_complete(code, cursor_pos) + matches = yield gen.maybe_future(self.do_complete(code, cursor_pos)) matches = json_clean(matches) completion_msg = self.session.send(stream, 'complete_reply', matches, parent, ident) @@ -445,11 +454,16 @@ def do_complete(self, code, cursor_pos): 'metadata' : {}, 'status' : 'ok'} + @gen.coroutine def inspect_request(self, stream, ident, parent): content = parent['content'] - reply_content = self.do_inspect(content['code'], content['cursor_pos'], - content.get('detail_level', 0)) + reply_content = yield gen.maybe_future( + self.do_inspect( + content['code'], content['cursor_pos'], + content.get('detail_level', 0), + ) + ) # Before we send this object over, we scrub it for JSON usage reply_content = json_clean(reply_content) msg = self.session.send(stream, 'inspect_reply', @@ -461,10 +475,11 @@ def do_inspect(self, code, cursor_pos, detail_level=0): """ return {'status': 'ok', 'data': {}, 'metadata': {}, 'found': False} + @gen.coroutine def history_request(self, stream, ident, parent): content = parent['content'] - reply_content = self.do_history(**content) + reply_content = yield gen.maybe_future(self.do_history(**content)) reply_content = json_clean(reply_content) msg = self.session.send(stream, 'history_reply', @@ -523,8 +538,9 @@ def comm_info_request(self, stream, ident, parent): reply_content, parent, ident) self.log.debug("%s", msg) + @gen.coroutine def shutdown_request(self, stream, ident, parent): - content = self.do_shutdown(parent['content']['restart']) + content = yield gen.maybe_future(self.do_shutdown(parent['content']['restart'])) self.session.send(stream, u'shutdown_reply', content, parent, ident=ident) # same content, but different msg_id for broadcasting on IOPub self._shutdown_message = self.session.msg(u'shutdown_reply', @@ -542,14 +558,15 @@ def do_shutdown(self, restart): """ return {'status': 'ok', 'restart': restart} + @gen.coroutine def is_complete_request(self, stream, ident, parent): content = parent['content'] code = content['code'] - reply_content = self.do_is_complete(code) + reply_content = yield gen.maybe_future(self.do_is_complete(code)) reply_content = json_clean(reply_content) reply_msg = self.session.send(stream, 'is_complete_reply', - reply_content, parent, ident) + reply_content, parent, ident) self.log.debug("%s", reply_msg) def do_is_complete(self, code): From 91c780f67caa48e94351633ce8d88ee3cb1eb489 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 24 May 2018 17:10:48 +0200 Subject: [PATCH 0202/1195] require Python 3.4 for asyncio always use setuptools in setup --- .travis.yml | 3 +-- setup.py | 11 +++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3b3c66c0a..8cb89d14b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ python: - 3.6 - 3.5 - 3.4 - - 2.7 sudo: false install: - | @@ -13,7 +12,7 @@ install: pip install --pre . pip install ipykernel[test] codecov - | - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" || "$TRAVIS_PYTHON_VERSION" == "2.7" ]]; then + if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then pip install matplotlib fi - pip freeze diff --git a/setup.py b/setup.py index 46a3c05ea..f76807d4d 100644 --- a/setup.py +++ b/setup.py @@ -16,13 +16,11 @@ import sys v = sys.version_info -if v[:2] < (2,7) or (v[0] >= 3 and v[:2] < (3,4)): - error = "ERROR: %s requires Python version 2.7 or 3.4 or above." % name +if v[:2] < (3, 4): + error = "ERROR: %s requires Python version 3.4 or above." % name print(error, file=sys.stderr) sys.exit(1) -PY3 = (sys.version_info[0] >= 3) - #----------------------------------------------------------------------------- # get on with it #----------------------------------------------------------------------------- @@ -81,12 +79,12 @@ def run(self): long_description="The IPython kernel for Jupyter", platforms="Linux, Mac OS X, Windows", keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], - python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', + python_requires='>=3.4', install_requires=[ 'ipython>=4.0.0', 'traitlets>=4.1.0', 'jupyter_client', - 'tornado>=4.0', + 'tornado>=4.2', ], extras_require={ 'test:python_version=="2.7"': ['mock'], @@ -107,6 +105,7 @@ def run(self): ], ) + if any(a.startswith(('bdist', 'build', 'install')) for a in sys.argv): from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME From 981b908be3bb9cdae479570c925ee8d41492e178 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 24 May 2018 17:13:43 +0200 Subject: [PATCH 0203/1195] bump major version due to new minimum Python version --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 4ffba6a93..21c09a70c 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (4, 9, 0) +version_info = (5, 0, 0, 'dev') __version__ = '.'.join(map(str, version_info)) kernel_protocol_version_info = (5, 1) From 52de81ddbe3b57d7d6dfe7e868adaf0493b0462f Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 24 May 2018 17:15:11 +0200 Subject: [PATCH 0204/1195] drop py27 on appveyor --- appveyor.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 7cdcc34ef..e11ae0242 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,8 +6,6 @@ clone_depth: 1 environment: matrix: - - python: "C:/Python27-x64" - - python: "C:/Python27" - python: "C:/Python36-x64" - python: "C:/Python36" From 77848a8a88b57ecc15adcdfa5c2e73548f28ff4b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 13 Aug 2018 10:45:03 -0700 Subject: [PATCH 0205/1195] update compat IPython 7.0+ --- ipykernel/kernelapp.py | 4 ++-- ipykernel/kernelbase.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index e0d729e2b..c1446865d 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -302,9 +302,9 @@ def log_connection_info(self): # also raw print to the terminal if no parent_handle (`ipython kernel`) # unless log-level is CRITICAL (--quiet) if not self.parent_handle and self.log_level < logging.CRITICAL: - io.rprint(_ctrl_c_message) + io.raw_print(_ctrl_c_message) for line in lines: - io.rprint(line) + io.raw_print(line) self.ports = dict(shell=self.shell_port, iopub=self.iopub_port, stdin=self.stdin_port, hb=self.hb_port, diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index b8a524db9..6898e05b7 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -764,7 +764,6 @@ def _input_request(self, prompt, ident, parent, password=False): def _at_shutdown(self): """Actions taken at shutdown by the kernel, called by python's atexit. """ - # io.rprint("Kernel at_shutdown") # dbg if self._shutdown_message is not None: self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown')) self.log.debug("%s", self._shutdown_message) From 0b41a276d7f4b893a233d6272b02199032f24489 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 13 Aug 2018 11:46:12 -0700 Subject: [PATCH 0206/1195] attempt with locks --- ipykernel/kernelbase.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 6898e05b7..e72adf87f 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -23,6 +23,7 @@ import zmq from tornado import ioloop from tornado import gen +from tornado.locks import Semaphore from zmq.eventloop.zmqstream import ZMQStream from traitlets.config.configurable import SingletonConfigurable @@ -38,6 +39,13 @@ from ._version import kernel_protocol_version +from collections import defaultdict + +# shell handlers are now coroutine (for async await code), +# so we may not want to consume all the message and block while it yields +_msg_locks = defaultdict(Semaphore) + + class Kernel(SingletonConfigurable): #--------------------------------------------------------------------------- @@ -233,7 +241,9 @@ def dispatch_shell(self, stream, msg): self.log.debug("%s: %s", msg_type, msg) self.pre_handler_hook() try: - yield gen.maybe_future(handler(stream, idents, msg)) + lock = _msg_locks[msg_type] + with (yield lock.acquire()): + yield gen.maybe_future(handler(stream, idents, msg)) except Exception: self.log.error("Exception in message handler:", exc_info=True) finally: @@ -376,7 +386,6 @@ def finish_metadata(self, parent, metadata, reply_content): @gen.coroutine def execute_request(self, stream, ident, parent): """handle an execute_request""" - try: content = parent[u'content'] code = py3compat.cast_unicode_py2(content[u'code']) From 30f95d39ff47a795a9a5a7eaabfa2551c686a43f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 14 Aug 2018 17:25:50 -0700 Subject: [PATCH 0207/1195] allow using other coroutine runners --- ipykernel/ipkernel.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 129d16326..9a283cce1 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -214,7 +214,16 @@ def do_execute(self, code, silent, store_history=True, def run_cell(*args, **kwargs): return shell.run_cell(*args, **kwargs) try: - res = yield run_cell(code, store_history=store_history, silent=silent) + # TODO: here we need to hook into the right event loop to run user + # code using trio/curio. + from IPython.core.interactiveshell import _asyncio_runner + if shell.loop_runner is _asyncio_runner: + res = yield run_cell(code, store_history=store_history, silent=silent) + else: + @gen.coroutine + def run_cell(*args, **kwargs): + return shell.run_cell(*args, **kwargs) + res = yield run_cell(code, store_history=store_history, silent=silent) finally: self._restore_input() From 88e69ec598459bbc763acbb93d53a05a70e7b66b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 16 Aug 2018 17:35:48 -0700 Subject: [PATCH 0208/1195] Revert "attempt with locks" This reverts commit 12e19e327519bc6ed56b4eec27de1f7a63a26703. --- ipykernel/kernelbase.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e72adf87f..6898e05b7 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -23,7 +23,6 @@ import zmq from tornado import ioloop from tornado import gen -from tornado.locks import Semaphore from zmq.eventloop.zmqstream import ZMQStream from traitlets.config.configurable import SingletonConfigurable @@ -39,13 +38,6 @@ from ._version import kernel_protocol_version -from collections import defaultdict - -# shell handlers are now coroutine (for async await code), -# so we may not want to consume all the message and block while it yields -_msg_locks = defaultdict(Semaphore) - - class Kernel(SingletonConfigurable): #--------------------------------------------------------------------------- @@ -241,9 +233,7 @@ def dispatch_shell(self, stream, msg): self.log.debug("%s: %s", msg_type, msg) self.pre_handler_hook() try: - lock = _msg_locks[msg_type] - with (yield lock.acquire()): - yield gen.maybe_future(handler(stream, idents, msg)) + yield gen.maybe_future(handler(stream, idents, msg)) except Exception: self.log.error("Exception in message handler:", exc_info=True) finally: @@ -386,6 +376,7 @@ def finish_metadata(self, parent, metadata, reply_content): @gen.coroutine def execute_request(self, stream, ident, parent): """handle an execute_request""" + try: content = parent[u'content'] code = py3compat.cast_unicode_py2(content[u'code']) From 78c2afd937d12c09de865ec105ef2b8e98863503 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 28 Aug 2018 14:35:19 -0700 Subject: [PATCH 0209/1195] update to check interactivity --- ipykernel/ipkernel.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 9a283cce1..f7e07e16a 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -216,9 +216,16 @@ def run_cell(*args, **kwargs): try: # TODO: here we need to hook into the right event loop to run user # code using trio/curio. - from IPython.core.interactiveshell import _asyncio_runner + from IPython.core.interactiveshell import _asyncio_runner, ExecutionResult if shell.loop_runner is _asyncio_runner: - res = yield run_cell(code, store_history=store_history, silent=silent) + coro = run_cell(code, store_history=store_history, silent=silent) + try: + interactivity = coro.send(None) + except StopIteration as exc: + return exc.value + if isinstance(interactivity, ExecutionResult): + return interactivity + res = yield coro else: @gen.coroutine def run_cell(*args, **kwargs): From fabb343dc70d22a932874a9938f3d8455432ee47 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 27 Aug 2018 18:54:33 -0700 Subject: [PATCH 0210/1195] Try to push things into queue to process items in order. --- ipykernel/kernelbase.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 6898e05b7..df0e60f21 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -23,6 +23,7 @@ import zmq from tornado import ioloop from tornado import gen +from tornado.queues import Queue from zmq.eventloop.zmqstream import ZMQStream from traitlets.config.configurable import SingletonConfigurable @@ -281,10 +282,18 @@ def start(self): if self.control_stream: self.control_stream.on_recv(self.dispatch_control, copy=False) + self.msg_queue = Queue() + + async def process_queue(): + while True: + stream, msg = await self.msg_queue.get() + await self.dispatch_shell(stream, msg) + def make_dispatcher(stream): def dispatcher(msg): - return self.dispatch_shell(stream, msg) + self.msg_queue.put((stream, msg)) return dispatcher + self.io_loop.add_callback(process_queue) for s in self.shell_streams: s.on_recv(make_dispatcher(s), copy=False) From 0844ebf0fbf34c45639d6cbbec2618f1540ce2a3 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 28 Aug 2018 14:44:03 -0700 Subject: [PATCH 0211/1195] Unrelated extra docs --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index df0e60f21..fb94d3285 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -643,7 +643,7 @@ def clear_request(self, stream, idents, parent): content = content) def do_clear(self): - """DEPRECATED""" + """DEPRECATED since 4.0.3""" raise NotImplementedError #--------------------------------------------------------------------------- From 98d2fb8087036e5a672d2d4cf585fe8eaff2179a Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 28 Aug 2018 14:44:45 -0700 Subject: [PATCH 0212/1195] typo --- docs/changelog.rst | 2 +- ipykernel/connect.py | 2 +- ipykernel/kernelbase.py | 2 +- ipykernel/zmqshell.py | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 38e2048c8..b213928a5 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -196,7 +196,7 @@ Changes in IPython kernel - Publish all IO in a thread, via :class:`IOPubThread`. This solves the problem of requiring :meth:`sys.stdout.flush` to be called in the notebook to produce output promptly during long-running cells. -- Remove refrences to outdated IPython guiref in kernel banner. +- Remove references to outdated IPython guiref in kernel banner. - Patch faulthandler to use ``sys.__stderr__`` instead of forwarded ``sys.stderr``, which has no fileno when forwarded. - Deprecate some vestiges of the Big Split: diff --git a/ipykernel/connect.py b/ipykernel/connect.py index faaa402ec..3106c9983 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -40,7 +40,7 @@ def get_connection_file(app=None): def find_connection_file(filename='kernel-*.json', profile=None): """DEPRECATED: find a connection file, and return its absolute path. - THIS FUNCION IS DEPRECATED. Use juptyer_client.find_connection_file instead. + THIS FUNCTION IS DEPRECATED. Use juptyer_client.find_connection_file instead. Parameters ---------- diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index fb94d3285..52d97265a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -82,7 +82,7 @@ def _default_ident(self): # Private interface _darwin_app_nap = Bool(True, - help="""Whether to use appnope for compatiblity with OS X App Nap. + help="""Whether to use appnope for compatibility with OS X App Nap. Only affects OS X >= 10.9. """ diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 3cdf6e6e3..a76240f61 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -174,7 +174,7 @@ def register_hook(self, hook): The DisplayHook objects must return a message from the __call__ method if they still require the - `session.send` method to be called after tranformation. + `session.send` method to be called after transformation. Returning `None` will halt that execution path, and session.send will not be called. """ @@ -253,7 +253,7 @@ def edit(self, parameter_s='', last_call=['','']): Arguments: - If arguments are given, the following possibilites exist: + If arguments are given, the following possibilities exist: - The arguments are numbers or pairs of colon-separated numbers (like 1 4:8 9). These are interpreted as lines of previous input to be From cae4507dcdeb9f770ae7e97303870d87c176de7b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 28 Aug 2018 15:39:52 -0700 Subject: [PATCH 0213/1195] Revert "Try to push things into queue to process items in order." This reverts commit 4c8ed2613052c428633b4ec93221490c95cde8f5. --- ipykernel/kernelbase.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 52d97265a..177b545ca 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -23,7 +23,6 @@ import zmq from tornado import ioloop from tornado import gen -from tornado.queues import Queue from zmq.eventloop.zmqstream import ZMQStream from traitlets.config.configurable import SingletonConfigurable @@ -282,18 +281,10 @@ def start(self): if self.control_stream: self.control_stream.on_recv(self.dispatch_control, copy=False) - self.msg_queue = Queue() - - async def process_queue(): - while True: - stream, msg = await self.msg_queue.get() - await self.dispatch_shell(stream, msg) - def make_dispatcher(stream): def dispatcher(msg): - self.msg_queue.put((stream, msg)) + return self.dispatch_shell(stream, msg) return dispatcher - self.io_loop.add_callback(process_queue) for s in self.shell_streams: s.on_recv(make_dispatcher(s), copy=False) From 770979bc585e1c8a7a668f2c7aa3853c950db7c6 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 4 Sep 2018 17:13:57 +0200 Subject: [PATCH 0214/1195] fix handling of asyncio and ExecutionResult objects - do_execute doesn't return ExecutionResults. Assign with `res =` - call blocking run_cell if we aren't inside running asyncio --- ipykernel/ipkernel.py | 48 +++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index f7e07e16a..07f5c3563 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -1,9 +1,11 @@ """The IPython kernel implementation""" +import asyncio import getpass import sys from IPython.core import release +from IPython.core.interactiveshell import ExecutionResult from ipython_genutils.py3compat import builtin_mod, PY3, unicode_type, safe_unicode from IPython.utils.tokenutil import token_at_cursor, line_at_cursor from tornado import gen @@ -13,6 +15,11 @@ from .kernelbase import Kernel as KernelBase from .zmqshell import ZMQInteractiveShell +try: + from IPython.core.interactiveshell import _asyncio_runner +except ImportError: + _asyncio_runner = None + try: from IPython.core.completer import rectify_completions as _rectify_completions, provisionalcompleter as _provisionalcompleter _use_experimental_60_completion = True @@ -214,23 +221,38 @@ def do_execute(self, code, silent, store_history=True, def run_cell(*args, **kwargs): return shell.run_cell(*args, **kwargs) try: - # TODO: here we need to hook into the right event loop to run user - # code using trio/curio. - from IPython.core.interactiveshell import _asyncio_runner, ExecutionResult - if shell.loop_runner is _asyncio_runner: + try: + from IPython.core.interactiveshell import _asyncio_runner + except ImportError: + _asyncio_runner = None + + # default case: runner is asyncio and asyncio is already running + # TODO: this should check every case for "are we inside the runner", + # not just asyncio + if ( + _asyncio_runner + and shell.loop_runner is _asyncio_runner + and asyncio.get_event_loop().is_running() + ): coro = run_cell(code, store_history=store_history, silent=silent) + # check interactivity try: - interactivity = coro.send(None) + res_or_interactivity = coro.send(None) except StopIteration as exc: - return exc.value - if isinstance(interactivity, ExecutionResult): - return interactivity - res = yield coro + res = exc.value + else: + # if code was not async, sending `None` was actually executing the code. + if isinstance(res_or_interactivity, ExecutionResult): + res = res_or_interactivity + else: + # this is where actual async execution happens + res = yield coro else: - @gen.coroutine - def run_cell(*args, **kwargs): - return shell.run_cell(*args, **kwargs) - res = yield run_cell(code, store_history=store_history, silent=silent) + # runner isn't already running, + # make synchronous call, + # letting shell dispatch to loop runners + res = shell.run_cell(code, store_history=store_history, silent=silent) + finally: self._restore_input() From 84c2ef585fb3225c8e8100df9c54e7be8093b04f Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 4 Sep 2018 17:27:00 +0200 Subject: [PATCH 0215/1195] add dispatch_queue to preserve async message handling order --- ipykernel/kernelbase.py | 51 ++++++++++++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 177b545ca..cf1f9a40a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -5,12 +5,14 @@ from __future__ import print_function +from datetime import datetime +from functools import partial +import logging +from signal import signal, default_int_handler, SIGINT import sys import time -import logging import uuid -from datetime import datetime try: # jupyter_client >= 5, use tz-aware now from jupyter_client.session import utcnow as now @@ -18,11 +20,11 @@ # jupyter_client < 5, use local now() now = datetime.now -from signal import signal, default_int_handler, SIGINT import zmq from tornado import ioloop from tornado import gen +from tornado.queues import Queue from zmq.eventloop.zmqstream import ZMQStream from traitlets.config.configurable import SingletonConfigurable @@ -31,7 +33,8 @@ from ipython_genutils.py3compat import unicode_type, string_types from ipykernel.jsonutil import json_clean from traitlets import ( - Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool, observe, default + Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool, + observe, default ) from jupyter_client.session import Session @@ -275,19 +278,45 @@ def enter_eventloop(self): self.post_handler_hook() self.log.info("exiting eventloop") + @gen.coroutine + def dispatch_queue(self): + """Coroutine to preserve order of message execution + + Ensures that only one message is processing at a time, + even when the handler is async + """ + + while True: + # get the next dispatch call + dispatch, args = yield self.msg_queue.get() + # run it and wait for it to finish + yield gen.maybe_future(dispatch(*args)) + + def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() - if self.control_stream: - self.control_stream.on_recv(self.dispatch_control, copy=False) + self.msg_queue = Queue() + self.io_loop.add_callback(self.dispatch_queue) + + def schedule_dispatch(dispatch, *args): + """schedule a message for dispatch""" + # via loop.add_callback to ensure everything gets scheduled + self.io_loop.add_callback( + lambda: self.msg_queue.put((dispatch, args)) + ) - def make_dispatcher(stream): - def dispatcher(msg): - return self.dispatch_shell(stream, msg) - return dispatcher + if self.control_stream: + self.control_stream.on_recv( + partial(schedule_dispatch, self.dispatch_control), + copy=False, + ) for s in self.shell_streams: - s.on_recv(make_dispatcher(s), copy=False) + s.on_recv( + partial(schedule_dispatch, self.dispatch_shell, s), + copy=False, + ) # publish idle status self._publish_status('starting') From 3b1f632528e3577539e96340f9d751723bf5c7cc Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 5 Sep 2018 13:53:27 +0200 Subject: [PATCH 0216/1195] use PriorityQueue for messages ensures control messages maintain priority --- ipykernel/kernelbase.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index cf1f9a40a..995d6edf0 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -24,7 +24,7 @@ import zmq from tornado import ioloop from tornado import gen -from tornado.queues import Queue +from tornado.queues import PriorityQueue from zmq.eventloop.zmqstream import ZMQStream from traitlets.config.configurable import SingletonConfigurable @@ -288,33 +288,54 @@ def dispatch_queue(self): while True: # get the next dispatch call - dispatch, args = yield self.msg_queue.get() # run it and wait for it to finish - yield gen.maybe_future(dispatch(*args)) - + try: + priority, dispatch, args = yield self.msg_queue.get() + yield gen.maybe_future(dispatch(*args)) + except Exception: + self.log.exception("Error in message handler") def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() - self.msg_queue = Queue() + self.msg_queue = PriorityQueue() self.io_loop.add_callback(self.dispatch_queue) - def schedule_dispatch(dispatch, *args): + class MessageEvent(tuple): + """class for priority message events + + ensures that comparison only invokes the priority entry, + not comparing the contents of the messages + """ + def __eq__(self, other): + return self[0] == other[0] + + def __lt__(self, other): + return self[0] < other[0] + + def schedule_dispatch(priority, dispatch, *args): """schedule a message for dispatch""" # via loop.add_callback to ensure everything gets scheduled + # on the eventloop self.io_loop.add_callback( - lambda: self.msg_queue.put((dispatch, args)) + lambda: self.msg_queue.put( + MessageEvent(( + priority, + dispatch, + args, + )) + ) ) if self.control_stream: self.control_stream.on_recv( - partial(schedule_dispatch, self.dispatch_control), + partial(schedule_dispatch, 1, self.dispatch_control), copy=False, ) for s in self.shell_streams: s.on_recv( - partial(schedule_dispatch, self.dispatch_shell, s), + partial(schedule_dispatch, 10, self.dispatch_shell, s), copy=False, ) From 1cfa49f7e7cf9fe0d9a84eafb147b2269a62d576 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 5 Sep 2018 13:58:55 +0200 Subject: [PATCH 0217/1195] Deprecate kernel.do_one_iteration eventloops should pause when a socket wakes, rather than calling doi, which can't work anymore now that processing one message is an async operation --- ipykernel/kernelbase.py | 65 ++++++++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 995d6edf0..8db06313f 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -12,6 +12,7 @@ import sys import time import uuid +import warnings try: # jupyter_client >= 5, use tz-aware now @@ -20,11 +21,10 @@ # jupyter_client < 5, use local now() now = datetime.now - -import zmq from tornado import ioloop from tornado import gen from tornado.queues import PriorityQueue +import zmq from zmq.eventloop.zmqstream import ZMQStream from traitlets.config.configurable import SingletonConfigurable @@ -106,7 +106,7 @@ def _default_ident(self): # Frequency of the kernel's event loop. # Units are in seconds, kernel subclasses for GUI toolkits may need to # adapt to milliseconds. - _poll_interval = Float(0.05).tag(config=True) + _poll_interval = Float(0.01).tag(config=True) # If the shutdown was requested over the network, we leave here the # necessary reply message so it can be sent by our registered atexit @@ -257,26 +257,39 @@ def post_handler_hook(self): def enter_eventloop(self): """enter eventloop""" - self.log.info("entering eventloop %s", self.eventloop) - for stream in self.shell_streams: - # flush any pending replies, - # which may be skipped by entering the eventloop - stream.flush(zmq.POLLOUT) - # restore default_int_handler - self.pre_handler_hook() - while self.eventloop is not None: + self.log.info("Entering eventloop %s", self.eventloop) + # record handle, so we can check when this changes + eventloop = self.eventloop + def advance_eventloop(): + # check if eventloop changed: + if self.eventloop is not eventloop: + self.log.info("exiting eventloop %s", eventloop) + return + if self.msg_queue.qsize(): + self.log.debug("Delaying eventloop due to waiting messages") + # still messages to process, make the eventloop wait + schedule_next() + return + self.log.debug("Advancing eventloop %s", eventloop) try: - self.eventloop(self) + eventloop(self) except KeyboardInterrupt: # Ctrl-C shouldn't crash the kernel self.log.error("KeyboardInterrupt caught in kernel") - continue - else: - # eventloop exited cleanly, this means we should stop (right?) - self.eventloop = None - break - self.post_handler_hook() - self.log.info("exiting eventloop") + pass + if self.eventloop is eventloop: + # schedule advance again + schedule_next() + + def schedule_next(): + """Schedule the next advance of the eventloop""" + # flush the eventloop every so often, + # giving us a chance to handle messages in the meantime + self.log.debug("Scheduling eventloop advance") + self.io_loop.call_later(1, advance_eventloop) + + # begin polling the eventloop + schedule_next() @gen.coroutine def dispatch_queue(self): @@ -343,13 +356,13 @@ def schedule_dispatch(priority, dispatch, *args): self._publish_status('starting') def do_one_iteration(self): - """step eventloop just once""" - if self.control_stream: - self.control_stream.flush() - for stream in self.shell_streams: - # handle at most one request per iteration - stream.flush(zmq.POLLIN, 1) - stream.flush(zmq.POLLOUT) + """DEPRECATED in ipykernel 5. Does nothing.""" + warnings.warn( + "Kernel.do_one_iteration is deprecated in ipykernel 5." + " Message processing can no longer be done in a blocking manner.", + DeprecationWarning, + stacklevel=2, + ) def record_ports(self, ports): """Record the ports that this kernel is using. From 249354dc791455eb5b9f499629ecd3c053d43be0 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 5 Sep 2018 13:59:55 +0200 Subject: [PATCH 0218/1195] update qt eventloop for new structure socket event pauses eventloop instead of calling doi --- ipykernel/eventloops.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 68b0b5cfb..1397f7845 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -4,6 +4,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from functools import partial import os import sys import platform @@ -14,6 +15,7 @@ from traitlets.config.application import Application from IPython.utils import io + def _use_appnope(): """Should we use appnope for dealing with OS X app nap? @@ -21,6 +23,7 @@ def _use_appnope(): """ return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9') + def _notify_stream_qt(kernel, stream): from IPython.external.qt_for_kernel import QtCore @@ -34,9 +37,15 @@ def context(): yield def process_stream_events(): - while stream.getsockopt(zmq.EVENTS) & zmq.POLLIN: - with context(): - kernel.do_one_iteration() + """fall back to main loop when there's a socket event""" + # call flush to ensure that the stream doesn't lose events + # due to our consuming of the edge-triggered FD + # flush returns the number of events consumed. + # if there were any, wake it up + if stream.flush(limit=1): + kernel.log.info("Socket event!") + notifier.setEnabled(False) + kernel.app.quit() fd = stream.getsockopt(zmq.FD) notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) @@ -88,6 +97,7 @@ def exit_decorator(exit_func): to register a function to be called on exit """ func.exit_hook = exit_func + return exit_func func.exit = exit_decorator return func @@ -122,11 +132,6 @@ def loop_qt4(kernel): _loop_qt(kernel.app) -@loop_qt4.exit -def loop_qt4_exit(kernel): - kernel.app.exit() - - @register_integration('qt', 'qt5') def loop_qt5(kernel): """Start a kernel with PyQt5 event loop integration.""" @@ -134,8 +139,10 @@ def loop_qt5(kernel): return loop_qt4(kernel) +# exit and watch are the same for qt 4 and 5 +@loop_qt4.exit @loop_qt5.exit -def loop_qt5_exit(kernel): +def loop_qt_exit(kernel): kernel.app.exit() From e719892c6f56ac5185a51584f5ddd136fb77b603 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 5 Sep 2018 14:00:34 +0200 Subject: [PATCH 0219/1195] eventloops: tk now works with new eventloop --- ipykernel/eventloops.py | 41 +++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 1397f7845..2de28c314 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -220,35 +220,32 @@ def loop_wx_exit(kernel): def loop_tk(kernel): """Start a kernel with the Tk event loop.""" - try: - from tkinter import Tk # Py 3 - except ImportError: - from Tkinter import Tk # Py 2 - doi = kernel.do_one_iteration - # Tk uses milliseconds - poll_interval = int(1000*kernel._poll_interval) - # For Tkinter, we create a Tk object and call its withdraw method. - class Timer(object): - def __init__(self, func): - self.app = Tk() - self.app.withdraw() - self.func = func + from tkinter import Tk, READABLE - def on_timer(self): - self.func() - self.app.after(poll_interval, self.on_timer) + def process_stream_events(stream, *a, **kw): + """fall back to main loop when there's a socket event""" + if stream.flush(limit=1): + kernel.log.info("Socket event!") + app.tk.deletefilehandler(stream.getsockopt(zmq.FD)) + app.quit() - def start(self): - self.on_timer() # Call it once to get things going. - self.app.mainloop() + # For Tkinter, we create a Tk object and call its withdraw method. + kernel.app = app = Tk() + kernel.app.withdraw() + for stream in kernel.shell_streams: + notifier = partial(process_stream_events, stream) + # seems to be needed for tk + notifier.__name__ = 'notifier' + app.tk.createfilehandler(stream.getsockopt(zmq.FD), READABLE, notifier) + # schedule initial call after start + app.after(0, notifier) - kernel.timer = Timer(doi) - kernel.timer.start() + app.mainloop() @loop_tk.exit def loop_tk_exit(kernel): - kernel.timer.app.destroy() + kernel.app.destroy() @register_integration('gtk') From 06a23f3ca9d4a5749d844306ce90f63712dffdd5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 5 Sep 2018 14:18:03 +0200 Subject: [PATCH 0220/1195] %gui asyncio with new eventloop requirements --- ipykernel/eventloops.py | 42 +++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 2de28c314..92aaf8cc6 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -43,7 +43,6 @@ def process_stream_events(): # flush returns the number of events consumed. # if there were any, wake it up if stream.flush(limit=1): - kernel.log.info("Socket event!") notifier.setEnabled(False) kernel.app.quit() @@ -225,7 +224,6 @@ def loop_tk(kernel): def process_stream_events(stream, *a, **kw): """fall back to main loop when there's a socket event""" if stream.flush(limit=1): - kernel.log.info("Socket event!") app.tk.deletefilehandler(stream.getsockopt(zmq.FD)) app.quit() @@ -330,11 +328,24 @@ def loop_asyncio(kernel): if loop.is_running(): return - def kernel_handler(): - loop.call_soon(kernel.do_one_iteration) - loop.call_later(kernel._poll_interval, kernel_handler) + if loop.is_closed(): + # main loop is closed, create a new one + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop._should_close = False + + # pause eventloop when there's an event on a zmq socket + def process_stream_events(stream): + """fall back to main loop when there's a socket event""" + if stream.flush(limit=1): + loop.stop() + + for stream in kernel.shell_streams: + fd = stream.getsockopt(zmq.FD) + notifier = partial(process_stream_events, stream) + loop.add_reader(fd, notifier) + loop.call_soon(notifier) - loop.call_soon(kernel_handler) while True: error = None try: @@ -343,9 +354,8 @@ def kernel_handler(): continue except Exception as e: error = e - if hasattr(loop, 'shutdown_asyncgens'): - loop.run_until_complete(loop.shutdown_asyncgens()) - loop.close() + if loop._should_close: + loop.close() if error is not None: raise error break @@ -356,8 +366,20 @@ def loop_asyncio_exit(kernel): """Exit hook for asyncio""" import asyncio loop = asyncio.get_event_loop() + + @asyncio.coroutine + def close_loop(): + if hasattr(loop, 'shutdown_asyncgens'): + yield from loop.shutdown_asyncgens() + loop._should_close = True + loop.stop() + if loop.is_running(): - loop.call_soon(loop.stop) + close_loop() + + elif not loop.is_closed(): + loop.run_until_complete(close_loop) + loop.close() def enable_gui(gui, kernel=None): From 00815070faf3e60a46b715b0588f4830c270c661 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 5 Sep 2018 14:49:10 +0200 Subject: [PATCH 0221/1195] wx eventloop works --- ipykernel/eventloops.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 92aaf8cc6..2ff808df7 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -169,9 +169,15 @@ def loop_wx(kernel): from appnope import nope nope() - doi = kernel.do_one_iteration # Wx uses milliseconds - poll_interval = int(1000*kernel._poll_interval) + poll_interval = int(1000 * kernel._poll_interval) + + def wake(): + """wake from wx""" + for stream in kernel.shell_streams: + if stream.flush(limit=1): + kernel.app.ExitMainLoop() + return # We have to put the wx.Timer in a wx.Frame for it to fire properly. # We make the Frame hidden when we create it in the main app below. @@ -188,16 +194,20 @@ def on_timer(self, event): self.func() # We need a custom wx.App to create our Frame subclass that has the - # wx.Timer to drive the ZMQ event loop. + # wx.Timer to defer back to the tornado event loop. class IPWxApp(wx.App): def OnInit(self): - self.frame = TimerFrame(doi) + self.frame = TimerFrame(wake) self.frame.Show(False) return True # The redirect=False here makes sure that wx doesn't replace # sys.stdout/stderr with its own classes. - kernel.app = IPWxApp(redirect=False) + if not ( + getattr(kernel, 'app', None) + and isinstance(kernel.app, wx.App) + ): + kernel.app = IPWxApp(redirect=False) # The import of wx on Linux sets the handler for signal.SIGINT # to 0. This is a bug in wx or gtk. We fix by just setting it From 88dde7241f9515339868638f8a60330ba8c4c6bb Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 5 Sep 2018 14:55:03 +0200 Subject: [PATCH 0222/1195] cocoa eventloop update return control instead of calling kernel.do_one_iteration --- ipykernel/eventloops.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 2ff808df7..7d3d32365 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -311,8 +311,10 @@ def handle_int(etype, value, tb): # don't let interrupts during mainloop invoke crash_handler: sys.excepthook = handle_int mainloop(kernel._poll_interval) - sys.excepthook = real_excepthook - kernel.do_one_iteration() + for stream in kernel.shell_streams: + if stream.flush(limit=1): + # events to process, return control to kernel + return except: raise except KeyboardInterrupt: From 85f278644058172f979b0931e65a81022247b396 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 5 Sep 2018 15:39:49 +0200 Subject: [PATCH 0223/1195] restore do_one_iteration as a coroutine specify control and shell priorities - do_one_iteration processes all control messages and at most one shell message - process_one processes one request --- ipykernel/kernelapp.py | 1 + ipykernel/kernelbase.py | 73 +++++++++++++++++++++++++++++++---------- 2 files changed, 57 insertions(+), 17 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index c1446865d..f0ca68bfb 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -378,6 +378,7 @@ def init_kernel(self): kernel_factory = self.kernel_class.instance kernel = kernel_factory(parent=self, session=self.session, + control_stream=control_stream, shell_streams=[shell_stream, control_stream], iopub_thread=self.iopub_thread, iopub_socket=self.iopub_socket, diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 8db06313f..8fb25db4a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -12,7 +12,6 @@ import sys import time import uuid -import warnings try: # jupyter_client >= 5, use tz-aware now @@ -23,7 +22,7 @@ from tornado import ioloop from tornado import gen -from tornado.queues import PriorityQueue +from tornado.queues import PriorityQueue, QueueEmpty import zmq from zmq.eventloop.zmqstream import ZMQStream @@ -41,6 +40,10 @@ from ._version import kernel_protocol_version +CONTROL_PRIORITY = 1 +SHELL_PRIORITY = 10 + + class Kernel(SingletonConfigurable): #--------------------------------------------------------------------------- @@ -291,20 +294,53 @@ def schedule_next(): # begin polling the eventloop schedule_next() + @gen.coroutine + def do_one_iteration(self): + """Process a single shell message + + Any pending control messages will be flushed as well + + .. versionchanged:: 5 + This is now a coroutine + """ + # flush messages off of shell streams into the message queue + for stream in self.shell_streams: + stream.flush() + # process all messages higher priority than shell (control), + # and at most one shell message per iteration + priority = 0 + while priority is not None and priority < SHELL_PRIORITY: + priority = yield self.process_one(wait=False) + + @gen.coroutine + def process_one(self, wait=True): + """Process one request + + Returns priority of the message handled. + Returns None if no message was handled. + """ + if wait: + get = self.msg_queue.get + else: + get = self.msg_queue.get_nowait + try: + priority, dispatch, args = yield get() + except QueueEmpty: + return + yield gen.maybe_future(dispatch(*args)) + @gen.coroutine def dispatch_queue(self): - """Coroutine to preserve order of message execution + """Coroutine to preserve order of message handling Ensures that only one message is processing at a time, even when the handler is async """ while True: - # get the next dispatch call - # run it and wait for it to finish + # receive the next message and handle it try: - priority, dispatch, args = yield self.msg_queue.get() - yield gen.maybe_future(dispatch(*args)) + yield self.process_one() except Exception: self.log.exception("Error in message handler") @@ -342,27 +378,30 @@ def schedule_dispatch(priority, dispatch, *args): if self.control_stream: self.control_stream.on_recv( - partial(schedule_dispatch, 1, self.dispatch_control), + partial( + schedule_dispatch, + CONTROL_PRIORITY, + self.dispatch_control, + ), copy=False, ) for s in self.shell_streams: + if s is self.control_stream: + continue s.on_recv( - partial(schedule_dispatch, 10, self.dispatch_shell, s), + partial( + schedule_dispatch, + SHELL_PRIORITY, + self.dispatch_shell, + s, + ), copy=False, ) # publish idle status self._publish_status('starting') - def do_one_iteration(self): - """DEPRECATED in ipykernel 5. Does nothing.""" - warnings.warn( - "Kernel.do_one_iteration is deprecated in ipykernel 5." - " Message processing can no longer be done in a blocking manner.", - DeprecationWarning, - stacklevel=2, - ) def record_ports(self, ports): """Record the ports that this kernel is using. From aa577a40ab8b60926ef4cc8a96654d4da4e7b1af Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 5 Sep 2018 16:18:34 +0200 Subject: [PATCH 0224/1195] fix abort on error now that messages are waiting in msg_queue, abort them by setting a stateful _aborting flag and placing a message in the queue that will clear it abort replies are now handled in dispatch instead of a special blocking method time is added to ensure message ordering of the same priority --- ipykernel/kernelbase.py | 147 +++++++++++++++++++++------------------- 1 file changed, 78 insertions(+), 69 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 8fb25db4a..ef280e653 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -42,6 +42,19 @@ CONTROL_PRIORITY = 1 SHELL_PRIORITY = 10 +ABORT_PRIORITY = 20 + +class _MessageEvent(tuple): + """class for priority message events + + ensures that comparison only invokes the priority entry, + not comparing the contents of the messages + """ + def __eq__(self, other): + return self[:2] == other[:2] + + def __lt__(self, other): + return self[:2] < other[:2] class Kernel(SingletonConfigurable): @@ -155,7 +168,7 @@ def __init__(self, **kwargs): @gen.coroutine def dispatch_control(self, msg): """dispatch control requests""" - idents,msg = self.session.feed_identities(msg, copy=False) + idents, msg = self.session.feed_identities(msg, copy=False) try: msg = self.session.deserialize(msg, content=True, copy=False) except: @@ -167,6 +180,10 @@ def dispatch_control(self, msg): # Set the parent message for side effects. self.set_parent(idents, msg) self._publish_status(u'busy') + if self._aborting: + self._send_abort_reply(self.control_stream, msg, idents) + self._publish_status(u'idle') + return header = msg['header'] msg_type = header['msg_type'] @@ -194,12 +211,7 @@ def should_handle(self, stream, msg, idents): msg_type = msg['header']['msg_type'] # is it safe to assume a msg_id will not be resubmitted? self.aborted.remove(msg_id) - reply_type = msg_type.split('_')[0] + '_reply' - status = {'status' : 'aborted'} - md = {'engine' : self.ident} - md.update(status) - self.session.send(stream, reply_type, metadata=md, - content=status, parent=msg, ident=idents) + self._send_abort_reply(stream, msg, idents) return False return True @@ -210,7 +222,7 @@ def dispatch_shell(self, stream, msg): if self.control_stream: self.control_stream.flush() - idents,msg = self.session.feed_identities(msg, copy=False) + idents, msg = self.session.feed_identities(msg, copy=False) try: msg = self.session.deserialize(msg, content=True, copy=False) except: @@ -221,6 +233,11 @@ def dispatch_shell(self, stream, msg): self.set_parent(idents, msg) self._publish_status(u'busy') + if self._aborting: + self._send_abort_reply(stream, msg, idents) + self._publish_status(u'idle') + return + msg_type = msg['header']['msg_type'] # Print some info about this message and leave a '--->' marker, so it's @@ -320,13 +337,12 @@ def process_one(self, wait=True): Returns None if no message was handled. """ if wait: - get = self.msg_queue.get + priority, t, dispatch, args = yield self.msg_queue.get() else: - get = self.msg_queue.get_nowait - try: - priority, dispatch, args = yield get() - except QueueEmpty: - return + try: + priority, t, dispatch, args = self.msg_queue.get_nowait() + except QueueEmpty: + return None yield gen.maybe_future(dispatch(*args)) @gen.coroutine @@ -344,42 +360,32 @@ def dispatch_queue(self): except Exception: self.log.exception("Error in message handler") + def schedule_dispatch(self, priority, dispatch, *args): + """schedule a message for dispatch""" + # via loop.add_callback to ensure everything gets scheduled + # on the eventloop + self.io_loop.add_callback( + lambda: self.msg_queue.put( + _MessageEvent(( + priority, + self.io_loop.time(), + dispatch, + args, + )) + ) + ) + def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() self.msg_queue = PriorityQueue() self.io_loop.add_callback(self.dispatch_queue) - class MessageEvent(tuple): - """class for priority message events - - ensures that comparison only invokes the priority entry, - not comparing the contents of the messages - """ - def __eq__(self, other): - return self[0] == other[0] - - def __lt__(self, other): - return self[0] < other[0] - - def schedule_dispatch(priority, dispatch, *args): - """schedule a message for dispatch""" - # via loop.add_callback to ensure everything gets scheduled - # on the eventloop - self.io_loop.add_callback( - lambda: self.msg_queue.put( - MessageEvent(( - priority, - dispatch, - args, - )) - ) - ) if self.control_stream: self.control_stream.on_recv( partial( - schedule_dispatch, + self.schedule_dispatch, CONTROL_PRIORITY, self.dispatch_control, ), @@ -391,7 +397,7 @@ def schedule_dispatch(priority, dispatch, *args): continue s.on_recv( partial( - schedule_dispatch, + self.schedule_dispatch, SHELL_PRIORITY, self.dispatch_shell, s, @@ -528,7 +534,7 @@ def execute_request(self, stream, ident, parent): self.log.debug("%s", reply_msg) if not silent and reply_msg['content']['status'] == u'error' and stop_on_error: - self._abort_queues() + yield self._abort_queues() def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): @@ -714,7 +720,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): def abort_request(self, stream, ident, parent): """abort a specific msg by id""" - self.log.warning("abort_request is deprecated in kernel_base. It os only part of IPython parallel") + self.log.warning("abort_request is deprecated in kernel_base. It is only part of IPython parallel") msg_ids = parent['content'].get('msg_ids', None) if isinstance(msg_ids, string_types): msg_ids = [msg_ids] @@ -730,7 +736,7 @@ def abort_request(self, stream, ident, parent): def clear_request(self, stream, idents, parent): """Clear our namespace.""" - self.log.warning("clear_request is deprecated in kernel_base. It os only part of IPython parallel") + self.log.warning("clear_request is deprecated in kernel_base. It is only part of IPython parallel") content = self.do_clear() self.session.send(stream, 'clear_reply', ident=idents, parent=parent, content = content) @@ -749,35 +755,38 @@ def _topic(self, topic): return py3compat.cast_bytes("%s.%s" % (base, topic)) + _aborting = Bool(False) + + @gen.coroutine def _abort_queues(self): for stream in self.shell_streams: - if stream: - self._abort_queue(stream) + stream.flush() + self._aborting = True - def _abort_queue(self, stream): - poller = zmq.Poller() - poller.register(stream.socket, zmq.POLLIN) - while True: - idents,msg = self.session.recv(stream, zmq.NOBLOCK, content=True) - if msg is None: - return + self.schedule_dispatch( + ABORT_PRIORITY, + self._dispatch_abort, + ) - self.log.info("Aborting:") - self.log.info("%s", msg) - msg_type = msg['header']['msg_type'] - reply_type = msg_type.split('_')[0] + '_reply' - - status = {'status' : 'aborted'} - md = {'engine' : self.ident} - md.update(status) - self._publish_status('busy', parent=msg) - reply_msg = self.session.send(stream, reply_type, metadata=md, - content=status, parent=msg, ident=idents) - self._publish_status('idle', parent=msg) - self.log.debug("%s", reply_msg) - # We need to wait a bit for requests to come in. This can probably - # be set shorter for true asynchronous clients. - poller.poll(50) + @gen.coroutine + def _dispatch_abort(self): + self.log.info("Finishing abort") + yield gen.sleep(0.05) + self._aborting = False + + @gen.coroutine + def _send_abort_reply(self, stream, msg, idents): + """Send a reply to an aborted request""" + self.log.info("Aborting:") + self.log.info("%s", msg) + reply_type = msg['header']['msg_type'].rsplit('_', 1)[0] + '_reply' + status = {'status': 'aborted'} + md = {'engine': self.ident} + md.update(status) + self.session.send( + stream, reply_type, metadata=md, + content=status, parent=msg, ident=idents, + ) def _no_raw_input(self): """Raise StdinNotImplentedError if active frontend doesn't support From 51e070b41cebd9c3389abae3fc07d8a1c52b039a Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 13:46:05 +0200 Subject: [PATCH 0225/1195] run_cell_async is now a regular coroutine pending update in IPython --- ipykernel/ipkernel.py | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 07f5c3563..6d82dbb36 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -212,9 +212,11 @@ def do_execute(self, code, silent, store_history=True, self._forward_input(allow_stdin) reply_content = {} - if hasattr(shell, 'run_cell_async'): + if hasattr(shell, 'run_cell_async') and hasattr(shell, 'should_run_async'): run_cell = shell.run_cell_async + should_run_async = shell.should_run_async else: + should_run_async = lambda cell: False # older IPython, # use blocking run_cell and wrap it in coroutine @gen.coroutine @@ -231,22 +233,11 @@ def run_cell(*args, **kwargs): # not just asyncio if ( _asyncio_runner + and should_run_async(code) and shell.loop_runner is _asyncio_runner and asyncio.get_event_loop().is_running() ): - coro = run_cell(code, store_history=store_history, silent=silent) - # check interactivity - try: - res_or_interactivity = coro.send(None) - except StopIteration as exc: - res = exc.value - else: - # if code was not async, sending `None` was actually executing the code. - if isinstance(res_or_interactivity, ExecutionResult): - res = res_or_interactivity - else: - # this is where actual async execution happens - res = yield coro + res = yield run_cell(code, store_history=store_history, silent=silent) else: # runner isn't already running, # make synchronous call, From c1aec8cc6289e255f0ae73a4b75428b9c41fafd1 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 15:46:47 +0200 Subject: [PATCH 0226/1195] turn async KeyboardInterrupt into CancelledError with async code, KeyboardInterrupt is raised in the main eventloop by default, which would halt the kernel. While running async code, register a special sigint handler that cancels the relevant task instead of halting the eventloop. --- ipykernel/ipkernel.py | 62 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 6d82dbb36..5144a015d 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -1,11 +1,13 @@ """The IPython kernel implementation""" import asyncio +from contextlib import contextmanager +from functools import partial import getpass +import signal import sys from IPython.core import release -from IPython.core.interactiveshell import ExecutionResult from ipython_genutils.py3compat import builtin_mod, PY3, unicode_type, safe_unicode from IPython.utils.tokenutil import token_at_cursor, line_at_cursor from tornado import gen @@ -204,6 +206,53 @@ def execution_count(self, value): # execution counter. pass + @contextmanager + def _cancel_on_sigint(self, future): + """ContextManager for capturing SIGINT and cancelling a future + + SIGINT raises in the event loop when running async code, + but we want it to halt a coroutine. + + Ideally, it would raise KeyboardInterrupt, + but this turns it into a CancelledError. + At least it gets a decent traceback to the user. + """ + sigint_future = asyncio.Future() + + # whichever future finishes first, + # cancel the other one + def cancel_unless_done(f, _ignored): + if f.cancelled() or f.done(): + return + f.cancel() + + # when sigint finishes, + # abort the coroutine with CancelledError + sigint_future.add_done_callback( + partial(cancel_unless_done, future) + ) + # when the main future finishes, + # stop watching for SIGINT events + future.add_done_callback( + partial(cancel_unless_done, sigint_future) + ) + + def handle_sigint(*args): + def set_sigint_result(): + if sigint_future.cancelled() or sigint_future.done(): + return + sigint_future.set_result(1) + # use add_callback for thread safety + self.io_loop.add_callback(set_sigint_result) + + # set the custom sigint hander during this context + save_sigint = signal.signal(signal.SIGINT, handle_sigint) + try: + yield + finally: + # restore the previous sigint handler + signal.signal(signal.SIGINT, save_sigint) + @gen.coroutine def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): @@ -223,10 +272,6 @@ def do_execute(self, code, silent, store_history=True, def run_cell(*args, **kwargs): return shell.run_cell(*args, **kwargs) try: - try: - from IPython.core.interactiveshell import _asyncio_runner - except ImportError: - _asyncio_runner = None # default case: runner is asyncio and asyncio is already running # TODO: this should check every case for "are we inside the runner", @@ -237,13 +282,16 @@ def run_cell(*args, **kwargs): and shell.loop_runner is _asyncio_runner and asyncio.get_event_loop().is_running() ): - res = yield run_cell(code, store_history=store_history, silent=silent) + coro = run_cell(code, store_history=store_history, silent=silent) + coro_future = asyncio.ensure_future(coro) + + with self._cancel_on_sigint(coro_future): + res = yield coro_future else: # runner isn't already running, # make synchronous call, # letting shell dispatch to loop runners res = shell.run_cell(code, store_history=store_history, silent=silent) - finally: self._restore_input() From 72cfec00d09abf19cde01671eaffdd35cb5535ae Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 16:00:59 +0200 Subject: [PATCH 0227/1195] dedent travis yaml doesn't like 4 spaces --- .travis.yml | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8cb89d14b..a35cccc8f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,26 +1,28 @@ language: python python: - - "nightly" - - "3.7-dev" - - 3.6 - - 3.5 - - 3.4 + - "nightly" + - "3.7-dev" + - 3.6 + - 3.5 + - 3.4 sudo: false install: - - | - pip install --upgrade setuptools pip - pip install --pre . - pip install ipykernel[test] codecov - - | - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then - pip install matplotlib - fi - - pip freeze + - | + # pip install + pip install --upgrade setuptools pip + pip install --pre . + pip install ipykernel[test] codecov + - | + # install matplotlib + if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then + pip install matplotlib + fi + - pip freeze script: - - jupyter kernelspec list - - pytest --cov ipykernel --durations 10 -v ipykernel + - jupyter kernelspec list + - pytest --cov ipykernel --durations 10 -v ipykernel after_success: - - codecov + - codecov matrix: - allow_failures: - - python: "nightly" + allow_failures: + - python: "nightly" From 1bdff212eda790b08113862f4923735a58fe3ff5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 16:02:03 +0200 Subject: [PATCH 0228/1195] test with tornado 4.5 which has different asyncio integration (specifically, not on asyncio by default) --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index a35cccc8f..45b0b12af 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,11 @@ install: if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then pip install matplotlib fi + - | + # pin tornado + if [[ ! -z "$TORNADO" ]]; then + pip install tornado=="$TORNADO" + fi - pip freeze script: - jupyter kernelspec list @@ -24,5 +29,9 @@ script: after_success: - codecov matrix: + include: + - python: 3.5 + env: + - TORNADO="4.5.*" allow_failures: - python: "nightly" From 7deb94bea24a4ab69816e24d6d635884fd29d2f9 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 16:33:40 +0200 Subject: [PATCH 0229/1195] test asyncio integration --- .travis.yml | 2 +- ipykernel/tests/test_async.py | 75 +++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 ipykernel/tests/test_async.py diff --git a/.travis.yml b/.travis.yml index 45b0b12af..6158c8b48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ install: - | # install matplotlib if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then - pip install matplotlib + pip install matplotlib curio trio fi - | # pin tornado diff --git a/ipykernel/tests/test_async.py b/ipykernel/tests/test_async.py new file mode 100644 index 000000000..5ee73d76c --- /dev/null +++ b/ipykernel/tests/test_async.py @@ -0,0 +1,75 @@ +"""Test async/await integration""" + +from distutils.version import LooseVersion as V +import sys + +import pytest +import IPython + + +from .utils import execute, flush_channels, start_new_kernel, TIMEOUT +from .test_message_spec import validate_message + + +KC = KM = None + + +def setup(): + """start the global kernel (if it isn't running) and return its client""" + global KM, KC + KM, KC = start_new_kernel() + flush_channels(KC) + + +def teardown(): + KC.stop_channels() + KM.shutdown_kernel(now=True) + + +skip_without_async = pytest.mark.skipif( + sys.version_info < (3, 5) or V(IPython.__version__) < V("7.0"), + reason="IPython >=7 with async/await required", +) + + +@skip_without_async +def test_async_await(): + flush_channels(KC) + msg_id, content = execute("import asyncio; await asyncio.sleep(0.1)", KC) + assert content["status"] == "ok", content + + +@pytest.mark.parametrize("asynclib", ["asyncio", "trio", "curio"]) +@skip_without_async +def test_async_interrupt(asynclib, request): + asynclib = "asyncio" + try: + __import__(asynclib) + except ImportError: + pytest.skip("Requires %s" % asynclib) + request.addfinalizer(lambda: execute("%autoawait asyncio", KC)) + + flush_channels(KC) + msg_id, content = execute("%autoawait " + asynclib, KC) + assert content["status"] == "ok", content + + flush_channels(KC) + msg_id = KC.execute( + "print('begin'); import {0}; await {0}.sleep(5)".format(asynclib) + ) + busy = KC.get_iopub_msg(timeout=TIMEOUT) + validate_message(busy, "status", msg_id) + assert busy["content"]["execution_state"] == "busy" + echo = KC.get_iopub_msg(timeout=TIMEOUT) + validate_message(echo, "execute_input") + stream = KC.get_iopub_msg(timeout=TIMEOUT) + # wait for the stream output to be sure kernel is in the async block + validate_message(stream, "stream") + assert stream["content"]["text"] == "begin\n" + + KM.interrupt_kernel() + reply = KC.get_shell_msg()["content"] + assert reply["status"] == "error", reply + assert reply["ename"] == "CancelledError" + + flush_channels(KC) From 164a62b64c108c380ca7710623d7592a9a817348 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 16:38:44 +0200 Subject: [PATCH 0230/1195] test with IPython master --- .travis.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.travis.yml b/.travis.yml index 6158c8b48..12523a4b2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,16 @@ install: if [[ ! -z "$TORNADO" ]]; then pip install tornado=="$TORNADO" fi + - | + # pin IPython + if [[ ! -z "$IPYTHON" ]]; then + if [[ "$IPYTHON" == "master" ]]; then + SPEC=git+https://github.com/ipython/ipython#egg=ipython + else + SPEC="ipython==$IPYTHON" + fi + pip install --pre "$SPEC" + fi - pip freeze script: - jupyter kernelspec list @@ -33,5 +43,9 @@ matrix: - python: 3.5 env: - TORNADO="4.5.*" + - IPYTHON=master + - python: 3.6 + env: + - IPYTHON=master allow_failures: - python: "nightly" From e3736c6ab049b0249b47ed0d1073ce32187e20da Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 16:49:05 +0200 Subject: [PATCH 0231/1195] skip asyncio eventloop test with tornado 4 it's only relevant on tornado 5 where asyncio is already running --- ipykernel/tests/test_eventloop.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index c487ee50b..a44907f10 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -1,9 +1,10 @@ """Test eventloop integration""" import sys -import time -import IPython.testing.decorators as dec +import pytest +import tornado + from .utils import flush_channels, start_new_kernel, execute KC = KM = None @@ -27,7 +28,8 @@ def teardown(): """ -@dec.skipif(sys.version_info < (3, 5), "async/await syntax required") +@pytest.mark.skipif(sys.version_info < (3, 5), reason="async/await syntax required") +@pytest.mark.skipif(tornado.version_info < (5,), reason="only relevant on tornado 5") def test_asyncio_interrupt(): flush_channels(KC) msg_id, content = execute('%gui asyncio', KC) From ef6974b798a5ea1189826294f4e4312653278aa6 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 16:50:51 +0200 Subject: [PATCH 0232/1195] blocking runners raise KeyboardInterrupt not CancelledError, which is only for the true async runs --- ipykernel/tests/test_async.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/test_async.py b/ipykernel/tests/test_async.py index 5ee73d76c..46ef78068 100644 --- a/ipykernel/tests/test_async.py +++ b/ipykernel/tests/test_async.py @@ -70,6 +70,6 @@ def test_async_interrupt(asynclib, request): KM.interrupt_kernel() reply = KC.get_shell_msg()["content"] assert reply["status"] == "error", reply - assert reply["ename"] == "CancelledError" + assert reply["ename"] in {"CancelledError", "KeyboardInterrupt"} flush_channels(KC) From d062f49c51a201803aeed8fc47522a86940fceed Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 Sep 2018 17:09:17 +0200 Subject: [PATCH 0233/1195] upgrade IPython master needed to get a later version --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 12523a4b2..794e2128b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,7 +30,7 @@ install: else SPEC="ipython==$IPYTHON" fi - pip install --pre "$SPEC" + pip install --upgrade --pre "$SPEC" fi - pip freeze script: From c1230aae30c5ab28691c8636dc55c2f3778d8add Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 9 Sep 2018 20:20:23 +0200 Subject: [PATCH 0234/1195] Fix test of set_next_input Test of set_next input were testing not only that several set_next_input were not set several time; but was doing so via the side effect that `?` was triggering set_next_input. Use directly ip.set_next_input to not rely on the behavior or `?` which anyway is buggy --- ipykernel/tests/test_message_spec.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index ebf9d9237..f22dc173f 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -465,8 +465,9 @@ def test_comm_info_request(): def test_single_payload(): flush_channels() - msg_id, reply = execute(code="for i in range(3):\n"+ - " x=range?\n") + msg_id, reply = execute(code="ip = get_ipython()\n" + "for i in range(3):\n" + " ip.set_next_input('Hello There')\n") payload = reply['payload'] next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"] assert len(next_input_pls) == 1 From 950ab876585fd8e1ec852d7db464d9f6cae59e0f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 9 Sep 2018 20:28:59 +0200 Subject: [PATCH 0235/1195] document why of not multiple set_next_input--amend --- ipykernel/tests/test_message_spec.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index f22dc173f..e75e219c6 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -464,6 +464,15 @@ def test_comm_info_request(): def test_single_payload(): + """ + We want to test the set_next_input is not triggered several time per cell. + This is (was ?) mostly due to the fact that `?` in a loop would trigger + several set_next_input. + + I'm tempted to thing that we actually want to _allow_ multiple + set_next_input (that's users' choice). But that `?` itself (and ?'s + transform) should avoid setting multiple set_next_input). + """ flush_channels() msg_id, reply = execute(code="ip = get_ipython()\n" "for i in range(3):\n" From 2b10aa1721afb05b56dd9ec84520cbb548cff58c Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 10 Sep 2018 10:55:27 +0200 Subject: [PATCH 0236/1195] unpin asyncio in async kernel test --- ipykernel/tests/test_async.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ipykernel/tests/test_async.py b/ipykernel/tests/test_async.py index 46ef78068..01b022369 100644 --- a/ipykernel/tests/test_async.py +++ b/ipykernel/tests/test_async.py @@ -42,7 +42,6 @@ def test_async_await(): @pytest.mark.parametrize("asynclib", ["asyncio", "trio", "curio"]) @skip_without_async def test_async_interrupt(asynclib, request): - asynclib = "asyncio" try: __import__(asynclib) except ImportError: From 3bd4bf53656563f490d38d15f681b54f9cc11481 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 12 Sep 2018 13:30:29 +0200 Subject: [PATCH 0237/1195] Update version str to be pep 440 complient, start beta1 --- docs/changelog.rst | 11 +++++++++++ ipykernel/_version.py | 15 +++++++++++++-- setup.py | 9 ++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index b213928a5..e5c18272e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,17 @@ Changes in IPython kernel ========================= +5.0 +--- + +5.0.0 +***** + +`4.9.0 on GitHub `__ + +- Add Support for IPython's Asynchronous code execution (:ghpull:`323`) +- Update release Process in ``Contributing.md`` (:ghpull:`339`) + 4.9 --- diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 21c09a70c..b4da91147 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,5 +1,16 @@ -version_info = (5, 0, 0, 'dev') -__version__ = '.'.join(map(str, version_info)) +version_info = (5, 0, 0, 'b1') +__version__ = '.'.join(map(str, version_info[:3])) + +# pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools +# confuses which one between the wheel and sdist is the most recent. +if len(version_info) == 4: + extra = version_info[3] + if extra.startswith(('a','b','rc')): + __version__ = __version__+extra + else: + __version__ = __version__+'.'+extra +if len(version_info) > 4: + raise NotImplementedError kernel_protocol_version_info = (5, 1) kernel_protocol_version = '%s.%s' % kernel_protocol_version_info diff --git a/setup.py b/setup.py index f76807d4d..153f5bafb 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,7 @@ #----------------------------------------------------------------------------- import sys +import re v = sys.version_info if v[:2] < (3, 4): @@ -60,10 +61,16 @@ def run(self): with open(pjoin(here, name, '_version.py')) as f: exec(f.read(), {}, version_ns) +current_version = version_ns['__version__'] + +loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$') +if not loose_pep440re.match(current_version): + raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % current_version) + setup_args = dict( name=name, - version=version_ns['__version__'], + version=current_version, cmdclass={ 'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled, }, From effbe9106352981492eef34692797a3c28dd121f Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 14 Sep 2018 10:05:26 +0200 Subject: [PATCH 0238/1195] typos in changelog --- docs/changelog.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index e5c18272e..d461b14c7 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -7,10 +7,10 @@ Changes in IPython kernel 5.0.0 ***** -`4.9.0 on GitHub `__ +`5.0.0 on GitHub `__ -- Add Support for IPython's Asynchronous code execution (:ghpull:`323`) -- Update release Process in ``Contributing.md`` (:ghpull:`339`) +- Add support for IPython's asynchronous code execution (:ghpull:`323`) +- Update release process in ``CONTRIBUTING.md`` (:ghpull:`339`) 4.9 --- From 9b52bfd911a7e1a396bdfd403c854cf2832cc6a5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 14 Sep 2018 10:11:33 +0200 Subject: [PATCH 0239/1195] add Python support change to changelog --- docs/changelog.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index d461b14c7..800d63d29 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,9 +9,11 @@ Changes in IPython kernel `5.0.0 on GitHub `__ +- Drop support for Python 2. ``ipykernel`` 5.0 requires Python >= 3.4 - Add support for IPython's asynchronous code execution (:ghpull:`323`) - Update release process in ``CONTRIBUTING.md`` (:ghpull:`339`) + 4.9 --- From ab24cf94beba934ad1d41c91d9c3c4f18b202bea Mon Sep 17 00:00:00 2001 From: "Jens H. Nielsen" Date: Thu, 20 Sep 2018 10:46:46 +0200 Subject: [PATCH 0240/1195] Check that sys.stdout/err is not None before flushing On windows this has been observed to be None at startup https://github.com/spyder-ide/spyder/issues/7842 --- ipykernel/kernelapp.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index f0ca68bfb..6e63053f6 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -323,7 +323,8 @@ def init_io(self): """Redirect input streams and set a display hook.""" if self.outstream_class: outstream_factory = import_item(str(self.outstream_class)) - sys.stdout.flush() + if sys.stdout is not None: + sys.stdout.flush() e_stdout = None if self.quiet else sys.__stdout__ e_stderr = None if self.quiet else sys.__stderr__ @@ -331,7 +332,8 @@ def init_io(self): sys.stdout = outstream_factory(self.session, self.iopub_thread, u'stdout', echo=e_stdout) - sys.stderr.flush() + if sys.stderr is not None: + sys.stderr.flush() sys.stderr = outstream_factory(self.session, self.iopub_thread, u'stderr', echo=e_stderr) From 0b5803289ca5739541c1728a4ac6fe55caf96fcd Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 25 Sep 2018 09:41:45 -0700 Subject: [PATCH 0241/1195] Remove usage of deprecated utils. IPython.utils.io.raw_print* have been deprected. Do not use, and print directly to __stdout__, flushing if end is not a new line. --- ipykernel/eventloops.py | 5 ++--- ipykernel/inprocess/blocking.py | 6 +++--- ipykernel/kernelapp.py | 5 ++--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 7d3d32365..5e93061fe 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -13,7 +13,6 @@ from distutils.version import LooseVersion as V from traitlets.config.application import Application -from IPython.utils import io def _use_appnope(): @@ -299,7 +298,7 @@ def handle_int(etype, value, tb): # wake the eventloop when we get a signal stop() if etype is KeyboardInterrupt: - io.raw_print("KeyboardInterrupt caught in CFRunLoop") + print("KeyboardInterrupt caught in CFRunLoop", file=sys.__stdout__) else: real_excepthook(etype, value, tb) @@ -319,7 +318,7 @@ def handle_int(etype, value, tb): raise except KeyboardInterrupt: # Ctrl-C shouldn't crash the kernel - io.raw_print("KeyboardInterrupt caught in kernel") + print("KeyboardInterrupt caught in kernel", file=sys.__stdout__) finally: # ensure excepthook is restored sys.excepthook = real_excepthook diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index 7cc3e100f..983694034 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -8,14 +8,13 @@ # Distributed under the terms of the BSD License. The full license is in # the file COPYING.txt, distributed as part of this software. #----------------------------------------------------------------------------- - +import sys try: from queue import Queue, Empty # Py 3 except ImportError: from Queue import Queue, Empty # Py 2 # IPython imports -from IPython.utils.io import raw_print from traitlets import Type # Local imports @@ -66,7 +65,8 @@ def call_handlers(self, msg): if msg_type == 'input_request': _raw_input = self.client.kernel._sys_raw_input prompt = msg['content']['prompt'] - raw_print(prompt, end='') + print(prompt, end='', file=sys.__stdout__) + sys.__stdout__.flush() self.client.input(_raw_input()) class BlockingInProcessKernelClient(InProcessKernelClient): diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 6e63053f6..7ae816a68 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -24,7 +24,6 @@ from IPython.core.shellapp import ( InteractiveShellApp, shell_flags, shell_aliases ) -from IPython.utils import io from ipython_genutils.path import filefind, ensure_dir_exists from traitlets import ( Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type, default @@ -302,9 +301,9 @@ def log_connection_info(self): # also raw print to the terminal if no parent_handle (`ipython kernel`) # unless log-level is CRITICAL (--quiet) if not self.parent_handle and self.log_level < logging.CRITICAL: - io.raw_print(_ctrl_c_message) + print(_ctrl_c_message, file=sys.__stdout__) for line in lines: - io.raw_print(line) + print(line, file=sys.__stdout__) self.ports = dict(shell=self.shell_port, iopub=self.iopub_port, stdin=self.stdin_port, hb=self.hb_port, From 6625663e9a8904949b10fbf9593e653fd727d964 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Tue, 28 Jun 2016 17:53:25 +0100 Subject: [PATCH 0242/1195] Remove colors_force flag See ipython/ipython#9673. This will disable colours if ipykernel is installed with IPython <5, so I'm bumping the IPython requirement in setup.py. So this probably can't be merged until after IPython 5 is released. --- ipykernel/zmqshell.py | 1 - setup.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index a76240f61..af19c7487 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -453,7 +453,6 @@ def _default_banner1(self): # Override the traitlet in the parent class, because there's no point using # readline for the kernel. Can be removed when the readline code is moved # to the terminal frontend. - colors_force = CBool(True) readline_use = CBool(False) # autoindent has no meaning in a zmqshell, and attempting to enable it # will print a warning in the absence of readline. diff --git a/setup.py b/setup.py index 153f5bafb..079570a37 100644 --- a/setup.py +++ b/setup.py @@ -88,7 +88,7 @@ def run(self): keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], python_requires='>=3.4', install_requires=[ - 'ipython>=4.0.0', + 'ipython>=5.0.0', 'traitlets>=4.1.0', 'jupyter_client', 'tornado>=4.2', From 309c8926d9f1cf0d54924726e02681f271e8e67f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 26 Sep 2018 18:19:40 -0700 Subject: [PATCH 0243/1195] release 5.0.0 --- CONTRIBUTING.md | 6 ++++-- ipykernel/_version.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 70def38d9..2be71db2b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,9 @@ Releasing ipykernel is *almost* standard for a Python package: - set version back to development The one extra step for ipykernel is that we need to make separate wheels for Python 2 and 3 -because the bundled kernelspec has different contents for Python 2 and 3. +because the bundled kernelspec has different contents for Python 2 and 3. This +affects only the 4.x branch of ipykernel as the 5+ version is only compatible +Python 3. The full release process is available below: @@ -49,7 +51,7 @@ git push --tags pip install --upgrade twine git clean -xfd python3 setup.py sdist bdist_wheel -python2 setup.py bdist_wheel # the extra step! +python2 setup.py bdist_wheel # the extra step for the 4.x branch. twine upload dist/* # set the version back to '.dev' in ipykernel/_version.py diff --git a/ipykernel/_version.py b/ipykernel/_version.py index b4da91147..2b5d7cba4 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 0, 0, 'b1') +version_info = (5, 0, 0) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 86d24956a13e39669c4be30784dd1b1e3db662cf Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 26 Sep 2018 18:23:10 -0700 Subject: [PATCH 0244/1195] back to 5.1.0.dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 2b5d7cba4..9d87b082d 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 0, 0) +version_info = (5, 1, 0, 'dev') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From dec3fd21486eed18e83675b800a9b4aa52d553c3 Mon Sep 17 00:00:00 2001 From: Martha Giannoudovardi Date: Thu, 27 Sep 2018 12:13:13 +0100 Subject: [PATCH 0245/1195] Remove references to python 2.7 This is to reflect that support for python < 3.4 was dropped. --- setup.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/setup.py b/setup.py index 079570a37..1c72d6069 100644 --- a/setup.py +++ b/setup.py @@ -94,7 +94,6 @@ def run(self): 'tornado>=4.2', ], extras_require={ - 'test:python_version=="2.7"': ['mock'], 'test': [ 'pytest', 'pytest-cov', @@ -107,7 +106,6 @@ def run(self): 'Intended Audience :: Science/Research', 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', ], ) From 219659f27e3e6c592370d0419a494d447e9b95c8 Mon Sep 17 00:00:00 2001 From: Ahmed Amin Date: Mon, 1 Oct 2018 02:33:12 +0200 Subject: [PATCH 0246/1195] removed deadcode for context --- ipykernel/eventloops.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 5e93061fe..e43a86f3d 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -27,14 +27,6 @@ def _notify_stream_qt(kernel, stream): from IPython.external.qt_for_kernel import QtCore - if _use_appnope() and kernel._darwin_app_nap: - from appnope import nope_scope as context - else: - from contextlib import contextmanager - @contextmanager - def context(): - yield - def process_stream_events(): """fall back to main loop when there's a socket event""" # call flush to ensure that the stream doesn't lose events From c562f46ed20fa499909d51360528223d800841e3 Mon Sep 17 00:00:00 2001 From: Nathan Morrical Date: Fri, 5 Oct 2018 09:57:18 -0600 Subject: [PATCH 0247/1195] Handling signal exceptions, which previously broke ipython kernel when ran in a separate thread. --- ipykernel/kernelapp.py | 8 +++++++- ipykernel/kernelbase.py | 10 ++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 7ae816a68..e416e982c 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -474,7 +474,13 @@ def initialize(self, argv=None): # file is definitely available at the time someone reads the log. self.log_connection_info() self.init_io() - self.init_signal() + try: + self.init_signal() + except: + # Catch exception when initializing signal fails, eg when running the + # kernel on a separate thread + if self.log_level < logging.CRITICAL: + self.log.error("Unable to initialize signal:", exc_info=True) self.init_kernel() # shell init steps self.init_path() diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index ef280e653..3f39b44e6 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -254,13 +254,19 @@ def dispatch_shell(self, stream, msg): self.log.warning("Unknown message type: %r", msg_type) else: self.log.debug("%s: %s", msg_type, msg) - self.pre_handler_hook() + try: + self.pre_handler_hook() + except Exception: + self.log.debug("Unable to signal in pre_handler_hook:", exc_info=True) try: yield gen.maybe_future(handler(stream, idents, msg)) except Exception: self.log.error("Exception in message handler:", exc_info=True) finally: - self.post_handler_hook() + try: + self.post_handler_hook() + except Exception: + self.log.debug("Unable to signal in post_handler_hook:", exc_info=True) sys.stdout.flush() sys.stderr.flush() From 4120a24549c1e98e83020603183f18f28dd4afe5 Mon Sep 17 00:00:00 2001 From: Tony Fast Date: Sun, 7 Oct 2018 12:02:52 -0400 Subject: [PATCH 0248/1195] Let do_inspect return text/html when enable_html_pager is available. --- ipykernel/ipkernel.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 5144a015d..1a3b92f66 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -400,18 +400,22 @@ def _experimental_do_complete(self, code, cursor_pos): def do_inspect(self, code, cursor_pos, detail_level=0): name = token_at_cursor(code, cursor_pos) - info = self.shell.object_inspect(name) reply_content = {'status' : 'ok'} - reply_content['data'] = data = {} + reply_content['data'] = {} reply_content['metadata'] = {} - reply_content['found'] = info['found'] - if info['found']: - info_text = self.shell.object_inspect_text( - name, - detail_level=detail_level, + try: + reply_content['data'].update( + self.shell.object_inspect_mime( + name, + detail_level=detail_level + ) ) - data['text/plain'] = info_text + if not self.shell.enable_html_pager: + reply_content['data'].pop('text/html') + reply_content['found'] = True + except KeyError: + reply_content['found'] = False return reply_content From 9e669009752d4215dec2b129787ea83133b1cf09 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 8 Oct 2018 11:05:25 +0200 Subject: [PATCH 0249/1195] use integer counter instead of loop.time() to preserve message ordering - avoids need to truncate comparison because it will always be unique - avoids sorting issues with low-resolution timers on Windows --- ipykernel/kernelbase.py | 45 ++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index ef280e653..b86479cff 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -7,6 +7,7 @@ from datetime import datetime from functools import partial +import itertools import logging from signal import signal, default_int_handler, SIGINT import sys @@ -44,18 +45,6 @@ SHELL_PRIORITY = 10 ABORT_PRIORITY = 20 -class _MessageEvent(tuple): - """class for priority message events - - ensures that comparison only invokes the priority entry, - not comparing the contents of the messages - """ - def __eq__(self, other): - return self[:2] == other[:2] - - def __lt__(self, other): - return self[:2] < other[:2] - class Kernel(SingletonConfigurable): @@ -360,20 +349,30 @@ def dispatch_queue(self): except Exception: self.log.exception("Error in message handler") + _message_counter = Any( + help="""Monotonic counter of messages + + Ensures messages of the same priority are handled in arrival order. + """, + ) + @default('_message_counter') + def _message_counter_default(self): + return itertools.count() + def schedule_dispatch(self, priority, dispatch, *args): """schedule a message for dispatch""" - # via loop.add_callback to ensure everything gets scheduled - # on the eventloop - self.io_loop.add_callback( - lambda: self.msg_queue.put( - _MessageEvent(( - priority, - self.io_loop.time(), - dispatch, - args, - )) - ) + idx = next(self._message_counter) + + self.msg_queue.put_nowait( + ( + priority, + idx, + dispatch, + args, + ) ) + # ensure the eventloop wakes up + self.io_loop.add_callback(lambda: None) def start(self): """register dispatchers for streams""" From 6ee942dfaefa42ffb8ba14bb6a8137bf537ef85b Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 8 Oct 2018 11:25:58 +0200 Subject: [PATCH 0250/1195] verify execution-message ordering --- ipykernel/tests/test_kernel.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index fa3949131..3d4ce44e0 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -18,7 +18,8 @@ from .utils import ( new_kernel, kernel, TIMEOUT, assemble_output, execute, - flush_channels, wait_for_idle) + flush_channels, wait_for_idle, +) def _check_master(kc, expected=True, stream="stdout"): @@ -273,6 +274,25 @@ def test_matplotlib_inline_on_import(): assert 'backend_inline' in backend_bundle['data']['text/plain'] +def test_message_order(): + N = 100 # number of messages to test + with kernel() as kc: + _, reply = execute("a = 1", kc=kc) + _check_status(reply) + offset = reply['execution_count'] + 1 + cell = "a += 1\na" + msg_ids = [] + # submit N executions as fast as we can + for i in range(N): + msg_ids.append(kc.execute(cell)) + # check message-handling order + for i, msg_id in enumerate(msg_ids, offset): + reply = kc.get_shell_msg(timeout=TIMEOUT) + _check_status(reply['content']) + assert reply['content']['execution_count'] == i + assert reply['parent_header']['msg_id'] == msg_id + + def test_shutdown(): """Kernel exits after polite shutdown_request""" with new_kernel() as kc: From 207ca08746eff039175f2e1ea8b4abb8baa4357e Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 8 Oct 2018 12:54:12 +0200 Subject: [PATCH 0251/1195] changelog for 5.1 includes missing changelog for 4.10 --- docs/changelog.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 800d63d29..f5039f01e 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,23 @@ Changes in IPython kernel ========================= +5.1 +--- + +5.1.0 +***** + +5.1.0 fixes some important regressions in 5.0, especially on Windows. + +`5.1.0 on GitHub `__ + +- Fix message-ordering bug that could result in out-of-order executions, + especially on Windows (:ghpull:`356`) +- Fix classifiers to indicate dropped Python 2 support (:ghpull:`354`) +- Remove some dead code (:ghpull:`355`) +- Support rich-media responses in ``inspect_requests`` (tooltips) (:ghpull:`361`) + + 5.0 --- @@ -14,6 +31,14 @@ Changes in IPython kernel - Update release process in ``CONTRIBUTING.md`` (:ghpull:`339`) +4.10 +---- + +`4.10 on GitHub `__ + +- Fix compatibility with IPython 7.0 (:ghpull:`348`) +- Fix compatibility in cases where sys.stdout can be None (:ghpull:`344`) + 4.9 --- From f2a1ff817aaa1badc0986366f4593c5cebbcf3cd Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 9 Oct 2018 11:01:09 +0200 Subject: [PATCH 0252/1195] make Kernel.stop_on_error_timeout configurable and increase default from 50 to 100 milliseconds due to reports of messages not being cancelled --- ipykernel/kernelbase.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index b86479cff..38d4696ad 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -113,6 +113,22 @@ def _default_ident(self): # adapt to milliseconds. _poll_interval = Float(0.01).tag(config=True) + stop_on_error_timeout = Float( + 0.1, + config=True, + help="""time (in seconds) to wait for messages to arrive + when aborting queued requests after an error. + + Requests that arrive within this window after an error + will be cancelled. + + Increase in the event of unusually slow network + causing significant delays, + which can manifest as e.g. "Run all" in a notebook + aborting some, but not all, messages after an error. + """ + ) + # If the shutdown was requested over the network, we leave here the # necessary reply message so it can be sent by our registered atexit # handler. This ensures that the reply is only sent to clients truly at @@ -770,7 +786,7 @@ def _abort_queues(self): @gen.coroutine def _dispatch_abort(self): self.log.info("Finishing abort") - yield gen.sleep(0.05) + yield gen.sleep(self.stop_on_error_timeout) self._aborting = False @gen.coroutine From cddb2690acf013e784e5c65f8ba5845c848f5519 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 9 Oct 2018 15:02:51 +0200 Subject: [PATCH 0253/1195] release 5.1.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 9d87b082d..363ce3d58 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 1, 0, 'dev') +version_info = (5, 1, 0) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 35b1d1a823dd22a0fe4eb99e89463c2527a11271 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 9 Oct 2018 15:03:46 +0200 Subject: [PATCH 0254/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 363ce3d58..efe484991 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 1, 0) +version_info = (5, 2, 0, 'dev') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 8dc46a85fd664c55977cedf2700e9d66ac61df7b Mon Sep 17 00:00:00 2001 From: Ray Osborn Date: Tue, 4 Dec 2018 17:15:21 -0600 Subject: [PATCH 0255/1195] Replace function for in-process kernel compatibility From v5.0.0, the default IPythonKernel contains an _abort_queues function invoked when a shell execute request generates an exception. This schedules a message, which clears then clears the abort. This process does not work in the InProcessKernel. After an exception, all shell execute requests return an "ERROR: execution aborted" message. This is fixed by overriding the _abort_queues function in the InProcessKernel. --- ipykernel/inprocess/ipkernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index d4990ed92..8363709e5 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -83,7 +83,7 @@ def start(self): """ Override registration of dispatchers for streams. """ self.shell.exit_now = False - def _abort_queue(self, stream): + def _abort_queues(self): """ The in-process kernel doesn't abort requests. """ pass From 4122099d2be435c284f7b0b4a61e252d55b2c56c Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 15 Feb 2019 10:44:07 +0100 Subject: [PATCH 0256/1195] update kernel tests for IPython upgrades - '' is inserted to sys.path after the start in IPython 7.2 - completion start point is flexible, so verify the spec rather than the value --- ipykernel/tests/test_kernel.py | 44 ++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 3d4ce44e0..96db617ce 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -4,6 +4,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import ast import io import os.path import sys @@ -52,17 +53,27 @@ def test_simple_print(): def test_sys_path(): """test that sys.path doesn't get messed up by default""" with kernel() as kc: - msg_id, content = execute(kc=kc, code="import sys; print (repr(sys.path[0]))") + msg_id, content = execute(kc=kc, code="import sys; print(repr(sys.path))") stdout, stderr = assemble_output(kc.iopub_channel) - assert stdout == "''\n" + # for error-output on failure + sys.stderr.write(stderr) + + sys_path = ast.literal_eval(stdout.strip()) + assert '' in sys_path + def test_sys_path_profile_dir(): """test that sys.path doesn't get messed up when `--profile-dir` is specified""" with new_kernel(['--profile-dir', locate_profile('default')]) as kc: - msg_id, content = execute(kc=kc, code="import sys; print (repr(sys.path[0]))") + msg_id, content = execute(kc=kc, code="import sys; print(repr(sys.path))") stdout, stderr = assemble_output(kc.iopub_channel) - assert stdout == "''\n" + # for error-output on failure + sys.stderr.write(stderr) + + sys_path = ast.literal_eval(stdout.strip()) + assert '' in sys_path + @dec.skipif(sys.platform == 'win32', "subprocess prints fail on Windows") def test_subprocess_print(): @@ -248,14 +259,23 @@ def test_complete(): cell = 'import IPython\nb = a.' kc.complete(cell) reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) - c = reply['content'] - assert c['status'] == 'ok' - assert c['cursor_start'] == cell.find('a.') - assert c['cursor_end'] == cell.find('a.') + 2 - matches = c['matches'] - nt.assert_greater(len(matches), 0) - for match in matches: - assert match[:2] == 'a.' + + c = reply['content'] + assert c['status'] == 'ok' + start = cell.find('a.') + end = start + 2 + assert c['cursor_end'] == cell.find('a.') + 2 + assert c['cursor_start'] <= end + + # there are many right answers for cursor_start, + # so verify application of the completion + # rather than the value of cursor_start + + matches = c['matches'] + assert matches + for m in matches: + completed = cell[:c['cursor_start']] + m + assert completed.startswith(cell) @dec.skip_without('matplotlib') From c7f1fecbf1d957853662dcf5469300b5bc056197 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 15 Feb 2019 14:25:27 +0100 Subject: [PATCH 0257/1195] pip eager upgrade strategy because pip doesn't solve dependencies correctly --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 794e2128b..157fcae2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,8 +10,7 @@ install: - | # pip install pip install --upgrade setuptools pip - pip install --pre . - pip install ipykernel[test] codecov + pip install --pre --upgrade --upgrade-strategy=eager .[test] codecov - | # install matplotlib if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then From 356cfdb51b30185e8be90e5722ea7f1dc8238b20 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 18 Feb 2019 14:15:58 +0100 Subject: [PATCH 0258/1195] send_abort_reply is not a coroutine --- ipykernel/kernelbase.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 176350170..2355ef955 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -795,7 +795,6 @@ def _dispatch_abort(self): yield gen.sleep(self.stop_on_error_timeout) self._aborting = False - @gen.coroutine def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" self.log.info("Aborting:") From fca430360b028cedd236d33e9428630ccfb466a3 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 20 Feb 2019 16:20:25 +0100 Subject: [PATCH 0259/1195] flush after sending replies to ensure that the reply is actually sent before we handle the next request --- ipykernel/kernelbase.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 2355ef955..0fbfebe53 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -205,6 +205,8 @@ def dispatch_control(self, msg): sys.stdout.flush() sys.stderr.flush() self._publish_status(u'idle') + # flush to ensure reply is sent + self.control_stream.flush(zmq.POLLOUT) def should_handle(self, stream, msg, idents): """Check whether a shell-channel message should be handled @@ -241,6 +243,9 @@ def dispatch_shell(self, stream, msg): if self._aborting: self._send_abort_reply(stream, msg, idents) self._publish_status(u'idle') + # flush to ensure reply is sent before + # handling the next request + stream.flush(zmq.POLLOUT) return msg_type = msg['header']['msg_type'] @@ -276,6 +281,9 @@ def dispatch_shell(self, stream, msg): sys.stdout.flush() sys.stderr.flush() self._publish_status(u'idle') + # flush to ensure reply is sent before + # handling the next request + stream.flush(zmq.POLLOUT) def pre_handler_hook(self): """Hook to execute before calling message handler""" From a01c28ae4c227a3a522316057c36d869d720111e Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 26 Mar 2019 15:52:50 +0100 Subject: [PATCH 0260/1195] ipykernel implements protocol v5.3 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index efe484991..ca224aba0 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -12,5 +12,5 @@ if len(version_info) > 4: raise NotImplementedError -kernel_protocol_version_info = (5, 1) +kernel_protocol_version_info = (5, 3) kernel_protocol_version = '%s.%s' % kernel_protocol_version_info From d3481991547e00222d488eb05f9f1fe653b8c4d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 9 Apr 2019 12:33:58 +0200 Subject: [PATCH 0261/1195] Add Python 3.8 to Travis CI --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 157fcae2e..be4545103 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,13 @@ language: python python: - "nightly" - - "3.7-dev" + - "3.8-dev" + - 3.7 - 3.6 - 3.5 - 3.4 sudo: false +dist: xenial install: - | # pip install From d687d8404c4bbe00e0c758522042b6d06679bb88 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 16 May 2019 03:02:11 -0500 Subject: [PATCH 0262/1195] Release 5.1.1 --- docs/changelog.rst | 7 +++++++ ipykernel/_version.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index f5039f01e..7281388dd 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,13 @@ Changes in IPython kernel 5.1 --- +5.1.1 +***** +5.1.1 fixes a bug that caused cells to get stuck in a busy state. + +- Flush after sending replies (:ghpull:`390`) + + 5.1.0 ***** diff --git a/ipykernel/_version.py b/ipykernel/_version.py index efe484991..1db42b17c 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 2, 0, 'dev') +version_info = (5, 1, 1) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From b81632e0a26671e6b100471282cf49ca4a21e681 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 16 May 2019 03:03:56 -0500 Subject: [PATCH 0263/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 1db42b17c..efe484991 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 1, 1) +version_info = (5, 2, 0, 'dev') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From c3f6e614eca6bd1a201e441ece79168d3d44605a Mon Sep 17 00:00:00 2001 From: "Philipp S. Sommer" Date: Sun, 26 May 2019 20:48:25 +0200 Subject: [PATCH 0264/1195] only flush stream in dispatch_shell if possible --- ipykernel/kernelbase.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 0fbfebe53..cc5c9d03b 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -245,7 +245,10 @@ def dispatch_shell(self, stream, msg): self._publish_status(u'idle') # flush to ensure reply is sent before # handling the next request - stream.flush(zmq.POLLOUT) + try: + stream.flush(zmq.POLLOUT) + except AttributeError: + pass return msg_type = msg['header']['msg_type'] @@ -283,7 +286,10 @@ def dispatch_shell(self, stream, msg): self._publish_status(u'idle') # flush to ensure reply is sent before # handling the next request - stream.flush(zmq.POLLOUT) + try: + stream.flush(zmq.POLLOUT) + except AttributeError: + pass def pre_handler_hook(self): """Hook to execute before calling message handler""" From bb55e7e3f61629d331924b9280d7c5159132480d Mon Sep 17 00:00:00 2001 From: Chilipp Date: Tue, 28 May 2019 13:53:44 +0200 Subject: [PATCH 0265/1195] Trigger rebuild on Travis From e0dd55f53163e7a09b2afa484d526da3b038ccab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= Date: Tue, 28 May 2019 14:15:34 +0200 Subject: [PATCH 0266/1195] Python 3.8: PEP 570 -- Python Positional-Only Parameters https://www.python.org/dev/peps/pep-0570/ Fixes https://github.com/ipython/ipykernel/issues/406 --- ipykernel/codeutil.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/codeutil.py b/ipykernel/codeutil.py index 3946a98a2..bac32bed0 100644 --- a/ipykernel/codeutil.py +++ b/ipykernel/codeutil.py @@ -33,6 +33,8 @@ def reduce_code(co): co.co_lnotab, co.co_freevars, co.co_cellvars] if sys.version_info[0] >= 3: args.insert(1, co.co_kwonlyargcount) + if sys.version_info > (3, 8, 0, 'alpha', 3): + args.insert(1, co.co_posonlyargcount) return code_ctor, tuple(args) copyreg.pickle(types.CodeType, reduce_code) From 846e1db178a354a279aa45e5090ad5fab9ec806c Mon Sep 17 00:00:00 2001 From: Dan Davison Date: Sun, 2 Jun 2019 22:07:31 +0300 Subject: [PATCH 0267/1195] Use shell.input_transformer_manager when available --- ipykernel/ipkernel.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 1a3b92f66..8031f1dd6 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -445,7 +445,11 @@ def do_shutdown(self, restart): return dict(status='ok', restart=restart) def do_is_complete(self, code): - status, indent_spaces = self.shell.input_splitter.check_complete(code) + transformer_manager = getattr(self.shell, 'input_transformer_manager', None) + if transformer_manager is None: + # input_splitter attribute is deprecated + transformer_manager = self.shell.input_splitter + status, indent_spaces = transformer_manager.check_complete(code) r = {'status': status} if status == 'incomplete': r['indent'] = ' ' * indent_spaces From 970ba1af5d441fbeec4e695c42eeea7eb8e6f05a Mon Sep 17 00:00:00 2001 From: Matthew Seal Date: Wed, 12 Jun 2019 22:47:26 -0700 Subject: [PATCH 0268/1195] Fixed socket binding race conditions --- ipykernel/heartbeat.py | 63 +++++++++++++++++++++++-------- ipykernel/inprocess/socket.py | 6 +-- ipykernel/kernelapp.py | 28 ++++++++++++-- ipykernel/tests/test_connect.py | 62 ++++++++++++++++++++++++++++++ ipykernel/tests/test_heartbeat.py | 54 ++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 22 deletions(-) create mode 100644 ipykernel/tests/test_heartbeat.py diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index cb03a4627..746cb7323 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -35,27 +35,60 @@ def __init__(self, context, addr=None): Thread.__init__(self) self.context = context self.transport, self.ip, self.port = addr - if self.port == 0: - if addr[0] == 'tcp': - s = socket.socket() - # '*' means all interfaces to 0MQ, which is '' to socket.socket - s.bind(('' if self.ip == '*' else self.ip, 0)) - self.port = s.getsockname()[1] - s.close() - elif addr[0] == 'ipc': - self.port = 1 - while os.path.exists("%s-%s" % (self.ip, self.port)): - self.port = self.port + 1 - else: - raise ValueError("Unrecognized zmq transport: %s" % addr[0]) + self.original_port = self.port + if self.original_port == 0: + self.pick_port() self.addr = (self.ip, self.port) self.daemon = True + def pick_port(self): + if self.transport == 'tcp': + s = socket.socket() + # '*' means all interfaces to 0MQ, which is '' to socket.socket + s.bind(('' if self.ip == '*' else self.ip, 0)) + self.port = s.getsockname()[1] + s.close() + elif self.transport == 'ipc': + self.port = 1 + while os.path.exists("%s-%s" % (self.ip, self.port)): + self.port = self.port + 1 + else: + raise ValueError("Unrecognized zmq transport: %s" % self.transport) + return self.port + + def _try_bind_socket(self): + c = ':' if self.transport == 'tcp' else '-' + return self.socket.bind('%s://%s' % (self.transport, self.ip) + c + str(self.port)) + + def _bind_socket(self): + try: + win_in_use = errno.WSAEADDRINUSE + except AttributeError: + win_in_use = None + + # Try up to 100 times to bind a port when in conflict to avoid + # infinite attempts in bad setups + max_attempts = 100 + for attempt in range(max_attempts): + try: + self._try_bind_socket() + except zmq.ZMQError as ze: + if attempt == max_attempts - 1: + raise + # Raise if we have any error not related to socket binding + if ze.errno != errno.EADDRINUSE and ze.errno != win_in_use: + raise + # Raise if we have any error not related to socket binding + if self.original_port == 0: + self.pick_port() + else: + raise + def run(self): self.socket = self.context.socket(zmq.ROUTER) self.socket.linger = 1000 - c = ':' if self.transport == 'tcp' else '-' - self.socket.bind('%s://%s' % (self.transport, self.ip) + c + str(self.port)) + self._bind_socket() + while True: try: zmq.device(zmq.QUEUE, self.socket, self.socket) diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index 37884891f..cbc4f7a49 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -20,7 +20,7 @@ #----------------------------------------------------------------------------- class SocketABC(with_metaclass(abc.ABCMeta, object)): - + @abc.abstractmethod def recv_multipart(self, flags=0, copy=True, track=False): raise NotImplementedError @@ -28,7 +28,7 @@ def recv_multipart(self, flags=0, copy=True, track=False): @abc.abstractmethod def send_multipart(self, msg_parts, flags=0, copy=True, track=False): raise NotImplementedError - + @classmethod def register(cls, other_cls): if other_cls is not DummySocket: @@ -47,7 +47,7 @@ class DummySocket(HasTraits): message_sent = Int(0) # Should be an Event context = Instance(zmq.Context) def _context_default(self): - return zmq.Context.instance() + return zmq.Context() #------------------------------------------------------------------------- # Socket interface diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index e416e982c..d6264667f 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -8,6 +8,7 @@ import atexit import os import sys +import errno import signal import traceback import logging @@ -171,7 +172,7 @@ def init_poller(self): # Parent polling doesn't work if ppid == 1 to start with. self.poller = ParentPollerUnix() - def _bind_socket(self, s, port): + def _try_bind_socket(self, s, port): iface = '%s://%s' % (self.transport, self.ip) if self.transport == 'tcp': if port <= 0: @@ -190,6 +191,25 @@ def _bind_socket(self, s, port): s.bind("ipc://%s" % path) return port + def _bind_socket(self, s, port): + try: + win_in_use = errno.WSAEADDRINUSE + except AttributeError: + win_in_use = None + + # Try up to 100 times to bind a port when in conflict to avoid + # infinite attempts in bad setups + max_attempts = 100 + for attempt in range(max_attempts): + try: + return self._try_bind_socket(s, port) + except zmq.ZMQError as ze: + # Raise if we have any error not related to socket binding + if ze.errno != errno.EADDRINUSE and ze.errno != win_in_use: + raise + if attempt == max_attempts - 1: + raise + def write_connection_file(self): """write connection info to JSON file""" cf = self.abs_connection_file @@ -229,7 +249,7 @@ def init_connection_file(self): def init_sockets(self): # Create a context, a session, and the kernel sockets. self.log.info("Starting the kernel at pid: %i", os.getpid()) - context = zmq.Context.instance() + context = zmq.Context() # Uncomment this to try closing the context. # atexit.register(context.term) @@ -477,8 +497,8 @@ def initialize(self, argv=None): try: self.init_signal() except: - # Catch exception when initializing signal fails, eg when running the - # kernel on a separate thread + # Catch exception when initializing signal fails, eg when running the + # kernel on a separate thread if self.log_level < logging.CRITICAL: self.log.error("Unable to initialize signal:", exc_info=True) self.init_kernel() diff --git a/ipykernel/tests/test_connect.py b/ipykernel/tests/test_connect.py index 739eb12bd..90c0ff75a 100644 --- a/ipykernel/tests/test_connect.py +++ b/ipykernel/tests/test_connect.py @@ -5,7 +5,11 @@ import json import os +import pytest +import errno +import zmq +from mock import patch from traitlets.config import Config from ipython_genutils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory from ipython_genutils.py3compat import str_to_bytes @@ -20,6 +24,9 @@ class DummyKernelApp(IPKernelApp): + def _default_shell_port(self): + return 0 + def initialize(self, argv=[]): self.init_profile_dir() self.init_connection_file() @@ -59,3 +66,58 @@ def test_get_connection_info(): info2['key'] = str_to_bytes(info2['key']) sub_info2 = {k:v for k,v in info.items() if k in sample_info} assert sub_info2 == sample_info + + +def test_port_bind_failure_raises(): + cfg = Config() + with TemporaryWorkingDirectory() as d: + cfg.ProfileDir.location = d + cf = 'kernel.json' + app = DummyKernelApp(config=cfg, connection_file=cf) + app.initialize() + with patch.object(app, '_try_bind_socket') as mock_try_bind: + mock_try_bind.side_effect = zmq.ZMQError(-100, "fails for unknown error types") + with pytest.raises(zmq.ZMQError): + app.init_sockets() + assert mock_try_bind.call_count == 1 + + +def test_port_bind_failure_recovery(): + try: + errno.WSAEADDRINUSE + except AttributeError: + # Fake windows address in-use code + errno.WSAEADDRINUSE = 12345 + + try: + cfg = Config() + with TemporaryWorkingDirectory() as d: + cfg.ProfileDir.location = d + cf = 'kernel.json' + app = DummyKernelApp(config=cfg, connection_file=cf) + app.initialize() + with patch.object(app, '_try_bind_socket') as mock_try_bind: + mock_try_bind.side_effect = [ + zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind unix"), + zmq.ZMQError(errno.WSAEADDRINUSE, "fails for non-bind windows") + ] + [0] * 100 + # Shouldn't raise anything as retries will kick in + app.init_sockets() + finally: + # Cleanup fake assignment + if errno.WSAEADDRINUSE == 12345: + del errno.WSAEADDRINUSE + + +def test_port_bind_failure_gives_up_retries(): + cfg = Config() + with TemporaryWorkingDirectory() as d: + cfg.ProfileDir.location = d + cf = 'kernel.json' + app = DummyKernelApp(config=cfg, connection_file=cf) + app.initialize() + with patch.object(app, '_try_bind_socket') as mock_try_bind: + mock_try_bind.side_effect = zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind") + with pytest.raises(zmq.ZMQError): + app.init_sockets() + assert mock_try_bind.call_count == 100 diff --git a/ipykernel/tests/test_heartbeat.py b/ipykernel/tests/test_heartbeat.py new file mode 100644 index 000000000..0fcf86d3e --- /dev/null +++ b/ipykernel/tests/test_heartbeat.py @@ -0,0 +1,54 @@ +"""Tests for heartbeat thread""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import json +import os +import pytest +import errno +import zmq + +from mock import patch + +from ipykernel.heartbeat import Heartbeat + + +def test_port_bind_failure_raises(): + heart = Heartbeat(None) + with patch.object(heart, '_try_bind_socket') as mock_try_bind: + mock_try_bind.side_effect = zmq.ZMQError(-100, "fails for unknown error types") + with pytest.raises(zmq.ZMQError): + heart._bind_socket() + assert mock_try_bind.call_count == 1 + + +def test_port_bind_failure_recovery(): + try: + errno.WSAEADDRINUSE + except AttributeError: + # Fake windows address in-use code + errno.WSAEADDRINUSE = 12345 + + try: + heart = Heartbeat(None) + with patch.object(heart, '_try_bind_socket') as mock_try_bind: + mock_try_bind.side_effect = [ + zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind unix"), + zmq.ZMQError(errno.WSAEADDRINUSE, "fails for non-bind windows") + ] + [0] * 100 + # Shouldn't raise anything as retries will kick in + heart._bind_socket() + finally: + # Cleanup fake assignment + if errno.WSAEADDRINUSE == 12345: + del errno.WSAEADDRINUSE + + +def test_port_bind_failure_gives_up_retries(): + heart = Heartbeat(None) + with patch.object(heart, '_try_bind_socket') as mock_try_bind: + mock_try_bind.side_effect = zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind") + with pytest.raises(zmq.ZMQError): + heart._bind_socket() + assert mock_try_bind.call_count == 100 From 14a2c7a6ad5c6949bf1449fce322a2faa84c9863 Mon Sep 17 00:00:00 2001 From: Chilipp Date: Thu, 13 Jun 2019 16:31:47 +0200 Subject: [PATCH 0269/1195] Revert "only flush stream in dispatch_shell if possible" This reverts commit c3f6e614eca6bd1a201e441ece79168d3d44605a. --- ipykernel/kernelbase.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index cc5c9d03b..0fbfebe53 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -245,10 +245,7 @@ def dispatch_shell(self, stream, msg): self._publish_status(u'idle') # flush to ensure reply is sent before # handling the next request - try: - stream.flush(zmq.POLLOUT) - except AttributeError: - pass + stream.flush(zmq.POLLOUT) return msg_type = msg['header']['msg_type'] @@ -286,10 +283,7 @@ def dispatch_shell(self, stream, msg): self._publish_status(u'idle') # flush to ensure reply is sent before # handling the next request - try: - stream.flush(zmq.POLLOUT) - except AttributeError: - pass + stream.flush(zmq.POLLOUT) def pre_handler_hook(self): """Hook to execute before calling message handler""" From c9ec71313a5058fe1dcc3a4cc5a78ca67a4fe407 Mon Sep 17 00:00:00 2001 From: Chilipp Date: Thu, 13 Jun 2019 16:34:18 +0200 Subject: [PATCH 0270/1195] implement dummy flush method see https://github.com/ipython/ipykernel/pull/405#issuecomment-501727200 --- ipykernel/inprocess/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 2e562f040..dc432a954 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -172,6 +172,9 @@ def _dispatch_to_kernel(self, msg): idents, reply_msg = self.session.recv(stream, copy=False) self.shell_channel.call_handlers_later(reply_msg) + def flush(self): + # dummy method (see https://github.com/ipython/ipykernel/pull/405) + #----------------------------------------------------------------------------- # ABC Registration From f3ac071e5c9bf7b7e3241b9967543b0282940ffb Mon Sep 17 00:00:00 2001 From: "Philipp S. Sommer" Date: Mon, 17 Jun 2019 11:15:41 +0200 Subject: [PATCH 0271/1195] add docstring for DummySocket.flush Co-Authored-By: Min RK --- ipykernel/inprocess/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index dc432a954..c926fd159 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -173,7 +173,7 @@ def _dispatch_to_kernel(self, msg): self.shell_channel.call_handlers_later(reply_msg) def flush(self): - # dummy method (see https://github.com/ipython/ipykernel/pull/405) + """no-op to comply with stream API""" #----------------------------------------------------------------------------- From f918e83bd81c1810183cba0e3d76392869a35b71 Mon Sep 17 00:00:00 2001 From: Matthew Seal Date: Fri, 12 Jul 2019 00:35:30 -0700 Subject: [PATCH 0272/1195] Added atexit and better port checks to avoid async failures --- ipykernel/kernelapp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index d6264667f..9e4531de5 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -199,7 +199,7 @@ def _bind_socket(self, s, port): # Try up to 100 times to bind a port when in conflict to avoid # infinite attempts in bad setups - max_attempts = 100 + max_attempts = 1 if port else 1 for attempt in range(max_attempts): try: return self._try_bind_socket(s, port) @@ -251,7 +251,7 @@ def init_sockets(self): self.log.info("Starting the kernel at pid: %i", os.getpid()) context = zmq.Context() # Uncomment this to try closing the context. - # atexit.register(context.term) + atexit.register(context.term) self.shell_socket = context.socket(zmq.ROUTER) self.shell_socket.linger = 1000 From f1ef18de590ec372ee4ef5d757cfc0ee69d8a4a6 Mon Sep 17 00:00:00 2001 From: Matthew Seal Date: Fri, 12 Jul 2019 00:50:46 -0700 Subject: [PATCH 0273/1195] Applied fixes for everything but test_shutdown --- ipykernel/heartbeat.py | 2 +- ipykernel/iostream.py | 22 +++++++++++----------- ipykernel/kernelapp.py | 2 +- ipykernel/tests/test_async.py | 4 ++-- ipykernel/tests/test_io.py | 1 - 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 746cb7323..83c79fc4a 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -68,7 +68,7 @@ def _bind_socket(self): # Try up to 100 times to bind a port when in conflict to avoid # infinite attempts in bad setups - max_attempts = 100 + max_attempts = 1 if self.original_port else 100 for attempt in range(max_attempts): try: self._try_bind_socket() diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 7dc519a40..2b870272f 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -167,14 +167,14 @@ def _check_mp_mode(self): return MASTER else: return CHILD - + def start(self): """Start the IOPub thread""" self.thread.start() # make sure we don't prevent process exit # I'm not sure why setting daemon=True above isn't enough, but it doesn't appear to be. atexit.register(self.stop) - + def stop(self): """Stop the IOPub thread""" if not self.thread.is_alive(): @@ -183,7 +183,7 @@ def stop(self): self.thread.join() if hasattr(self._local, 'event_pipe'): self._local.event_pipe.close() - + def close(self): self.socket.close() self.socket = None @@ -206,11 +206,11 @@ def schedule(self, f): def send_multipart(self, *args, **kwargs): """send_multipart schedules actual zmq send in my thread. - + If my thread isn't running (e.g. forked process), send immediately. """ self.schedule(lambda : self._really_send(*args, **kwargs)) - + def _really_send(self, msg, *args, **kwargs): """The callback that actually sends messages""" mp_mode = self._check_mp_mode() @@ -231,10 +231,10 @@ def _really_send(self, msg, *args, **kwargs): class BackgroundSocket(object): """Wrapper around IOPub thread that provides zmq send[_multipart]""" io_thread = None - + def __init__(self, io_thread): self.io_thread = io_thread - + def __getattr__(self, attr): """Wrap socket attr access for backward-compatibility""" if attr.startswith('__') and attr.endswith('__'): @@ -245,7 +245,7 @@ def __getattr__(self, attr): DeprecationWarning, stacklevel=2) return getattr(self.io_thread.socket, attr) super(BackgroundSocket, self).__getattr__(attr) - + def __setattr__(self, attr, value): if attr == 'io_thread' or (attr.startswith('__' and attr.endswith('__'))): super(BackgroundSocket, self).__setattr__(attr, value) @@ -253,7 +253,7 @@ def __setattr__(self, attr, value): warnings.warn("Setting zmq Socket attribute %s on BackgroundSocket" % attr, DeprecationWarning, stacklevel=2) setattr(self.io_thread.socket, attr, value) - + def send(self, msg, *args, **kwargs): return self.send_multipart([msg], *args, **kwargs) @@ -264,7 +264,7 @@ def send_multipart(self, *args, **kwargs): class OutStream(TextIOBase): """A file like object that publishes the stream to a 0MQ PUB socket. - + Output is handed off to an IO Thread """ @@ -419,7 +419,7 @@ def writable(self): def _flush_buffer(self): """clear the current buffer and return the current buffer data. - + This should only be called in the IO thread. """ data = u'' diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 9e4531de5..6ea76dac6 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -199,7 +199,7 @@ def _bind_socket(self, s, port): # Try up to 100 times to bind a port when in conflict to avoid # infinite attempts in bad setups - max_attempts = 1 if port else 1 + max_attempts = 1 if port else 100 for attempt in range(max_attempts): try: return self._try_bind_socket(s, port) diff --git a/ipykernel/tests/test_async.py b/ipykernel/tests/test_async.py index 01b022369..d1f327bff 100644 --- a/ipykernel/tests/test_async.py +++ b/ipykernel/tests/test_async.py @@ -14,14 +14,14 @@ KC = KM = None -def setup(): +def setup_function(): """start the global kernel (if it isn't running) and return its client""" global KM, KC KM, KC = start_new_kernel() flush_channels(KC) -def teardown(): +def teardown_function(): KC.stop_channels() KM.shutdown_kernel(now=True) diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index b6fa718f1..c1ac9c86e 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -39,4 +39,3 @@ def test_io_api(): with nt.assert_raises(io.UnsupportedOperation): stream.tell() - \ No newline at end of file From 3a63c518aaf562225e9562302b43d2229dd22426 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 18 Jul 2019 13:55:04 +0200 Subject: [PATCH 0274/1195] add explicit cleanup of zmq sockets avoids hangs during context termination --- ipykernel/heartbeat.py | 6 +++- ipykernel/iostream.py | 2 ++ ipykernel/kernelapp.py | 27 ++++++++++++++-- ipykernel/tests/test_connect.py | 52 +++++++++++++++---------------- ipykernel/tests/test_heartbeat.py | 8 ++--- 5 files changed, 60 insertions(+), 35 deletions(-) diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 83c79fc4a..9d5ed177d 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -87,7 +87,11 @@ def _bind_socket(self): def run(self): self.socket = self.context.socket(zmq.ROUTER) self.socket.linger = 1000 - self._bind_socket() + try: + self._bind_socket() + except Exception: + self.socket.close() + raise while True: try: diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 2b870272f..ed95c0315 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -185,6 +185,8 @@ def stop(self): self._local.event_pipe.close() def close(self): + if self.closed: + return self.socket.close() self.socket = None diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 6ea76dac6..3716f0f63 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -113,6 +113,14 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, kernel = Any() poller = Any() # don't restrict this even though current pollers are all Threads heartbeat = Instance(Heartbeat, allow_none=True) + + context = Any() + shell_socket = Any() + control_socket = Any() + stdin_socket = Any() + iopub_socket = Any() + iopub_thread = Any() + ports = Dict() subcommands = { @@ -249,9 +257,8 @@ def init_connection_file(self): def init_sockets(self): # Create a context, a session, and the kernel sockets. self.log.info("Starting the kernel at pid: %i", os.getpid()) - context = zmq.Context() - # Uncomment this to try closing the context. - atexit.register(context.term) + assert self.context is None, "init_sockets cannot be called twice!" + self.context = context = zmq.Context() self.shell_socket = context.socket(zmq.ROUTER) self.shell_socket.linger = 1000 @@ -299,6 +306,20 @@ def init_heartbeat(self): self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port) self.heartbeat.start() + def close(self): + """Close zmq sockets in an orderly fashion""" + if self.heartbeat: + self.heartbeat.socket.close() + self.heartbeat.context.term() + if self.iopub_thread: + self.iopub_thread.stop() + self.iopub_thread.close() + for channel in ('shell', 'control', 'stdin'): + socket = getattr(self, channel + "_socket", None) + if socket and not socket.closed: + socket.close() + self.context.term() + def log_connection_info(self): """display connection info, and store ports""" basename = os.path.basename(self.connection_file) diff --git a/ipykernel/tests/test_connect.py b/ipykernel/tests/test_connect.py index 90c0ff75a..0eee4282b 100644 --- a/ipykernel/tests/test_connect.py +++ b/ipykernel/tests/test_connect.py @@ -3,13 +3,14 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import errno import json import os +from unittest.mock import patch + import pytest -import errno import zmq -from mock import patch from traitlets.config import Config from ipython_genutils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory from ipython_genutils.py3compat import str_to_bytes @@ -68,12 +69,13 @@ def test_get_connection_info(): assert sub_info2 == sample_info -def test_port_bind_failure_raises(): +def test_port_bind_failure_raises(request): cfg = Config() with TemporaryWorkingDirectory() as d: cfg.ProfileDir.location = d cf = 'kernel.json' app = DummyKernelApp(config=cfg, connection_file=cf) + request.addfinalizer(app.close) app.initialize() with patch.object(app, '_try_bind_socket') as mock_try_bind: mock_try_bind.side_effect = zmq.ZMQError(-100, "fails for unknown error types") @@ -82,39 +84,37 @@ def test_port_bind_failure_raises(): assert mock_try_bind.call_count == 1 -def test_port_bind_failure_recovery(): +def test_port_bind_failure_recovery(request): try: errno.WSAEADDRINUSE except AttributeError: # Fake windows address in-use code - errno.WSAEADDRINUSE = 12345 - - try: - cfg = Config() - with TemporaryWorkingDirectory() as d: - cfg.ProfileDir.location = d - cf = 'kernel.json' - app = DummyKernelApp(config=cfg, connection_file=cf) - app.initialize() - with patch.object(app, '_try_bind_socket') as mock_try_bind: - mock_try_bind.side_effect = [ - zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind unix"), - zmq.ZMQError(errno.WSAEADDRINUSE, "fails for non-bind windows") - ] + [0] * 100 - # Shouldn't raise anything as retries will kick in - app.init_sockets() - finally: - # Cleanup fake assignment - if errno.WSAEADDRINUSE == 12345: - del errno.WSAEADDRINUSE - + p = patch.object(errno, 'WSAEADDRINUSE', 12345, create=True) + p.start() + request.addfinalizer(p.stop) -def test_port_bind_failure_gives_up_retries(): cfg = Config() with TemporaryWorkingDirectory() as d: cfg.ProfileDir.location = d cf = 'kernel.json' app = DummyKernelApp(config=cfg, connection_file=cf) + request.addfinalizer(app.close) + app.initialize() + with patch.object(app, '_try_bind_socket') as mock_try_bind: + mock_try_bind.side_effect = [ + zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind unix"), + zmq.ZMQError(errno.WSAEADDRINUSE, "fails for non-bind windows") + ] + [0] * 100 + # Shouldn't raise anything as retries will kick in + app.init_sockets() + +def test_port_bind_failure_gives_up_retries(request): + cfg = Config() + with TemporaryWorkingDirectory() as d: + cfg.ProfileDir.location = d + cf = 'kernel.json' + app = DummyKernelApp(config=cfg, connection_file=cf) + request.addfinalizer(app.close) app.initialize() with patch.object(app, '_try_bind_socket') as mock_try_bind: mock_try_bind.side_effect = zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind") diff --git a/ipykernel/tests/test_heartbeat.py b/ipykernel/tests/test_heartbeat.py index 0fcf86d3e..b077d1cf2 100644 --- a/ipykernel/tests/test_heartbeat.py +++ b/ipykernel/tests/test_heartbeat.py @@ -3,13 +3,11 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import json -import os -import pytest import errno -import zmq +from unittest.mock import patch -from mock import patch +import pytest +import zmq from ipykernel.heartbeat import Heartbeat From ae563d6c12ea9aa0fb64b0b3a3cc6c27659e660e Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 18 Jul 2019 14:13:10 +0200 Subject: [PATCH 0275/1195] increase shutdown test timeout to 30 seconds because travis can be real slow --- ipykernel/tests/test_kernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 96db617ce..fda8a1f4d 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -320,7 +320,7 @@ def test_shutdown(): execute(u'a = 1', kc=kc) wait_for_idle(kc) kc.shutdown() - for i in range(100): # 10s timeout + for i in range(300): # 30s timeout if km.is_alive(): time.sleep(.1) else: From d1b2d015842ec13b9c734ae2e6c1e458a0423f61 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 19 Jul 2019 09:52:27 +0200 Subject: [PATCH 0276/1195] add explicit socket cleanup might fix garbage collection-related hangs on kernel exit --- ipykernel/kernelapp.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 3716f0f63..db3d8a461 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -259,6 +259,7 @@ def init_sockets(self): self.log.info("Starting the kernel at pid: %i", os.getpid()) assert self.context is None, "init_sockets cannot be called twice!" self.context = context = zmq.Context() + atexit.register(self.close) self.shell_socket = context.socket(zmq.ROUTER) self.shell_socket.linger = 1000 @@ -308,17 +309,23 @@ def init_heartbeat(self): def close(self): """Close zmq sockets in an orderly fashion""" + self.log.info("Cleaning up sockets") if self.heartbeat: + self.log.debug("Closing heartbeat channel") self.heartbeat.socket.close() self.heartbeat.context.term() if self.iopub_thread: + self.log.debug("Closing iopub channel") self.iopub_thread.stop() self.iopub_thread.close() for channel in ('shell', 'control', 'stdin'): + self.log.debug("Closing %s channel", channel) socket = getattr(self, channel + "_socket", None) if socket and not socket.closed: socket.close() + self.log.debug("Terminating zmq context") self.context.term() + self.log.debug("Terminated zmq context") def log_connection_info(self): """display connection info, and store ports""" @@ -547,8 +554,10 @@ def start(self): except KeyboardInterrupt: pass + launch_new_instance = IPKernelApp.launch_instance + def main(): """Run an IPKernel as an application""" app = IPKernelApp.instance() From 15de5dc84b573ff0ef24386ffc2cce429f285a13 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 7 Aug 2019 13:11:14 +0200 Subject: [PATCH 0277/1195] reset io capture before closing sockets avoids attempting to use io sockets after they are used, e.g. by sys.excepthook --- ipykernel/kernelapp.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index db3d8a461..2849d0a5f 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -309,6 +309,8 @@ def init_heartbeat(self): def close(self): """Close zmq sockets in an orderly fashion""" + # un-capture IO before we start closing channels + self.reset_io() self.log.info("Cleaning up sockets") if self.heartbeat: self.log.debug("Closing heartbeat channel") @@ -391,6 +393,15 @@ def init_io(self): self.patch_io() + def reset_io(self): + """restore original io + + restores state after init_io + """ + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + sys.displayhook = sys.__displayhook__ + def patch_io(self): """Patch important libraries that can't handle sys.stdout forwarding""" try: From 140f44e09482dae9367eba3f16e5ce9e28c12bee Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 7 Aug 2019 13:12:57 +0200 Subject: [PATCH 0278/1195] cleanup heartbeat channel shutdown - signal exit by calling threadsafe context.term from the main thread - catch resulting ETERM in heartbeat thread and close the socket cleanly - suppress ENOTSOCK caused by closing the socket elsewhere (shouldn't be done, but better to catch it than hang) --- ipykernel/heartbeat.py | 13 +++++++++++++ ipykernel/kernelapp.py | 1 - 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 9d5ed177d..39ac5379a 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -98,7 +98,20 @@ def run(self): zmq.device(zmq.QUEUE, self.socket, self.socket) except zmq.ZMQError as e: if e.errno == errno.EINTR: + # signal interrupt, resume heartbeat continue + elif e.errno == zmq.ETERM: + # context terminated, close socket and exit + try: + self.socket.close() + except zmq.ZMQError: + # suppress further errors during cleanup + # this shouldn't happen, though + pass + break + elif e.errno == zmq.ENOTSOCK: + # socket closed elsewhere, exit + break else: raise else: diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 2849d0a5f..159dc51bb 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -314,7 +314,6 @@ def close(self): self.log.info("Cleaning up sockets") if self.heartbeat: self.log.debug("Closing heartbeat channel") - self.heartbeat.socket.close() self.heartbeat.context.term() if self.iopub_thread: self.log.debug("Closing iopub channel") From 2fd9b2c8e3d99c4e293895ef88e8b8102f1c8540 Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Wed, 7 Aug 2019 12:18:08 -0700 Subject: [PATCH 0279/1195] Add changelog for 5.1.2 --- docs/changelog.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 7281388dd..da25b22d6 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,20 @@ Changes in IPython kernel 5.1 --- +5.1.2 +***** + +5.1.2 fixes some socket-binding race conditions that caused testing failures in +nbconvert. + +- Fix socket-binding race conditions (:ghpull: `412`, :ghpull: `419`) +- Add a no-op ``flush`` method to ``DummySocket`` and comply with stream API + (:ghpull: `405`) +- Update kernel version to indicate kernel v5.3 support (:ghpull: `394`) +- Add testing for upcoming Python 3.8 and PEP 570 positional parameters + (:ghpull: `396`, :ghpull: `408`) + + 5.1.1 ***** 5.1.1 fixes a bug that caused cells to get stuck in a busy state. From ae9ebd0da9cff1b9a5fd6e409c0de44892da9100 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 7 Aug 2019 15:41:01 -0500 Subject: [PATCH 0280/1195] switch to release version --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index ca224aba0..4c990aa52 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 2, 0, 'dev') +version_info = (5,1,2) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 944bdcc778d146b4523cbde9861ea47c6e9fe24d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 7 Aug 2019 15:43:22 -0500 Subject: [PATCH 0281/1195] back to dev mode --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 4c990aa52..ca224aba0 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5,1,2) +version_info = (5, 2, 0, 'dev') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 43338546080e737ec1b5e04cf0176362192c2c5d Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 8 Feb 2019 13:24:16 +0100 Subject: [PATCH 0282/1195] Use sys.executable in kernelspec --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1c72d6069..d1128d68f 100644 --- a/setup.py +++ b/setup.py @@ -114,7 +114,12 @@ def run(self): if any(a.startswith(('bdist', 'build', 'install')) for a in sys.argv): from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME - argv = make_ipkernel_cmd(executable='python') + # When building a wheel, the executable specified in the kernelspec is simply 'python'. + # When installing from source, the full `sys.executable` can be used. + if any(a.startswith('bdist') for a in sys.argv): + argv = make_ipkernel_cmd(executable='python') + else: + argv = make_ipkernel_cmd() dest = os.path.join(here, 'data_kernelspec') if os.path.exists(dest): shutil.rmtree(dest) From b53f1366d0e515a1822796d636b4d61d8e3247c1 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Wed, 28 Aug 2019 15:58:46 +0200 Subject: [PATCH 0283/1195] Only generate kernelspec when installing or building wheel --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index d1128d68f..c52c322f8 100644 --- a/setup.py +++ b/setup.py @@ -111,14 +111,14 @@ def run(self): ) -if any(a.startswith(('bdist', 'build', 'install')) for a in sys.argv): +if any(a.startswith(('bdist', 'install')) for a in sys.argv): from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME # When building a wheel, the executable specified in the kernelspec is simply 'python'. - # When installing from source, the full `sys.executable` can be used. if any(a.startswith('bdist') for a in sys.argv): argv = make_ipkernel_cmd(executable='python') - else: + # When installing from source, the full `sys.executable` can be used. + if any(a.startswith('install') for a in sys.argv): argv = make_ipkernel_cmd() dest = os.path.join(here, 'data_kernelspec') if os.path.exists(dest): From e825834043e79a5f33642983cf1dd8a6540d9e9a Mon Sep 17 00:00:00 2001 From: Edison Gustavo Muenz Date: Sat, 31 Aug 2019 13:52:31 +0200 Subject: [PATCH 0284/1195] Rename _asyncio.py to _asyncio_utils.py to avoid name conflicts on Python 3.7 The name _asyncio is reserved to Python, which can lead to naming conflicts when running the tests depending on the setup of the machine. On PyCharm this is specially painful. --- ipykernel/tests/{_asyncio.py => _asyncio_utils.py} | 0 ipykernel/tests/test_eventloop.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename ipykernel/tests/{_asyncio.py => _asyncio_utils.py} (100%) diff --git a/ipykernel/tests/_asyncio.py b/ipykernel/tests/_asyncio_utils.py similarity index 100% rename from ipykernel/tests/_asyncio.py rename to ipykernel/tests/_asyncio_utils.py diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index a44907f10..418c7d34b 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -23,7 +23,7 @@ def teardown(): async_code = """ -from ipykernel.tests._asyncio import async_func +from ipykernel.tests._asyncio_utils import async_func async_func() """ From f1df72c11a3c596c14b0f7c45d9d72f65b099ee3 Mon Sep 17 00:00:00 2001 From: Edison Gustavo Muenz Date: Sat, 31 Aug 2019 16:28:31 +0200 Subject: [PATCH 0285/1195] Don't redirect stdout if nose machinery is not present By spawning subprocesses with `stdout = open('/dev/null')` then PyCharm is not able to attach to them. This is specially painful when debugging the tests in test_kernel(), where the methods `kernel()` do spawn kernels in subprocesses if required. --- ipykernel/tests/utils.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 31ab00b05..47eab54df 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -33,11 +33,11 @@ def start_new_kernel(**kwargs): Integrates with our output capturing for tests. """ + kwargs['stderr'] = STDOUT try: - stdout = nose.iptest_stdstreams_fileno() + kwargs['stdout'] = nose.iptest_stdstreams_fileno() except AttributeError: - stdout = open(os.devnull) - kwargs.update(dict(stdout=stdout, stderr=STDOUT)) + pass return manager.start_new_kernel(startup_timeout=STARTUP_TIMEOUT, **kwargs) @@ -131,8 +131,11 @@ def new_kernel(argv=None): ------- kernel_client: connected KernelClient instance """ - stdout = getattr(nose, 'iptest_stdstreams_fileno', open(os.devnull)) - kwargs = dict(stdout=stdout, stderr=STDOUT) + kwargs = {'stderr': STDOUT} + try: + kwargs['stdout'] = nose.iptest_stdstreams_fileno() + except AttributeError: + pass if argv is not None: kwargs['extra_arguments'] = argv return manager.run_kernel(**kwargs) From d4d870c3229ef29116f7b37985de9229be86767f Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Thu, 12 Sep 2019 15:29:00 +0100 Subject: [PATCH 0286/1195] Fix Heartbeat._bind_socket to return on the first successful bind. --- ipykernel/heartbeat.py | 2 ++ ipykernel/tests/test_heartbeat.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 39ac5379a..01be21122 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -83,6 +83,8 @@ def _bind_socket(self): self.pick_port() else: raise + else: + return def run(self): self.socket = self.context.socket(zmq.ROUTER) diff --git a/ipykernel/tests/test_heartbeat.py b/ipykernel/tests/test_heartbeat.py index b077d1cf2..cc21cf754 100644 --- a/ipykernel/tests/test_heartbeat.py +++ b/ipykernel/tests/test_heartbeat.py @@ -21,6 +21,14 @@ def test_port_bind_failure_raises(): assert mock_try_bind.call_count == 1 +def test_port_bind_failure_succeeds(): + heart = Heartbeat(None) + with patch.object(heart, '_try_bind_socket') as mock_try_bind: + mock_try_bind.side_effect = lambda: None + heart._bind_socket() + assert mock_try_bind.call_count == 1 + + def test_port_bind_failure_recovery(): try: errno.WSAEADDRINUSE From 154d49a266b792bc70f4efe8a85a4ac90b62a15e Mon Sep 17 00:00:00 2001 From: Mark Dickinson Date: Thu, 12 Sep 2019 16:00:06 +0100 Subject: [PATCH 0287/1195] Use a better test name; remove unnecessary mock side_effect. --- ipykernel/tests/test_heartbeat.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ipykernel/tests/test_heartbeat.py b/ipykernel/tests/test_heartbeat.py index cc21cf754..9340abd7f 100644 --- a/ipykernel/tests/test_heartbeat.py +++ b/ipykernel/tests/test_heartbeat.py @@ -21,10 +21,9 @@ def test_port_bind_failure_raises(): assert mock_try_bind.call_count == 1 -def test_port_bind_failure_succeeds(): +def test_port_bind_success(): heart = Heartbeat(None) with patch.object(heart, '_try_bind_socket') as mock_try_bind: - mock_try_bind.side_effect = lambda: None heart._bind_socket() assert mock_try_bind.call_count == 1 From 34cde64611138ff33c7e8ab9b8ceb064c6fce28a Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 14 Sep 2019 14:03:08 +0100 Subject: [PATCH 0288/1195] Avoid KeyError when unregistering comms --- ipykernel/comm/comm.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 172c2b322..178ce4215 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -73,7 +73,7 @@ def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys) def __del__(self): """trigger close on gc""" - self.close() + self.close(deleting=True) # publishing messages @@ -98,7 +98,7 @@ def open(self, data=None, metadata=None, buffers=None): comm_manager.unregister_comm(self) raise - def close(self, data=None, metadata=None, buffers=None): + def close(self, data=None, metadata=None, buffers=None, deleting=False): """Close the frontend-side version of this comm""" if self._closed: # only close once @@ -113,7 +113,9 @@ def close(self, data=None, metadata=None, buffers=None): self._publish_msg('comm_close', data=data, metadata=metadata, buffers=buffers, ) - self.kernel.comm_manager.unregister_comm(self) + if not deleting: + # If deleting, the comm can't be registered + self.kernel.comm_manager.unregister_comm(self) def send(self, data=None, metadata=None, buffers=None): """Send a message to the frontend-side version of this comm""" From 78cbaa5585d48e0d72e494dae89503a832253fa3 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 14 Sep 2019 16:42:34 +0100 Subject: [PATCH 0289/1195] Set closed flag --- ipykernel/comm/manager.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 358eb4092..d7b290de1 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -119,7 +119,8 @@ def comm_close(self, stream, ident, msg): comm = self.get_comm(comm_id) if comm is None: return - + + self.comms[comm_id]._closed = True del self.comms[comm_id] try: From fd98a182057e1179daca6746a6fc79a83eb1a435 Mon Sep 17 00:00:00 2001 From: Chilipp Date: Mon, 16 Sep 2019 11:39:53 +0200 Subject: [PATCH 0290/1195] Moved InProcessKernelClient.flush to DummySocket Corrects the wrong placement of the flush method implemented in https://github.com/ipython/ipykernel/pull/405 --- ipykernel/inprocess/client.py | 3 --- ipykernel/inprocess/socket.py | 3 +++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index c926fd159..2e562f040 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -172,9 +172,6 @@ def _dispatch_to_kernel(self, msg): idents, reply_msg = self.session.recv(stream, copy=False) self.shell_channel.call_handlers_later(reply_msg) - def flush(self): - """no-op to comply with stream API""" - #----------------------------------------------------------------------------- # ABC Registration diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index cbc4f7a49..e500922f5 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -61,4 +61,7 @@ def send_multipart(self, msg_parts, flags=0, copy=True, track=False): self.queue.put_nowait(msg_parts) self.message_sent += 1 + def flush(self): + """no-op to comply with stream API""" + SocketABC.register(DummySocket) From 846531b92198efc4db2a37bdadf4a037defcfd96 Mon Sep 17 00:00:00 2001 From: Chilipp Date: Mon, 16 Sep 2019 12:16:50 +0200 Subject: [PATCH 0291/1195] Added timeout parameter to DummySocket.flush see https://github.com/ipython/ipykernel/blob/6fc9a7e9fde006a5e1f7ae40010ba8a3306af47e/ipykernel/kernelbase.py#L286 --- ipykernel/inprocess/socket.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index e500922f5..4026c6f77 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -61,7 +61,8 @@ def send_multipart(self, msg_parts, flags=0, copy=True, track=False): self.queue.put_nowait(msg_parts) self.message_sent += 1 - def flush(self): + def flush(self, timeout=1.0): """no-op to comply with stream API""" + pass SocketABC.register(DummySocket) From 3a4dd3e7f3de0b4aea6742c92f1bdcd1b7820802 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 14 Sep 2019 10:31:14 -0500 Subject: [PATCH 0292/1195] add release notes for 5.1.3 --- docs/changelog.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index da25b22d6..7b7dd217a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,18 @@ Changes in IPython kernel 5.1 --- +5.1.3 +***** + +5.1.3 Includes several bugfixes and internal logic improvements. + +- Skip unregisterying comm with destructor (:ghpull: `433`) +- Fix ``Heartbeat._bind_socket`` to return on the first bind (:ghpull: `431`) +- Don't redirect stdout if nose machinery is not present (:ghpull: `427`) +- Rename `_asyncio.py` to `_asyncio_utils.py` to avoid name conflicts on Python 3.6+ (:ghpull: `426`) +- Only generate kernelspec when installing or building wheel (:ghpull: `425`) + + 5.1.2 ***** From c6d12cdb87661b1bb024b72c2e293e825e31e19d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 15 Sep 2019 13:47:54 -0500 Subject: [PATCH 0293/1195] Add 435 to release notes --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 7b7dd217a..703e2e940 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,7 +9,7 @@ Changes in IPython kernel 5.1.3 Includes several bugfixes and internal logic improvements. -- Skip unregisterying comm with destructor (:ghpull: `433`) +- Fix comm shutdown behavior (:ghpull: `433`, :ghpull: `435`) - Fix ``Heartbeat._bind_socket`` to return on the first bind (:ghpull: `431`) - Don't redirect stdout if nose machinery is not present (:ghpull: `427`) - Rename `_asyncio.py` to `_asyncio_utils.py` to avoid name conflicts on Python 3.6+ (:ghpull: `426`) From b2099bd21f513daae076fbf212c574e4f3d1934d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 16 Sep 2019 16:09:05 -0500 Subject: [PATCH 0294/1195] Update docs/changelog.rst Co-Authored-By: Carol Willing --- docs/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 703e2e940..2e0d51c1d 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,7 +9,7 @@ Changes in IPython kernel 5.1.3 Includes several bugfixes and internal logic improvements. -- Fix comm shutdown behavior (:ghpull: `433`, :ghpull: `435`) +- Fix comm shutdown behavior by adding a ``deleting`` option to ``close`` which can be set to prevent registering new comm channels during shutdown (:ghpull: `433`, :ghpull: `435`) - Fix ``Heartbeat._bind_socket`` to return on the first bind (:ghpull: `431`) - Don't redirect stdout if nose machinery is not present (:ghpull: `427`) - Rename `_asyncio.py` to `_asyncio_utils.py` to avoid name conflicts on Python 3.6+ (:ghpull: `426`) From aff92ad44f760faf16b5089c156628c312bc6271 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 17 Sep 2019 11:11:56 -0500 Subject: [PATCH 0295/1195] add 437 notes --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 2e0d51c1d..a752235c1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -11,6 +11,7 @@ Changes in IPython kernel - Fix comm shutdown behavior by adding a ``deleting`` option to ``close`` which can be set to prevent registering new comm channels during shutdown (:ghpull: `433`, :ghpull: `435`) - Fix ``Heartbeat._bind_socket`` to return on the first bind (:ghpull: `431`) +- Moved ``InProcessKernelClient.flush`` to ``DummySocket`` (:gphull: `437`) - Don't redirect stdout if nose machinery is not present (:ghpull: `427`) - Rename `_asyncio.py` to `_asyncio_utils.py` to avoid name conflicts on Python 3.6+ (:ghpull: `426`) - Only generate kernelspec when installing or building wheel (:ghpull: `425`) From d6fcdeed7e80e7554dee5e811cfa74c2439973ff Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 21 Sep 2019 20:51:52 +0100 Subject: [PATCH 0296/1195] Disable AppNap --- ipykernel/ipkernel.py | 7 +++++++ setup.py | 1 + 2 files changed, 8 insertions(+) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 1a3b92f66..d1573b6f4 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -17,6 +17,9 @@ from .kernelbase import Kernel as KernelBase from .zmqshell import ZMQInteractiveShell +if sys.platform == 'darwin': + import appnope + try: from IPython.core.interactiveshell import _asyncio_runner except ImportError: @@ -79,6 +82,10 @@ def __init__(self, **kwargs): for msg_type in comm_msg_types: self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type) + if sys.platform == 'darwin': + # Disable app-nap as the kernel is not a gui but can have guis + appnope.nope() + help_links = List([ { 'text': "Python Reference", diff --git a/setup.py b/setup.py index c52c322f8..7d1d65c0a 100644 --- a/setup.py +++ b/setup.py @@ -92,6 +92,7 @@ def run(self): 'traitlets>=4.1.0', 'jupyter_client', 'tornado>=4.2', + 'appnope;platform_system=="Darwin"', ], extras_require={ 'test': [ From dae72f8ffb9f2013bf3970c5dc58d46337f3fbb8 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sun, 22 Sep 2019 09:12:43 +0100 Subject: [PATCH 0297/1195] use the _darwin_app_nap option --- ipykernel/ipkernel.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index d1573b6f4..7cbb39328 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -16,9 +16,7 @@ from .comm import CommManager from .kernelbase import Kernel as KernelBase from .zmqshell import ZMQInteractiveShell - -if sys.platform == 'darwin': - import appnope +from .eventloops import _use_appnope try: from IPython.core.interactiveshell import _asyncio_runner @@ -82,8 +80,9 @@ def __init__(self, **kwargs): for msg_type in comm_msg_types: self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type) - if sys.platform == 'darwin': + if _use_appnope() and self._darwin_app_nap: # Disable app-nap as the kernel is not a gui but can have guis + import appnope appnope.nope() help_links = List([ From 4caabc83dc6c9cff64b9789bfa487d1d29ff57cd Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sun, 22 Sep 2019 09:16:31 +0100 Subject: [PATCH 0298/1195] remove redundant appnope call --- ipykernel/eventloops.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index e43a86f3d..3524a112b 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -154,12 +154,6 @@ def loop_wx(kernel): import wx - if _use_appnope() and kernel._darwin_app_nap: - # we don't hook up App Nap contexts for Wx, - # just disable it outright. - from appnope import nope - nope() - # Wx uses milliseconds poll_interval = int(1000 * kernel._poll_interval) From 2ae5a8154e806effb7802254118aaa7bf04e4a85 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 27 Sep 2019 10:18:43 +0200 Subject: [PATCH 0299/1195] ensure control stream is flushed before processing shell messages previous flush was not handled at the right time with new async dispatch Affected case: - shell request 1 takes a long time to process, is blocking (no async) - shell request 2 arrives - control request arrives later, but before shell request 1 is processed It's possible that shell request 2 is in our Python queue, but control request is still waiting one ioloop iteration to be handled from zmq. --- ipykernel/kernelbase.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 0fbfebe53..9685d5470 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -225,10 +225,6 @@ def should_handle(self, stream, msg, idents): @gen.coroutine def dispatch_shell(self, stream, msg): """dispatch shell requests""" - # flush control requests first - if self.control_stream: - self.control_stream.flush() - idents, msg = self.session.feed_identities(msg, copy=False) try: msg = self.session.deserialize(msg, content=True, copy=False) @@ -373,6 +369,9 @@ def dispatch_queue(self): """ while True: + # ensure control stream is flushed before processing shell messages + if self.control_stream: + self.control_stream.flush() # receive the next message and handle it try: yield self.process_one() From 445153cae23adf94ebf528ca96ea1e4f78451fa3 Mon Sep 17 00:00:00 2001 From: Martin Bergtholdt Date: Thu, 11 Apr 2019 14:03:52 +0200 Subject: [PATCH 0300/1195] Allow pyside2 gui loop --- ipykernel/eventloops.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index e43a86f3d..6906e6b3f 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -125,7 +125,6 @@ def loop_qt4(kernel): @register_integration('qt', 'qt5') def loop_qt5(kernel): """Start a kernel with PyQt5 event loop integration.""" - os.environ['QT_API'] = 'pyqt5' return loop_qt4(kernel) From 831ce39a8cb11cd939e0119e9f5bdcd32f8047d8 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 3 Oct 2019 09:39:22 +0200 Subject: [PATCH 0301/1195] changelog for 443 --- docs/changelog.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index a752235c1..e2705c55c 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -15,6 +15,7 @@ Changes in IPython kernel - Don't redirect stdout if nose machinery is not present (:ghpull: `427`) - Rename `_asyncio.py` to `_asyncio_utils.py` to avoid name conflicts on Python 3.6+ (:ghpull: `426`) - Only generate kernelspec when installing or building wheel (:ghpull: `425`) +- Fix priority ordering of control-channel messages in some cases (:ghpull:`443`) 5.1.2 From 7fe186994c48d236aaee1acb89d0284ae577ed8b Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Fri, 4 Oct 2019 13:02:39 +0100 Subject: [PATCH 0302/1195] Add flaky --- ipykernel/tests/test_embed_kernel.py | 6 ++++++ ipykernel/tests/test_start_kernel.py | 5 +++++ setup.py | 1 + 3 files changed, 12 insertions(+) diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 5e60245b0..57ad5eded 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -9,6 +9,7 @@ from contextlib import contextmanager from subprocess import Popen, PIPE +from flaky import flaky from jupyter_client import BlockingKernelClient from jupyter_core import paths @@ -61,6 +62,8 @@ def setup_kernel(cmd): client.stop_channels() kernel.terminate() + +@flaky(max_runs=3) def test_embed_kernel_basic(): """IPython.embed_kernel() is basically functional""" cmd = '\n'.join([ @@ -93,6 +96,8 @@ def test_embed_kernel_basic(): text = content['data']['text/plain'] assert '10' in text + +@flaky(max_runs=3) def test_embed_kernel_namespace(): """IPython.embed_kernel() inherits calling namespace""" cmd = '\n'.join([ @@ -128,6 +133,7 @@ def test_embed_kernel_namespace(): content = msg['content'] assert not content['found'] +@flaky(max_runs=3) def test_embed_kernel_reentrant(): """IPython.embed_kernel() can be called multiple times""" cmd = '\n'.join([ diff --git a/ipykernel/tests/test_start_kernel.py b/ipykernel/tests/test_start_kernel.py index 797ddf19c..b69785453 100644 --- a/ipykernel/tests/test_start_kernel.py +++ b/ipykernel/tests/test_start_kernel.py @@ -1,7 +1,10 @@ from .test_embed_kernel import setup_kernel +from flaky import flaky TIMEOUT = 15 + +@flaky(max_runs=3) def test_ipython_start_kernel_userns(): cmd = ('from IPython import start_kernel\n' 'ns = {"tre": 123}\n' @@ -27,6 +30,8 @@ def test_ipython_start_kernel_userns(): text = content['data']['text/plain'] assert u'DummyMod' in text + +@flaky(max_runs=3) def test_ipython_start_kernel_no_userns(): # Issue #4188 - user_ns should be passed to shell as None, not {} cmd = ('from IPython import start_kernel\n' diff --git a/setup.py b/setup.py index c52c322f8..340befb68 100644 --- a/setup.py +++ b/setup.py @@ -97,6 +97,7 @@ def run(self): 'test': [ 'pytest', 'pytest-cov', + 'flaky', 'nose', # nose because there are still a few nose.tools imports hanging around ], }, From 3889d3e67e4d85b5c22ba04211bc411a7a5933ab Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 5 Oct 2019 08:21:55 +0100 Subject: [PATCH 0303/1195] terminate kernel on error --- ipykernel/tests/test_embed_kernel.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 57ad5eded..b48cfc0e4 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -52,9 +52,12 @@ def setup_kernel(cmd): raise IOError("Connection file %r never arrived" % connection_file) client = BlockingKernelClient(connection_file=connection_file) - client.load_connection_file() - client.start_channels() - client.wait_for_ready() + try: + client.load_connection_file() + client.start_channels() + client.wait_for_ready() + except: + kernel.terminate() try: yield client From 8d4b4fb168f528b1903a8cd6db638dcb8392ff99 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 5 Oct 2019 08:27:04 +0100 Subject: [PATCH 0304/1195] stop process inconditionnaly --- ipykernel/tests/test_embed_kernel.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index b48cfc0e4..979871f15 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -56,13 +56,11 @@ def setup_kernel(cmd): client.load_connection_file() client.start_channels() client.wait_for_ready() - except: - kernel.terminate() - - try: - yield client + try: + yield client + finally: + client.stop_channels() finally: - client.stop_channels() kernel.terminate() From 8997a3658d1ddb6e8f5e517a3ddad379115665df Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 5 Oct 2019 09:00:14 +0100 Subject: [PATCH 0305/1195] make sure Popen is terminated --- ipykernel/tests/test_embed_kernel.py | 44 ++++++++++++++-------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 979871f15..533cd5430 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -30,29 +30,29 @@ def setup_kernel(cmd): kernel_manager: connected KernelManager instance """ kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE) - connection_file = os.path.join( - paths.jupyter_runtime_dir(), - 'kernel-%i.json' % kernel.pid, - ) - # wait for connection file to exist, timeout after 5s - tic = time.time() - while not os.path.exists(connection_file) \ - and kernel.poll() is None \ - and time.time() < tic + SETUP_TIMEOUT: - time.sleep(0.1) - - if kernel.poll() is not None: - o,e = kernel.communicate() - e = py3compat.cast_unicode(e) - raise IOError("Kernel failed to start:\n%s" % e) - - if not os.path.exists(connection_file): - if kernel.poll() is None: - kernel.terminate() - raise IOError("Connection file %r never arrived" % connection_file) - - client = BlockingKernelClient(connection_file=connection_file) try: + connection_file = os.path.join( + paths.jupyter_runtime_dir(), + 'kernel-%i.json' % kernel.pid, + ) + # wait for connection file to exist, timeout after 5s + tic = time.time() + while not os.path.exists(connection_file) \ + and kernel.poll() is None \ + and time.time() < tic + SETUP_TIMEOUT: + time.sleep(0.1) + + if kernel.poll() is not None: + o,e = kernel.communicate() + e = py3compat.cast_unicode(e) + raise IOError("Kernel failed to start:\n%s" % e) + + if not os.path.exists(connection_file): + if kernel.poll() is None: + kernel.terminate() + raise IOError("Connection file %r never arrived" % connection_file) + + client = BlockingKernelClient(connection_file=connection_file) client.load_connection_file() client.start_channels() client.wait_for_ready() From c8a3e29e80461b1e36c76cf5c773a2d8837aea4c Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 5 Oct 2019 09:23:46 +0100 Subject: [PATCH 0306/1195] More flaky tests --- ipykernel/tests/test_kernel.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index fda8a1f4d..6543164b0 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -11,6 +11,7 @@ import time import nose.tools as nt +import flaky as flaky from IPython.testing import decorators as dec, tools as tt from ipython_genutils import py3compat @@ -75,6 +76,7 @@ def test_sys_path_profile_dir(): assert '' in sys_path +@flaky(max_runs=3) @dec.skipif(sys.platform == 'win32', "subprocess prints fail on Windows") def test_subprocess_print(): """printing from forked mp.Process""" @@ -104,6 +106,7 @@ def test_subprocess_print(): _check_master(kc, expected=True, stream="stderr") +@flaky(max_runs=3) def test_subprocess_noprint(): """mp.Process without print doesn't trigger iostream mp_mode""" with kernel() as kc: @@ -126,6 +129,7 @@ def test_subprocess_noprint(): _check_master(kc, expected=True, stream="stderr") +@flaky(max_runs=3) @dec.skipif(sys.platform == 'win32', "subprocess prints fail on Windows") def test_subprocess_error(): """error in mp.Process doesn't crash""" From f8befb10224b82949b91b4b834d14592ef41e331 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 5 Oct 2019 09:35:12 +0100 Subject: [PATCH 0307/1195] correct import typo --- ipykernel/tests/test_kernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 6543164b0..219ded2d3 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -11,7 +11,7 @@ import time import nose.tools as nt -import flaky as flaky +from flaky import flaky from IPython.testing import decorators as dec, tools as tt from ipython_genutils import py3compat From 4f11d75a83d633d290a1b44401e258823aaad7f2 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 5 Oct 2019 14:29:44 +0100 Subject: [PATCH 0308/1195] wait until connection file is readable --- ipykernel/tests/test_embed_kernel.py | 71 +++++++++++++++++----------- 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 5e60245b0..c92de50a6 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -6,6 +6,7 @@ import os import sys import time +import json from contextlib import contextmanager from subprocess import Popen, PIPE @@ -28,39 +29,53 @@ def setup_kernel(cmd): ------- kernel_manager: connected KernelManager instance """ - kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE) - connection_file = os.path.join( - paths.jupyter_runtime_dir(), - 'kernel-%i.json' % kernel.pid, - ) - # wait for connection file to exist, timeout after 5s - tic = time.time() - while not os.path.exists(connection_file) \ - and kernel.poll() is None \ - and time.time() < tic + SETUP_TIMEOUT: - time.sleep(0.1) - - if kernel.poll() is not None: - o,e = kernel.communicate() - e = py3compat.cast_unicode(e) - raise IOError("Kernel failed to start:\n%s" % e) - - if not os.path.exists(connection_file): - if kernel.poll() is None: - kernel.terminate() - raise IOError("Connection file %r never arrived" % connection_file) - - client = BlockingKernelClient(connection_file=connection_file) - client.load_connection_file() - client.start_channels() - client.wait_for_ready() + def connection_file_ready(connection_file): + """Check if connection_file is a readable json file.""" + if not os.path.exists(connection_file): + return False + try: + with open(connection_file) as f: + json.load(f) + return True + except ValueError: + return False + + kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE) try: - yield client + connection_file = os.path.join( + paths.jupyter_runtime_dir(), + 'kernel-%i.json' % kernel.pid, + ) + # wait for connection file to exist, timeout after 5s + tic = time.time() + while not connection_file_ready(connection_file) \ + and kernel.poll() is None \ + and time.time() < tic + SETUP_TIMEOUT: + time.sleep(0.1) + + if kernel.poll() is not None: + o,e = kernel.communicate() + e = py3compat.cast_unicode(e) + raise IOError("Kernel failed to start:\n%s" % e) + + if not os.path.exists(connection_file): + if kernel.poll() is None: + kernel.terminate() + raise IOError("Connection file %r never arrived" % connection_file) + + client = BlockingKernelClient(connection_file=connection_file) + client.load_connection_file() + client.start_channels() + client.wait_for_ready() + try: + yield client + finally: + client.stop_channels() finally: - client.stop_channels() kernel.terminate() + def test_embed_kernel_basic(): """IPython.embed_kernel() is basically functional""" cmd = '\n'.join([ From 82f063c4196045988e060882cd6bc3cf34c6761f Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Sat, 5 Oct 2019 14:30:36 +0100 Subject: [PATCH 0309/1195] Add a bit more time --- ipykernel/tests/test_embed_kernel.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index c92de50a6..061dc5c67 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -54,6 +54,9 @@ def connection_file_ready(connection_file): and time.time() < tic + SETUP_TIMEOUT: time.sleep(0.1) + # Wait 100ms for the writing to finish + time.sleep(0.1) + if kernel.poll() is not None: o,e = kernel.communicate() e = py3compat.cast_unicode(e) From 48107b0ce068befe5c0b866fc3a62b16bb44a9ad Mon Sep 17 00:00:00 2001 From: "Bruno P. Kinoshita" Date: Sat, 12 Oct 2019 14:07:25 +1300 Subject: [PATCH 0310/1195] Fix comment typo --- ipykernel/connect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 3106c9983..0cbcada70 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -40,7 +40,7 @@ def get_connection_file(app=None): def find_connection_file(filename='kernel-*.json', profile=None): """DEPRECATED: find a connection file, and return its absolute path. - THIS FUNCTION IS DEPRECATED. Use juptyer_client.find_connection_file instead. + THIS FUNCTION IS DEPRECATED. Use jupyter_client.find_connection_file instead. Parameters ---------- From 17caf99b7231c74159732db5f4181189efb50a78 Mon Sep 17 00:00:00 2001 From: Martin Bergtholdt Date: Mon, 14 Oct 2019 11:23:29 +0200 Subject: [PATCH 0311/1195] set default python bindings --- ipykernel/eventloops.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 6906e6b3f..6e282d17b 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -125,6 +125,8 @@ def loop_qt4(kernel): @register_integration('qt', 'qt5') def loop_qt5(kernel): """Start a kernel with PyQt5 event loop integration.""" + if os.environ.get('QT_API', None) is None: + os.environ['QT_API'] = 'pyqt5' return loop_qt4(kernel) From 34ea83ccb3936e491828fe5972579e1950b086c9 Mon Sep 17 00:00:00 2001 From: Martin Bergtholdt Date: Thu, 17 Oct 2019 13:09:42 +0200 Subject: [PATCH 0312/1195] FIX: set appropriate QT_API, defaulting to pyqt5 with pyside2 as fall-back --- ipykernel/eventloops.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 6e282d17b..51b502001 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -126,7 +126,15 @@ def loop_qt4(kernel): def loop_qt5(kernel): """Start a kernel with PyQt5 event loop integration.""" if os.environ.get('QT_API', None) is None: - os.environ['QT_API'] = 'pyqt5' + try: + import PyQt5 + os.environ['QT_API'] = 'pyqt5' + except ImportError: + try: + import PySide2 + os.environ['QT_API'] = 'pyside2' + except ImportError: + pass return loop_qt4(kernel) From 114ab6dafa6afd1841cb11744e16133ac2106d20 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 20 Oct 2019 06:55:40 -0500 Subject: [PATCH 0313/1195] Release 5.1.3 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index ca224aba0..04e5a2ca4 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 2, 0, 'dev') +version_info = (5, 1, 3) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From bd29855eed6887478c6b9a79a856e7567cda091e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 20 Oct 2019 06:56:54 -0500 Subject: [PATCH 0314/1195] back to dev version --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 04e5a2ca4..ca224aba0 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 1, 3) +version_info = (5, 2, 0, 'dev') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From f40d74afe52c4f45749860d293b6f55ac7e0e7d4 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 12 Nov 2019 13:17:15 +0100 Subject: [PATCH 0315/1195] workaround tornado+py38+windows compatibility issue set eventloop policy to the old default while tornado is not compatible with the new one --- ipykernel/kernelapp.py | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 159dc51bb..fdc6329f9 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -514,13 +514,45 @@ def configure_tornado_logger(self): handler.setFormatter(formatter) logger.addHandler(handler) + def _init_asyncio_patch(self): + """set default asyncio policy to be compatible with tornado + + Tornado 6 (at least) is not compatible with the default + asyncio implementation on Windows + + Pick the older SelectorEventLoopPolicy on Windows + if the known-incompatible default policy is in use. + + do this as early as possible to make it a low priority and overrideable + + ref: https://github.com/tornadoweb/tornado/issues/2608 + + FIXME: if/when tornado supports the defaults in asyncio, + remove and bump tornado requirement for py38 + """ + if sys.platform.startswith("win") and sys.version_info >= (3, 8): + import asyncio + try: + from asyncio import ( + WindowsProactorEventLoopPolicy, + WindowsSelectorEventLoopPolicy, + ) + except ImportError: + pass + # not affected + else: + if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: + # WindowsProactorEventLoopPolicy is not compatible with tornado 6 + # fallback to the pre-3.8 default of Selector + asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) + @catch_config_error def initialize(self, argv=None): + self._init_asyncio_patch() super(IPKernelApp, self).initialize(argv) if self.subapp is not None: return - # register zmq IOLoop with tornado - zmq_ioloop.install() + self.init_blackhole() self.init_connection_file() self.init_poller() From 7d40c4e66cc79e8211fbe41d13ae25b16cf66562 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sun, 24 Nov 2019 21:10:49 +0000 Subject: [PATCH 0316/1195] Test with released Python 3.8 on Travis CI Closes gh-454 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index be4545103..4a9e7bb44 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: python python: - "nightly" - - "3.8-dev" + - 3.8 - 3.7 - 3.6 - 3.5 From d7a39cb3ff9f60446bb4c20b230c853473a4d835 Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sun, 24 Nov 2019 21:12:07 +0000 Subject: [PATCH 0317/1195] IPython master no longer supports Python 3.5 --- .travis.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4a9e7bb44..f989abf9b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,10 +41,6 @@ after_success: - codecov matrix: include: - - python: 3.5 - env: - - TORNADO="4.5.*" - - IPYTHON=master - python: 3.6 env: - IPYTHON=master From 01d1c8cffd053d68ed2551c4ec76f70090bf3fdd Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sun, 24 Nov 2019 21:13:05 +0000 Subject: [PATCH 0318/1195] Drop Travis tests on Python 3.4 IPython 7.x (first released over a year ago) requires Python 3.5 --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f989abf9b..e2a6e777f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,6 @@ python: - 3.7 - 3.6 - 3.5 - - 3.4 sudo: false dist: xenial install: From 72781022bf73d09af559cbc1860a9981b3059d7f Mon Sep 17 00:00:00 2001 From: Valentin Novikov Date: Wed, 4 Dec 2019 02:46:59 +0500 Subject: [PATCH 0319/1195] fix: 'NoneType' object has no attribute 'thread' --- ipykernel/iostream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index ed95c0315..7867f9fdf 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -338,7 +338,7 @@ def flush(self): send will happen in the background thread """ - if self.pub_thread.thread.is_alive(): + if self.pub_thread and self.pub_thread.thread is not None and self.pub_thread.thread.is_alive(): # request flush on the background thread self.pub_thread.schedule(self._flush) # wait for flush to actually get through, if we can. From 938f2c797bc4ea29f70ff52822358a4976170258 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 4 Dec 2019 08:58:42 +0000 Subject: [PATCH 0320/1195] Fix None eventloop --- ipykernel/kernelbase.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 9685d5470..ff9afa5ef 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -295,6 +295,9 @@ def enter_eventloop(self): self.log.info("Entering eventloop %s", self.eventloop) # record handle, so we can check when this changes eventloop = self.eventloop + if eventloop is None: + self.log.info("Exiting as there is no eventloop") + return def advance_eventloop(): # check if eventloop changed: if self.eventloop is not eventloop: From a5fa5687dbb9a675e8ee9084d21588df7b93978c Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 4 Dec 2019 09:09:17 +0000 Subject: [PATCH 0321/1195] Add blank line around nested function --- ipykernel/kernelbase.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index ff9afa5ef..7c5b4a86e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -298,6 +298,7 @@ def enter_eventloop(self): if eventloop is None: self.log.info("Exiting as there is no eventloop") return + def advance_eventloop(): # check if eventloop changed: if self.eventloop is not eventloop: From 60219ef601f04c8008fd0462bd9c1bc2347c79d6 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 12 Dec 2019 16:34:05 -0800 Subject: [PATCH 0322/1195] Remove Python2 code compatibility We haven't been compatible with Python 2 for a while. Start some cleanup. --- ipykernel/serialize.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 5ceabbf63..437ebb591 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -6,16 +6,10 @@ import warnings warnings.warn("ipykernel.serialize is deprecated. It has moved to ipyparallel.serialize", DeprecationWarning) -try: - import cPickle - pickle = cPickle -except: - cPickle = None - import pickle +import pickle from itertools import chain -from ipython_genutils.py3compat import PY3, buffer_to_bytes_py2 from ipykernel.pickleutil import ( can, uncan, can_sequence, uncan_sequence, CannedObject, istype, sequence_types, PICKLE_PROTOCOL, @@ -23,9 +17,6 @@ from jupyter_client.session import MAX_ITEMS, MAX_BYTES -if PY3: - buffer = memoryview - #----------------------------------------------------------------------------- # Serialization Functions #----------------------------------------------------------------------------- @@ -44,8 +35,6 @@ def _extract_buffers(obj, threshold=MAX_BYTES): # because pickling buffer objects just results in broken pointers elif isinstance(buf, memoryview): obj.buffers[i] = buf.tobytes() - elif isinstance(buf, buffer): - obj.buffers[i] = bytes(buf) return buffers def _restore_buffers(obj, buffers): @@ -109,7 +98,7 @@ def deserialize_object(buffers, g=None): (newobj, bufs) : unpacked object, and the list of remaining unused buffers. """ bufs = list(buffers) - pobj = buffer_to_bytes_py2(bufs.pop(0)) + pobj = bufs.pop(0) canned = pickle.loads(pobj) if istype(canned, sequence_types) and len(canned) < MAX_ITEMS: for c in canned: @@ -164,9 +153,9 @@ def unpack_apply_message(bufs, g=None, copy=True): Returns: original f,args,kwargs""" bufs = list(bufs) # allow us to pop assert len(bufs) >= 2, "not enough buffers!" - pf = buffer_to_bytes_py2(bufs.pop(0)) + pf = bufs.pop(0) f = uncan(pickle.loads(pf), g) - pinfo = buffer_to_bytes_py2(bufs.pop(0)) + pinfo = bufs.pop(0) info = pickle.loads(pinfo) arg_bufs, kwarg_bufs = bufs[:info['narg_bufs']], bufs[info['narg_bufs']:] From bb1d2360ea926534dc4868d6360d48be654cb2e0 Mon Sep 17 00:00:00 2001 From: ossdev07 Date: Mon, 30 Dec 2019 06:36:16 +0000 Subject: [PATCH 0323/1195] Added arm64 jobs for Travis-CI Signed-off-by: ossdev07 --- .travis.yml | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index e2a6e777f..2baa1daac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,27 @@ language: python -python: - - "nightly" - - 3.8 - - 3.7 - - 3.6 - - 3.5 +matrix: + include: + - arch: arm64 + python: "nightly" + dist: bionic + - arch: amd64 + python: "nightly" + - arch: arm64 + python: 3.5 + - arch: amd64 + python: 3.5 + - arch: arm64 + python: 3.6 + - arch: amd64 + python: 3.6 + - arch: arm64 + python: 3.7 + - arch: amd64 + python: 3.7 + - arch: arm64 + python: 3.8 + - arch: amd64 + python: 3.8 sudo: false dist: xenial install: From c568b45ac6522ea518c8362871d7e11dc0764769 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 10 Jan 2020 17:38:37 +0100 Subject: [PATCH 0324/1195] Remove ipywidgets hack --- ipykernel/kernelapp.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index fdc6329f9..d683e8263 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -488,18 +488,6 @@ def init_shell(self): if self.shell: self.shell.configurables.append(self) - def init_extensions(self): - super(IPKernelApp, self).init_extensions() - # BEGIN HARDCODED WIDGETS HACK - # Ensure ipywidgets extension is loaded if available - extension_man = self.shell.extension_manager - if 'ipywidgets' not in extension_man.loaded: - try: - extension_man.load_extension('ipywidgets') - except ImportError as e: - self.log.debug('ipywidgets package not installed. Widgets will not be available.') - # END HARDCODED WIDGETS HACK - def configure_tornado_logger(self): """ Configure the tornado logging.Logger. From edfef85296fdbf56810a1d26180850fed11998dd Mon Sep 17 00:00:00 2001 From: Thomas A Caswell Date: Sat, 11 Jan 2020 23:38:11 -0500 Subject: [PATCH 0325/1195] FIX: add missing import from backend_agg closes #231 and https://github.com/matplotlib/matplotlib/issues/8291 --- ipykernel/pylab/backend_inline.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index 273177ad6..b4d31077a 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -6,7 +6,11 @@ from __future__ import print_function import matplotlib -from matplotlib.backends.backend_agg import new_figure_manager, FigureCanvasAgg # analysis: ignore +from matplotlib.backends.backend_agg import ( + new_figure_manager, + FigureCanvasAgg, + new_figure_manager_given_figure, +) # analysis: ignore from matplotlib import colors from matplotlib._pylab_helpers import Gcf From 5d0757689fe565604861ba96927f3df64865e179 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Thu, 16 Jan 2020 12:26:16 +0100 Subject: [PATCH 0326/1195] Schedule flush on subprocess without newline --- ipykernel/iostream.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 7867f9fdf..b7a2c0807 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -406,6 +406,8 @@ def write(self, string): # and this helps. if '\n' in string: self.flush() + else: + self.pub_thread.schedule(self._flush) else: self._schedule_flush() From 2eee758aab896d09b48c820a13595bd29f657a40 Mon Sep 17 00:00:00 2001 From: "Mark E. Haase" Date: Fri, 24 Jan 2020 15:38:50 -0500 Subject: [PATCH 0327/1195] Add new trio_loop config flag that runs Trio on main thread This flag causes Trio to run on the main thread, and puts ZMQ and other stuff on a background thread. When trio runs on the main thread, async cells are executed as tasks inside a global nursery. A global nursery named GLOBAL_NURSERY is exported in builtins so that background tasks can continue to run even after a cell finishes executing. Exceptions in background tasks are caught and displayed in the current cell. This implicitly runs `%autoawait trio` when the kernel starts and then disables the %autoawait magic so that users can't switch to a different loop while in Trio loop mode. Co-authored-by: Brian Mackintosh --- ipykernel/kernelapp.py | 31 +++++++++++++++++++++++++++---- ipykernel/trio_runner.py | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 ipykernel/trio_runner.py diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index d683e8263..afd1dbdc4 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -12,6 +12,7 @@ import signal import traceback import logging +import threading from tornado import ioloop import zmq @@ -72,6 +73,10 @@ {'IPKernelApp' : {'pylab' : 'auto'}}, """Pre-load matplotlib and numpy for interactive use with the default matplotlib backend."""), + 'trio-loop' : ( + {'InteractiveShell' : {'trio_loop' : False}}, + 'Enable Trio as main event loop.' + ), }) # inherit flags&aliases for any IPython shell apps @@ -147,6 +152,7 @@ def abs_connection_file(self): # streams, etc. no_stdout = Bool(False, help="redirect stdout to the null device").tag(config=True) no_stderr = Bool(False, help="redirect stderr to the null device").tag(config=True) + trio_loop = Bool(False, help="Set main event loop.").tag(config=True) quiet = Bool(True, help="Only send stdout/stderr to output stream").tag(config=True) outstream_class = DottedObjectName('ipykernel.iostream.OutStream', help="The importstring for the OutStream factory").tag(config=True) @@ -579,10 +585,27 @@ def start(self): self.poller.start() self.kernel.start() self.io_loop = ioloop.IOLoop.current() - try: - self.io_loop.start() - except KeyboardInterrupt: - pass + if self.trio_loop: + from ipykernel.trio_runner import TrioRunner + import warnings + tr = TrioRunner() + self.kernel.shell.set_trio_runner(tr) + self.kernel.shell.run_line_magic('autoawait', 'trio') + self.kernel.shell.magics_manager.magics['line']['autoawait'] = \ + lambda _: warnings.warn("Autoawait isn't allowed in Trio " + "background loop mode.") + bg_thread = threading.Thread(target=self.io_loop.start, daemon=True, + name='TornadoBackground') + bg_thread.start() + try: + tr.run() + except KeyboardInterrupt: + pass + else: + try: + self.io_loop.start() + except KeyboardInterrupt: + pass launch_new_instance = IPKernelApp.launch_instance diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py new file mode 100644 index 000000000..443fb0409 --- /dev/null +++ b/ipykernel/trio_runner.py @@ -0,0 +1,38 @@ +import builtins +import logging +import traceback + +import trio + + +class TrioRunner: + def __init__(self): + self._trio_token = None + + def run(self): + def log_nursery_exc(exc): + exc = '\n'.join(traceback.format_exception(type(exc), exc, + exc.__traceback__)) + logging.error('An exception occurred in a global nursery task.\n%s', + exc) + + async def trio_main(): + self._trio_token = trio.hazmat.current_trio_token() + async with trio.open_nursery() as nursery: + # TODO This hack prevents the nursery from cancelling all child + # tasks when an uncaught exception occurs, but it's ugly. + nursery._add_exc = log_nursery_exc + builtins.GLOBAL_NURSERY = nursery + await trio.sleep_forever() + + trio.run(trio_main) + + def __call__(self, async_fn): + async def loc(coro): + """ + We need the dummy no-op async def to protect from + trio's internal. See https://github.com/python-trio/trio/issues/89 + """ + return await coro + + return trio.from_thread.run(loc, async_fn, trio_token=self._trio_token) From 9c246c8260d2308287cfc261ca09bd0b2c92e5d8 Mon Sep 17 00:00:00 2001 From: "Mark E. Haase" Date: Fri, 24 Jan 2020 15:49:46 -0500 Subject: [PATCH 0328/1195] Move a bit more of the implementation into trio_runner.py This minimizes the amount of change in kernelapp.py. Co-authored-by: Brian Mackintosh --- ipykernel/kernelapp.py | 11 +---------- ipykernel/trio_runner.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index afd1dbdc4..75d4d5e64 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -12,7 +12,6 @@ import signal import traceback import logging -import threading from tornado import ioloop import zmq @@ -587,16 +586,8 @@ def start(self): self.io_loop = ioloop.IOLoop.current() if self.trio_loop: from ipykernel.trio_runner import TrioRunner - import warnings tr = TrioRunner() - self.kernel.shell.set_trio_runner(tr) - self.kernel.shell.run_line_magic('autoawait', 'trio') - self.kernel.shell.magics_manager.magics['line']['autoawait'] = \ - lambda _: warnings.warn("Autoawait isn't allowed in Trio " - "background loop mode.") - bg_thread = threading.Thread(target=self.io_loop.start, daemon=True, - name='TornadoBackground') - bg_thread.start() + tr.initialize(self.kernel, self.io_loop) try: tr.run() except KeyboardInterrupt: diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index 443fb0409..ad4c30e72 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -1,6 +1,8 @@ import builtins import logging +import threading import traceback +import warnings import trio @@ -9,6 +11,16 @@ class TrioRunner: def __init__(self): self._trio_token = None + def initialize(self, kernel, io_loop): + kernel.shell.set_trio_runner(self) + kernel.shell.run_line_magic('autoawait', 'trio') + kernel.shell.magics_manager.magics['line']['autoawait'] = \ + lambda _: warnings.warn("Autoawait isn't allowed in Trio " + "background loop mode.") + bg_thread = threading.Thread(target=io_loop.start, daemon=True, + name='TornadoBackground') + bg_thread.start() + def run(self): def log_nursery_exc(exc): exc = '\n'.join(traceback.format_exception(type(exc), exc, From 06967ee2b11d11ab1af038302798c31dac062055 Mon Sep 17 00:00:00 2001 From: Eric Prestat Date: Sun, 26 Jan 2020 13:19:35 +0000 Subject: [PATCH 0329/1195] Workaround tornado+py38+windows compatibility issue (Same as https://github.com/ipython/ipykernel/pull/456). Set eventloop policy to the old default while tornado is not compatible with the new one. --- ipykernel/ipkernel.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 7cbb39328..eb4cfad5c 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -57,7 +57,25 @@ def _user_ns_changed(self, name, old, new): _sys_raw_input = Any() _sys_eval_input = Any() + def _init_asyncio_patch(self): + if sys.platform.startswith("win") and sys.version_info >= (3, 8): + import asyncio + try: + from asyncio import ( + WindowsProactorEventLoopPolicy, + WindowsSelectorEventLoopPolicy, + ) + except ImportError: + pass + # not affected + else: + if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: + # WindowsProactorEventLoopPolicy is not compatible with tornado 6 + # fallback to the pre-3.8 default of Selector + asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) + def __init__(self, **kwargs): + self._init_asyncio_patch() super(IPythonKernel, self).__init__(**kwargs) # Initialize the InteractiveShell subclass From b0a48b87a34fc63a67ccc3fb6b395c60396b4c4d Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 27 Jan 2020 11:36:57 +0100 Subject: [PATCH 0330/1195] changelog for 5.1.4 --- docs/changelog.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index e2705c55c..69e67641a 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,17 @@ Changes in IPython kernel 5.1 --- +5.1.4 +***** + +5.1.4 Includes a few bugfixes, +especially for compatibility with Python 3.8 on Windows. + +- Fix pickle issues when using inline matplotlib backend (:ghpull:`476`) +- Fix an error during kernel shutdown (:ghpull:`463`) +- Fix compatibility issues with Python 3.8 (:ghpull:`456`, :ghpull:`461`) +- Remove some dead code (:ghpull:`474`, :ghpull:`467`) + 5.1.3 ***** From 09c292887a621ac7844f7f9a77658b8055682e98 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 27 Jan 2020 11:37:03 +0100 Subject: [PATCH 0331/1195] release 5.1.4 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index ca224aba0..298a287cd 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 2, 0, 'dev') +version_info = (5, 1, 4) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 7b2727d827a95d2059c9d9d9354899feabb642ee Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 27 Jan 2020 11:44:24 +0100 Subject: [PATCH 0332/1195] back to dev --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 298a287cd..ca224aba0 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 1, 4) +version_info = (5, 2, 0, 'dev') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 11e6d4c500e4392933803b52cba2c6d2d12f30f3 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 27 Jan 2020 09:37:02 -0500 Subject: [PATCH 0333/1195] add py38 and py35 to the appveyor matrix --- appveyor.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index e11ae0242..5116a2b7c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,8 +6,9 @@ clone_depth: 1 environment: matrix: + - python: "C:/Python38-x64" - python: "C:/Python36-x64" - - python: "C:/Python36" + - python: "C:/Python35" cache: - C:\Users\appveyor\AppData\Local\pip\Cache From 23e8ed5c9727ebff1c1a9c69c26ad1ca2bb1bdfe Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 27 Jan 2020 09:38:39 -0500 Subject: [PATCH 0334/1195] exclude pytest 5.3.4 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 793ce9357..8920f1647 100644 --- a/setup.py +++ b/setup.py @@ -96,7 +96,7 @@ def run(self): ], extras_require={ 'test': [ - 'pytest', + 'pytest !=5.3.4', 'pytest-cov', 'flaky', 'nose', # nose because there are still a few nose.tools imports hanging around From a8ae1263bb90294825d2db3cffe6b411727815fb Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 27 Jan 2020 09:47:13 -0500 Subject: [PATCH 0335/1195] set pytest to fail fast in appveyor --- appveyor.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 5116a2b7c..7d564a918 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,6 +15,7 @@ cache: init: - cmd: set PATH=%python%;%python%\scripts;%PATH% + install: - cmd: | python -m pip install --upgrade setuptools pip wheel @@ -26,8 +27,9 @@ install: pip install matplotlib numpy pip freeze - cmd: python -c "import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)" + test_script: - - cmd: pytest -v --cov ipykernel ipykernel + - cmd: pytest -v -x --cov ipykernel ipykernel on_success: - cmd: pip install codecov From 04455ea40ca4a9c85d6ac072cd995d64c9c8fa84 Mon Sep 17 00:00:00 2001 From: Eric Prestat Date: Wed, 29 Jan 2020 12:07:56 +0000 Subject: [PATCH 0336/1195] Revert "Workaround tornado+py38+windows compatibility issue (Same as https://github.com/ipython/ipykernel/pull/456). Set eventloop policy to the old default while tornado is not compatible with the new one." This reverts commit 06967ee2b11d11ab1af038302798c31dac062055. --- ipykernel/ipkernel.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index eb4cfad5c..7cbb39328 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -57,25 +57,7 @@ def _user_ns_changed(self, name, old, new): _sys_raw_input = Any() _sys_eval_input = Any() - def _init_asyncio_patch(self): - if sys.platform.startswith("win") and sys.version_info >= (3, 8): - import asyncio - try: - from asyncio import ( - WindowsProactorEventLoopPolicy, - WindowsSelectorEventLoopPolicy, - ) - except ImportError: - pass - # not affected - else: - if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: - # WindowsProactorEventLoopPolicy is not compatible with tornado 6 - # fallback to the pre-3.8 default of Selector - asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) - def __init__(self, **kwargs): - self._init_asyncio_patch() super(IPythonKernel, self).__init__(**kwargs) # Initialize the InteractiveShell subclass From 2c2882c448ec07f71f04f279bc2c0d7fe4073ea7 Mon Sep 17 00:00:00 2001 From: Eric Prestat Date: Wed, 29 Jan 2020 11:55:55 +0000 Subject: [PATCH 0337/1195] Fix example to work on windows with python3.8 --- examples/embedding/inprocess_qtconsole.py | 29 ++++++++++++++++++++++ examples/embedding/inprocess_terminal.py | 30 +++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/examples/embedding/inprocess_qtconsole.py b/examples/embedding/inprocess_qtconsole.py index 3fc662944..2e7b50ab1 100644 --- a/examples/embedding/inprocess_qtconsole.py +++ b/examples/embedding/inprocess_qtconsole.py @@ -1,5 +1,6 @@ from __future__ import print_function import os +import sys from IPython.qt.console.rich_ipython_widget import RichIPythonWidget from IPython.qt.inprocess import QtInProcessKernelManager @@ -10,10 +11,38 @@ def print_process_id(): print('Process ID is:', os.getpid()) +def init_asyncio_patch(): + """set default asyncio policy to be compatible with tornado + Tornado 6 (at least) is not compatible with the default + asyncio implementation on Windows + Pick the older SelectorEventLoopPolicy on Windows + if the known-incompatible default policy is in use. + do this as early as possible to make it a low priority and overrideable + ref: https://github.com/tornadoweb/tornado/issues/2608 + FIXME: if/when tornado supports the defaults in asyncio, + remove and bump tornado requirement for py38 + """ + if sys.platform.startswith("win") and sys.version_info >= (3, 8): + import asyncio + try: + from asyncio import ( + WindowsProactorEventLoopPolicy, + WindowsSelectorEventLoopPolicy, + ) + except ImportError: + pass + # not affected + else: + if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: + # WindowsProactorEventLoopPolicy is not compatible with tornado 6 + # fallback to the pre-3.8 default of Selector + asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) + def main(): # Print the ID of the main process print_process_id() + init_asyncio_patch() app = guisupport.get_app_qt4() # Create an in-process kernel diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index a295c0a2f..2b334e532 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -1,5 +1,6 @@ from __future__ import print_function import os +import sys from IPython.kernel.inprocess import InProcessKernelManager from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell @@ -9,12 +10,41 @@ def print_process_id(): print('Process ID is:', os.getpid()) +def init_asyncio_patch(): + """set default asyncio policy to be compatible with tornado + Tornado 6 (at least) is not compatible with the default + asyncio implementation on Windows + Pick the older SelectorEventLoopPolicy on Windows + if the known-incompatible default policy is in use. + do this as early as possible to make it a low priority and overrideable + ref: https://github.com/tornadoweb/tornado/issues/2608 + FIXME: if/when tornado supports the defaults in asyncio, + remove and bump tornado requirement for py38 + """ + if sys.platform.startswith("win") and sys.version_info >= (3, 8): + import asyncio + try: + from asyncio import ( + WindowsProactorEventLoopPolicy, + WindowsSelectorEventLoopPolicy, + ) + except ImportError: + pass + # not affected + else: + if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: + # WindowsProactorEventLoopPolicy is not compatible with tornado 6 + # fallback to the pre-3.8 default of Selector + asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) + + def main(): print_process_id() # Create an in-process kernel # >>> print_process_id() # will print the same process ID as the main process + init_asyncio_patch() kernel_manager = InProcessKernelManager() kernel_manager.start_kernel() kernel = kernel_manager.kernel From 50da1077d985221254848d36b6a77d863f9eaaed Mon Sep 17 00:00:00 2001 From: Eric Prestat Date: Wed, 29 Jan 2020 11:56:45 +0000 Subject: [PATCH 0338/1195] Update deprecated import in some examples. --- examples/embedding/inprocess_qtconsole.py | 4 ++-- examples/embedding/inprocess_terminal.py | 4 ++-- examples/embedding/internal_ipkernel.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/embedding/inprocess_qtconsole.py b/examples/embedding/inprocess_qtconsole.py index 2e7b50ab1..8ab5d7389 100644 --- a/examples/embedding/inprocess_qtconsole.py +++ b/examples/embedding/inprocess_qtconsole.py @@ -2,8 +2,8 @@ import os import sys -from IPython.qt.console.rich_ipython_widget import RichIPythonWidget -from IPython.qt.inprocess import QtInProcessKernelManager +from qtconsole.rich_ipython_widget import RichIPythonWidget +from qtconsole.inprocess import QtInProcessKernelManager from IPython.lib import guisupport diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index 2b334e532..1d1ddc0bf 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -2,8 +2,8 @@ import os import sys -from IPython.kernel.inprocess import InProcessKernelManager -from IPython.terminal.console.interactiveshell import ZMQTerminalInteractiveShell +from ipykernel.inprocess import InProcessKernelManager +from jupyter_console.ptshell import ZMQTerminalInteractiveShell def print_process_id(): diff --git a/examples/embedding/internal_ipkernel.py b/examples/embedding/internal_ipkernel.py index 7cba947d0..f20b112f3 100644 --- a/examples/embedding/internal_ipkernel.py +++ b/examples/embedding/internal_ipkernel.py @@ -5,7 +5,7 @@ import sys from IPython.lib.kernel import connect_qtconsole -from IPython.kernel.zmq.kernelapp import IPKernelApp +from ipykernel.kernelapp import IPKernelApp #----------------------------------------------------------------------------- # Functions and classes From 3bb1bf518d5b103ac02babdaf8143a8c9e84dc9f Mon Sep 17 00:00:00 2001 From: "Mark E. Haase" Date: Fri, 31 Jan 2020 09:32:17 -0500 Subject: [PATCH 0339/1195] Add support for kernel interrupts The trio runner didn't support kernel interrupts: it would just continue to hang. I'm not sure why, but kernelapp.py ignores SIGINT. So in this commit, the Trio runner registers a new SIGINT handler that uses Trio cancel scopes to cancel the currently executing cell. The downside to this approach is that if a cell hangs in a loop that never reaches a Trio checkpoint, then the cancellation will have no effect. We could have sent SIGINT to the main thread (i.e. SIG_DFL), but that would have the effect of potentially raising SIGINT on one of the background tasks, which would likely force you to restart the kernel. --- ipykernel/trio_runner.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index ad4c30e72..4ba1da196 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -1,5 +1,6 @@ import builtins import logging +import signal import threading import traceback import warnings @@ -9,6 +10,7 @@ class TrioRunner: def __init__(self): + self._cell_cancel_scope = None self._trio_token = None def initialize(self, kernel, io_loop): @@ -21,7 +23,15 @@ def initialize(self, kernel, io_loop): name='TornadoBackground') bg_thread.start() + def interrupt(self, signum, frame): + if self._cell_cancel_scope: + self._cell_cancel_scope.cancel() + else: + raise Exception('Kernel interrupted but no cell is running') + def run(self): + old_sig = signal.signal(signal.SIGINT, self.interrupt) + def log_nursery_exc(exc): exc = '\n'.join(traceback.format_exception(type(exc), exc, exc.__traceback__)) @@ -38,13 +48,13 @@ async def trio_main(): await trio.sleep_forever() trio.run(trio_main) + signal.signal(signal.SIGINT, old_sig) def __call__(self, async_fn): async def loc(coro): - """ - We need the dummy no-op async def to protect from - trio's internal. See https://github.com/python-trio/trio/issues/89 - """ - return await coro + self._cell_cancel_scope = trio.CancelScope() + with self._cell_cancel_scope: + return await coro + self._cell_cancel_scope = None return trio.from_thread.run(loc, async_fn, trio_token=self._trio_token) From 9dc80db35129a091300d08f292f951b00eac43e1 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 1 Feb 2020 17:17:05 -0800 Subject: [PATCH 0340/1195] try to fix tests on 3.8 --- ipykernel/inprocess/tests/test_kernel.py | 34 ++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index aa7cf6725..31509e9e3 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -20,9 +20,43 @@ from StringIO import StringIO +def _init_asyncio_patch(): + """set default asyncio policy to be compatible with tornado + + Tornado 6 (at least) is not compatible with the default + asyncio implementation on Windows + + Pick the older SelectorEventLoopPolicy on Windows + if the known-incompatible default policy is in use. + + do this as early as possible to make it a low priority and overrideable + + ref: https://github.com/tornadoweb/tornado/issues/2608 + + FIXME: if/when tornado supports the defaults in asyncio, + remove and bump tornado requirement for py38 + """ + if sys.platform.startswith("win") and sys.version_info >= (3, 8): + import asyncio + try: + from asyncio import ( + WindowsProactorEventLoopPolicy, + WindowsSelectorEventLoopPolicy, + ) + except ImportError: + pass + # not affected + else: + if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: + # WindowsProactorEventLoopPolicy is not compatible with tornado 6 + # fallback to the pre-3.8 default of Selector + asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) + + class InProcessKernelTestCase(unittest.TestCase): def setUp(self): + _init_asyncio_patch() self.km = InProcessKernelManager() self.km.start_kernel() self.kc = self.km.client() From 979e6fd2db9824934a89d97011d3f3eccc6d4499 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 1 Feb 2020 17:26:41 -0800 Subject: [PATCH 0341/1195] Don't indicate support for 3.4. While it _may_ work, we are not having CI on 3.4 anymore; so it is risky to indicate it work on 3.4 in setup.py --- setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 793ce9357..64ff06ee1 100644 --- a/setup.py +++ b/setup.py @@ -17,8 +17,8 @@ import re v = sys.version_info -if v[:2] < (3, 4): - error = "ERROR: %s requires Python version 3.4 or above." % name +if v[:2] < (3, 5): + error = "ERROR: %s requires Python version 3.5 or above." % name print(error, file=sys.stderr) sys.exit(1) @@ -86,7 +86,7 @@ def run(self): long_description="The IPython kernel for Jupyter", platforms="Linux, Mac OS X, Windows", keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], - python_requires='>=3.4', + python_requires='>=3.5', install_requires=[ 'ipython>=5.0.0', 'traitlets>=4.1.0', From b006d4f0e163fdfcca542c661e9a7cffe27ba1a5 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Mon, 3 Feb 2020 12:06:27 +0100 Subject: [PATCH 0342/1195] Avoid overflushing --- ipykernel/iostream.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index b7a2c0807..7ab35f377 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -296,6 +296,7 @@ def __init__(self, session, pub_thread, name, pipe=None, echo=None): self.parent_header = {} self._master_pid = os.getpid() self._flush_pending = False + self._subprocess_flush_pending = False self._io_loop = pub_thread.io_loop self._new_buffer() self.echo = None @@ -362,6 +363,7 @@ def _flush(self): unless the thread has been destroyed (e.g. forked subprocess). """ self._flush_pending = False + self._subprocess_flush_pending = False if self.echo is not None: try: @@ -401,13 +403,13 @@ def write(self, string): # only touch the buffer in the IO thread to avoid races self.pub_thread.schedule(lambda : self._buffer.write(string)) if is_child: - # newlines imply flush in subprocesses # mp.Pool cannot be trusted to flush promptly (or ever), # and this helps. - if '\n' in string: - self.flush() - else: - self.pub_thread.schedule(self._flush) + if self._subprocess_flush_pending: + return + self._subprocess_flush_pending = True + # We can not rely on self._io_loop.call_later from a subprocess + self.pub_thread.schedule(self._flush) else: self._schedule_flush() From 7125d8537753106bc5885a906b8dfa90379e1091 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Mon, 2 Mar 2020 10:09:37 +0100 Subject: [PATCH 0343/1195] Add control channel --- ipykernel/inprocess/client.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 2e562f040..5de4f774d 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -41,6 +41,7 @@ class InProcessKernelClient(KernelClient): shell_channel_class = Type(InProcessChannel) iopub_channel_class = Type(InProcessChannel) stdin_channel_class = Type(InProcessChannel) + control_channel_class = Type(InProcessChannel) hb_channel_class = Type(InProcessHBChannel) kernel = Instance('ipykernel.inprocess.ipkernel.InProcessKernel', @@ -82,6 +83,12 @@ def stdin_channel(self): self._stdin_channel = self.stdin_channel_class(self) return self._stdin_channel + @property + def control_channel(self): + if self._control_channel is None: + self._control_channel = self.control_channel_class(self) + return self._control_channel + @property def hb_channel(self): if self._hb_channel is None: From 531f0e1423dc7cfe2abb1b51c94b14b6db2f9821 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 2 Mar 2020 15:39:29 -0500 Subject: [PATCH 0344/1195] Use the IPython custom debugger. --- ipykernel/kernelapp.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index d683e8263..7fba9b8db 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -534,6 +534,13 @@ def _init_asyncio_patch(self): # fallback to the pre-3.8 default of Selector asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) + def init_pdb(self): + """Replace pdb with IPython's version that is e.g. interruptible.""" + import pdb + from IPython.core import debugger + pdb.Pdb = debugger.Pdb + pdb.set_trace = debugger.set_trace + @catch_config_error def initialize(self, argv=None): self._init_asyncio_patch() @@ -541,6 +548,7 @@ def initialize(self, argv=None): if self.subapp is not None: return + self.init_pdb() self.init_blackhole() self.init_connection_file() self.init_poller() From 5431a027629cb954f272f4b9ae98962bfa83ef29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=2E=20K=C3=A4rkk=C3=A4inen?= Date: Sat, 14 Mar 2020 14:27:17 +0200 Subject: [PATCH 0345/1195] Suppress internal traceback when interrupting program and provide useful diagnostic message. --- ipykernel/kernelbase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 9685d5470..263ca56fe 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -575,7 +575,7 @@ def complete_request(self, stream, ident, parent): content = parent['content'] code = content['code'] cursor_pos = content['cursor_pos'] - + matches = yield gen.maybe_future(self.do_complete(code, cursor_pos)) matches = json_clean(matches) completion_msg = self.session.send(stream, 'complete_reply', @@ -886,7 +886,7 @@ def _input_request(self, prompt, ident, parent, password=False): self.log.warning("Invalid Message:", exc_info=True) except KeyboardInterrupt: # re-raise KeyboardInterrupt, to truncate traceback - raise KeyboardInterrupt + raise KeyboardInterrupt("Interrupted by user") from None else: break try: From e385c286a2fc2336fa159cb85a6dc4ee353283b3 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Sat, 21 Mar 2020 14:11:20 +0100 Subject: [PATCH 0346/1195] Update for v5.2 --- docs/changelog.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 69e67641a..962b0cf14 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,21 @@ Changes in IPython kernel ========================= +5.2 +--- + +5.2.0 +***** + +5.2.0 Includes several bugfixes and internal logic improvements. + +- Produce better traceback when kernel is interrupted (:ghpull:`491`) +- Add ``InProcessKernelClient.control_channel`` for compatibility with jupyter-client v6.0.0 (:ghpull:`489`) +- Drop support for Python 3.4 (:ghpull:`483`) +- Work around issue related to Tornado with python3.8 on Windows (:ghpull:`480`, :ghpull:`481`) +- Prevent entering event loop if it is None (:ghpull:`464`) +- Use ``shell.input_transformer_manager`` when available (:ghpull:`411`) + 5.1 --- From 5cc538a4b02136569799e1d3394a58302ace53e8 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 21 Mar 2020 16:00:20 -0500 Subject: [PATCH 0347/1195] Release 5.2.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index ca224aba0..ee1abafef 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 2, 0, 'dev') +version_info = (5, 2, 0) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 0b7aadb86d8257bc0201051a043589a58dd2b887 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 21 Mar 2020 16:01:44 -0500 Subject: [PATCH 0348/1195] back to dev version --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index ee1abafef..7e9b44ada 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 2, 0) +version_info = (5, 3, 0, 'dev') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 74879243f19f43286f8fd855c95e84d752d6d835 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 25 Mar 2020 21:33:42 -0400 Subject: [PATCH 0349/1195] add offset argument to seek in io test --- ipykernel/tests/test_io.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index c1ac9c86e..c0e7021af 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -35,7 +35,6 @@ def test_io_api(): with nt.assert_raises(io.UnsupportedOperation): stream.readline() with nt.assert_raises(io.UnsupportedOperation): - stream.seek() + stream.seek(0) with nt.assert_raises(io.UnsupportedOperation): stream.tell() - From 40854cadf557e5120cd0c970588e23ff5565eebb Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 30 Mar 2020 14:04:10 -0400 Subject: [PATCH 0350/1195] A test for interruptions. --- ipykernel/tests/test_kernel.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 219ded2d3..f2fdb9109 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -330,3 +330,17 @@ def test_shutdown(): else: break assert not km.is_alive() + + +def test_interrupt_during_input(): + """Kernel exits after being interrupted, while waiting in input().""" + with new_kernel() as kc: + km = kc.parent + msg_id = kc.execute("input()") + time.sleep(1) # Make sure it's actually waiting for input. + km.interrupt_kernel() + + # If we failed to interrupt interrupt, this will timeout: + reply = kc.get_shell_msg(timeout=TIMEOUT) + from .test_message_spec import validate_message + validate_message(reply, 'execute_reply', msg_id) From 2c6779f988c06b3cba796104b5c94ca255d6df77 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Mon, 30 Mar 2020 14:19:26 -0400 Subject: [PATCH 0351/1195] New interrupt mechanism that works better in some cases. --- ipykernel/parentpoller.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 446f656df..2f3411c5c 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -14,6 +14,16 @@ except ImportError: from thread import interrupt_main # Py 2 from threading import Thread +if hasattr(signal, "raise_signal"): + # Python 3.8 and later: + raise_signal = signal.raise_signal +elif ctypes: + # Emulate raise_signal using ctypes: + raise_signal = getattr(ctypes.PyDLL(None), "raise") +else: + # Give up: + def raise_signal(sig): + warnings.warn("Process interruption won't work, upgrade to Python 3.8") from traitlets.log import get_logger @@ -103,7 +113,9 @@ def run(self): # check if signal handler is callable # to avoid 'int not callable' error (Python issue #23395) if callable(signal.getsignal(signal.SIGINT)): - interrupt_main() + # interrupt_main() doesn't seem to interrupt input(), + # so use a different mechanism: + raise_signal(signal.SIGINT) elif handle == self.parent_handle: get_logger().warning("Parent appears to have exited, shutting down.") From acbfccba075540f3ed8d9b7919317d5c9fd3ba2a Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 1 Apr 2020 20:39:42 -0400 Subject: [PATCH 0352/1195] Apparently working interruption on Windows. --- ipykernel/kernelbase.py | 11 ++++++++--- ipykernel/parentpoller.py | 2 -- ipykernel/tests/test_kernel.py | 9 ++++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 9685d5470..155908475 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -863,6 +863,7 @@ def _input_request(self, prompt, ident, parent, password=False): # Flush output before making the request. sys.stderr.flush() sys.stdout.flush() + # flush the stdin socket, to purge stale replies while True: try: @@ -881,14 +882,18 @@ def _input_request(self, prompt, ident, parent, password=False): # Await a response. while True: try: - ident, reply = self.session.recv(self.stdin_socket, 0) - except Exception: + ident, reply = self.session.recv(self.stdin_socket) + except Exception as e: self.log.warning("Invalid Message:", exc_info=True) except KeyboardInterrupt: # re-raise KeyboardInterrupt, to truncate traceback raise KeyboardInterrupt else: - break + # Use polling with sleep so KeyboardInterrupts can get through: + if (ident, reply) == (None, None): + time.sleep(0.001) + else: + break try: value = py3compat.unicode_to_str(reply['content']['value']) except: diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 2f3411c5c..d4a36ca8d 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -113,8 +113,6 @@ def run(self): # check if signal handler is callable # to avoid 'int not callable' error (Python issue #23395) if callable(signal.getsignal(signal.SIGINT)): - # interrupt_main() doesn't seem to interrupt input(), - # so use a different mechanism: raise_signal(signal.SIGINT) elif handle == self.parent_handle: diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index f2fdb9109..d543e5175 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -333,14 +333,17 @@ def test_shutdown(): def test_interrupt_during_input(): - """Kernel exits after being interrupted, while waiting in input().""" + """Kernel exits after being interrupted, while waiting in input(). + + input() appears to have issues other functions don't, and it needs to be + interruptible for pdb to be interruptible. + """ with new_kernel() as kc: km = kc.parent msg_id = kc.execute("input()") time.sleep(1) # Make sure it's actually waiting for input. km.interrupt_kernel() - # If we failed to interrupt interrupt, this will timeout: reply = kc.get_shell_msg(timeout=TIMEOUT) from .test_message_spec import validate_message - validate_message(reply, 'execute_reply', msg_id) + validate_message(reply, 'execute_reply', msg_id) \ No newline at end of file From 41f9cfa1c7eef590072aea059912500cd00782d9 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Fri, 3 Apr 2020 13:37:22 -0400 Subject: [PATCH 0353/1195] Actually interrupt_main() is fine. --- ipykernel/parentpoller.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index d4a36ca8d..446f656df 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -14,16 +14,6 @@ except ImportError: from thread import interrupt_main # Py 2 from threading import Thread -if hasattr(signal, "raise_signal"): - # Python 3.8 and later: - raise_signal = signal.raise_signal -elif ctypes: - # Emulate raise_signal using ctypes: - raise_signal = getattr(ctypes.PyDLL(None), "raise") -else: - # Give up: - def raise_signal(sig): - warnings.warn("Process interruption won't work, upgrade to Python 3.8") from traitlets.log import get_logger @@ -113,7 +103,7 @@ def run(self): # check if signal handler is callable # to avoid 'int not callable' error (Python issue #23395) if callable(signal.getsignal(signal.SIGINT)): - raise_signal(signal.SIGINT) + interrupt_main() elif handle == self.parent_handle: get_logger().warning("Parent appears to have exited, shutting down.") From 0573d1f1c9ef4109f237ae8273c5fe6cbbca5773 Mon Sep 17 00:00:00 2001 From: dalthviz Date: Thu, 9 Apr 2020 13:00:08 -0500 Subject: [PATCH 0354/1195] Handle system commands that use UNC paths on Windows --- ipykernel/zmqshell.py | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index af19c7487..5feece09e 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -42,7 +42,7 @@ ) from IPython.utils import openpy from ipykernel.jsonutil import json_clean, encode_images -from IPython.utils.process import arg_split +from IPython.utils.process import arg_split, system from ipython_genutils import py3compat from ipython_genutils.py3compat import unicode_type from traitlets import ( @@ -601,5 +601,41 @@ def init_virtualenv(self): # https://ipython.readthedocs.io/en/latest/install/kernel_install.html pass + def system_piped(self, cmd): + """Call the given cmd in a subprocess, piping stdout/err + + Parameters + ---------- + cmd : str + Command to execute (can not end in '&', as background processes are + not supported. Should not be a command that expects input + other than simple text. + """ + if cmd.rstrip().endswith('&'): + # this is *far* from a rigorous test + # We do not support backgrounding processes because we either use + # pexpect or pipes to read from. Users can always just call + # os.system() or use ip.system=ip.system_raw + # if they really want a background process. + raise OSError("Background processes not supported.") + + # we explicitly do NOT return the subprocess status code, because + # a non-None value would trigger :func:`sys.displayhook` calls. + # Instead, we store the exit_code in user_ns. + # Also, protect system call from UNC paths on Windows here too + # as is done in InteractiveShell.system_raw + if sys.platform == 'win32': + cmd = self.var_expand(cmd, depth=1) + from IPython.utils._process_win32 import AvoidUNCPath + with AvoidUNCPath() as path: + if path is not None: + cmd = 'pushd %s &&%s' % (path, cmd) + self.user_ns['_exit_code'] = system(cmd) + else: + self.user_ns['_exit_code'] = system(self.var_expand(cmd, depth=1)) + + # Ensure new system_piped implementation is used + system = system_piped + InteractiveShellABC.register(ZMQInteractiveShell) From 75c5b7c3a29e31e42a8ee65c10cb41d083429dcf Mon Sep 17 00:00:00 2001 From: dalthviz Date: Thu, 9 Apr 2020 15:51:56 -0500 Subject: [PATCH 0355/1195] Add test for UNC paths on Windows --- ipykernel/tests/test_kernel.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 219ded2d3..acc9fdf6e 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -7,6 +7,7 @@ import ast import io import os.path +import platform import sys import time @@ -316,6 +317,26 @@ def test_message_order(): assert reply['content']['execution_count'] == i assert reply['parent_header']['msg_id'] == msg_id +@dec.skipif( + sys.platform.startswith('linux'), "Test UNC paths handling on Windows") +def test_unc_paths(): + with kernel() as kc, TemporaryDirectory() as td: + iopub = kc.iopub_channel + drive_file_path = os.path.join(td, 'unc.txt') + with open(drive_file_path, 'w+') as f: + f.write('# UNC test') + + unc_root = '\\\\{0:s}'.format(platform.node()) + file_path = os.path.splitdrive(os.path.dirname(drive_file_path))[1] + unc_file_path = os.path.join(unc_root, file_path[1:]) + + _, reply = execute("ls {0:s}".format(unc_file_path), kc=kc) + _check_status(reply) + + stdout, stderr = assemble_output(iopub) + assert '10 unc.txt' in stdout, stdout + assert stderr == '', stderr + def test_shutdown(): """Kernel exits after polite shutdown_request""" From d3ae727ed7ad296b763cba97ea1ca8b731581bb7 Mon Sep 17 00:00:00 2001 From: dalthviz Date: Thu, 9 Apr 2020 17:40:29 -0500 Subject: [PATCH 0356/1195] Testing --- ipykernel/tests/test_kernel.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index acc9fdf6e..a15f39cfd 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -317,25 +317,35 @@ def test_message_order(): assert reply['content']['execution_count'] == i assert reply['parent_header']['msg_id'] == msg_id -@dec.skipif( - sys.platform.startswith('linux'), "Test UNC paths handling on Windows") + +@dec.skipif(sys.platform.startswith('linux')) def test_unc_paths(): with kernel() as kc, TemporaryDirectory() as td: - iopub = kc.iopub_channel drive_file_path = os.path.join(td, 'unc.txt') with open(drive_file_path, 'w+') as f: f.write('# UNC test') - unc_root = '\\\\{0:s}'.format(platform.node()) file_path = os.path.splitdrive(os.path.dirname(drive_file_path))[1] unc_file_path = os.path.join(unc_root, file_path[1:]) - _, reply = execute("ls {0:s}".format(unc_file_path), kc=kc) - _check_status(reply) + iopub = kc.iopub_channel - stdout, stderr = assemble_output(iopub) - assert '10 unc.txt' in stdout, stdout - assert stderr == '', stderr + kc.execute("cd {0:s}".format(unc_file_path)) + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + assert reply['content']['status'] == 'ok' + out, err = assemble_output(iopub) + assert unc_file_path in out + + flush_channels(kc) + kc.execute(code="ls") + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + assert reply['content']['status'] == 'ok' + out, err = assemble_output(iopub) + assert 'unc.txt' in out + + kc.execute(code="cd") + reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + assert reply['content']['status'] == 'ok' def test_shutdown(): From 1c0b856a9c9f287f26d935721934181b01841f6b Mon Sep 17 00:00:00 2001 From: dalthviz Date: Thu, 9 Apr 2020 18:39:41 -0500 Subject: [PATCH 0357/1195] Testing --- ipykernel/tests/test_kernel.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index a15f39cfd..907ce02f7 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -7,7 +7,6 @@ import ast import io import os.path -import platform import sys import time @@ -324,7 +323,7 @@ def test_unc_paths(): drive_file_path = os.path.join(td, 'unc.txt') with open(drive_file_path, 'w+') as f: f.write('# UNC test') - unc_root = '\\\\{0:s}'.format(platform.node()) + unc_root = '\\\\localhost\\C$' file_path = os.path.splitdrive(os.path.dirname(drive_file_path))[1] unc_file_path = os.path.join(unc_root, file_path[1:]) From 2391590b14bc0e50e5113d03a9d77b1a175ed030 Mon Sep 17 00:00:00 2001 From: dalthviz Date: Mon, 13 Apr 2020 19:35:36 -0500 Subject: [PATCH 0358/1195] Testing: Skip test_complete on Linux --- ipykernel/tests/test_kernel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 907ce02f7..9fb2c4863 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -256,6 +256,7 @@ def test_is_complete(): assert reply['content']['status'] == 'complete' +@dec.skipif(sys.platform.startswith('linux')) def test_complete(): with kernel() as kc: execute(u'a = 1', kc=kc) From e5f17d616cfbae69eb9633fb438b83cc1573297a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 14 Apr 2020 14:25:14 -0500 Subject: [PATCH 0359/1195] Release 5.2.1 --- docs/changelog.rst | 6 ++++++ ipykernel/_version.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 962b0cf14..55002cdc3 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,12 @@ Changes in IPython kernel 5.2 --- +5.2.1 +***** + +- Handle system commands that use UNC paths on Windows (:ghpull:`500`) +- Add offset argument to seek in io test (:ghpull:`496`) + 5.2.0 ***** diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7e9b44ada..25bc75f25 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 3, 0, 'dev') +version_info = (5, 2, 1) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 7b7046adb9230bfdfc59fd332ae1647cdd521e3c Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 14 Apr 2020 14:26:02 -0500 Subject: [PATCH 0360/1195] back to dev version --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 25bc75f25..7e9b44ada 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 2, 1) +version_info = (5, 3, 0, 'dev') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 45b94e9e810352f2a96fa2882e650e085d040b3b Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 21 Apr 2020 14:53:15 -0400 Subject: [PATCH 0361/1195] Tweak docstring. --- ipykernel/tests/test_kernel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index d543e5175..48251dcc1 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -333,10 +333,10 @@ def test_shutdown(): def test_interrupt_during_input(): - """Kernel exits after being interrupted, while waiting in input(). + """The kernel exits after being interrupted, while waiting in input(). input() appears to have issues other functions don't, and it needs to be - interruptible for pdb to be interruptible. + interruptible in order for pdb to be interruptible. """ with new_kernel() as kc: km = kc.parent @@ -346,4 +346,4 @@ def test_interrupt_during_input(): # If we failed to interrupt interrupt, this will timeout: reply = kc.get_shell_msg(timeout=TIMEOUT) from .test_message_spec import validate_message - validate_message(reply, 'execute_reply', msg_id) \ No newline at end of file + validate_message(reply, 'execute_reply', msg_id) From 06d6858ba92ee9e08139c7d1accb31181ce4dd90 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 21 Apr 2020 15:33:21 -0400 Subject: [PATCH 0362/1195] Explanatory comment expanded. --- ipykernel/kernelbase.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index b02d7e110..85bd73332 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -893,7 +893,9 @@ def _input_request(self, prompt, ident, parent, password=False): # re-raise KeyboardInterrupt, to truncate traceback raise KeyboardInterrupt("Interrupted by user") from None else: - # Use polling with sleep so KeyboardInterrupts can get through: + # Use polling with sleep so KeyboardInterrupts can get through; + # doing a blocking recv() means stdin reads are uninterruptible + # on Windows: if (ident, reply) == (None, None): time.sleep(0.001) else: From 939c23b63cc76e9742ede1241150ebd77fa16ad9 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 21 Apr 2020 16:41:18 -0400 Subject: [PATCH 0363/1195] Use the new InterruptiblePdb. --- ipykernel/kernelapp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 7fba9b8db..51d2bc860 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -538,6 +538,7 @@ def init_pdb(self): """Replace pdb with IPython's version that is e.g. interruptible.""" import pdb from IPython.core import debugger + debugger.Pdb = debugger.InterruptiblePdb pdb.Pdb = debugger.Pdb pdb.set_trace = debugger.set_trace From c566e4b54602d393b9fb16d3722e75086573e676 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 5 May 2020 13:55:57 -0400 Subject: [PATCH 0364/1195] Use polling with select(). --- ipykernel/kernelbase.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 85bd73332..8c808566c 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -886,20 +886,25 @@ def _input_request(self, prompt, ident, parent, password=False): # Await a response. while True: try: - ident, reply = self.session.recv(self.stdin_socket) - except Exception as e: - self.log.warning("Invalid Message:", exc_info=True) + # Use polling with select() so KeyboardInterrupts can get + # through; doing a blocking recv() means stdin reads are + # uninterruptible on Windows. We need a timeout because + # zmq.select() is also uninterruptible, but at least this + # way reads get noticed immediately and KeyboardInterrupts + # get noticed fairly quickly by human response time standards. + rlist, _, xlist = zmq.select( + [self.stdin_socket], [], [self.stdin_socket], 0.01 + ) + if rlist or xlist: + ident, reply = self.session.recv(self.stdin_socket) + if (ident, reply) != (None, None): + break except KeyboardInterrupt: # re-raise KeyboardInterrupt, to truncate traceback raise KeyboardInterrupt("Interrupted by user") from None - else: - # Use polling with sleep so KeyboardInterrupts can get through; - # doing a blocking recv() means stdin reads are uninterruptible - # on Windows: - if (ident, reply) == (None, None): - time.sleep(0.001) - else: - break + except Exception as e: + self.log.warning("Invalid Message:", exc_info=True) + try: value = py3compat.unicode_to_str(reply['content']['value']) except: From cf5d6274f09bfd7264e921b79062b81f21586735 Mon Sep 17 00:00:00 2001 From: dalthviz Date: Mon, 11 May 2020 21:34:27 -0500 Subject: [PATCH 0365/1195] Skip test_unc_paths if OS is not Windows --- ipykernel/tests/test_kernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 9fb2c4863..85df48870 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -256,7 +256,7 @@ def test_is_complete(): assert reply['content']['status'] == 'complete' -@dec.skipif(sys.platform.startswith('linux')) +@dec.skipif(sys.platform != 'win32', "only run on Windows") def test_complete(): with kernel() as kc: execute(u'a = 1', kc=kc) From 33904b053b610d713868b28de87f4e2d819a6d00 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 12 May 2020 11:14:20 -0400 Subject: [PATCH 0366/1195] Fix punctuation. --- ipykernel/tests/test_kernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 48251dcc1..f7c780fa7 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -333,7 +333,7 @@ def test_shutdown(): def test_interrupt_during_input(): - """The kernel exits after being interrupted, while waiting in input(). + """The kernel exits after being interrupted while waiting in input(). input() appears to have issues other functions don't, and it needs to be interruptible in order for pdb to be interruptible. From 3250f8625aec0c1df30013b2229d656bba11ceb0 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 19 May 2020 10:53:24 -0400 Subject: [PATCH 0367/1195] Tweak to trigger build. --- ipykernel/tests/test_kernel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index f7c780fa7..239f957f3 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -333,7 +333,8 @@ def test_shutdown(): def test_interrupt_during_input(): - """The kernel exits after being interrupted while waiting in input(). + """ + The kernel exits after being interrupted while waiting in input(). input() appears to have issues other functions don't, and it needs to be interruptible in order for pdb to be interruptible. From 029ac5b52746c61be8bb2cf5827bfd870d64aa27 Mon Sep 17 00:00:00 2001 From: astromancer Date: Wed, 20 May 2020 08:49:35 +0200 Subject: [PATCH 0368/1195] fix display imports --- ipykernel/pylab/backend_inline.py | 2 +- ipykernel/tests/test_message_spec.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index b4d31077a..f93c98e4d 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -15,7 +15,7 @@ from matplotlib._pylab_helpers import Gcf from IPython.core.getipython import get_ipython -from IPython.core.display import display +from IPython.display import display from .config import InlineBackend diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index e75e219c6..cafb99ba1 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -541,7 +541,7 @@ def test_stream(): def test_display_data(): flush_channels() - msg_id, reply = execute("from IPython.core.display import display; display(1)") + msg_id, reply = execute("from IPython.display import display; display(1)") display = KC.iopub_channel.get_msg(timeout=TIMEOUT) validate_message(display, 'display_data', parent=msg_id) From 4c7d87dad2bacda6a37f633ed6f9429ab4e4e40a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 20 May 2020 05:08:12 -0500 Subject: [PATCH 0369/1195] Update changelog --- docs/changelog.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 55002cdc3..45f481406 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,21 @@ Changes in IPython kernel ========================= +5.3 +--- + +5.3.0 +***** + +5.3.0 Adds support for Trio event loops and has some bug fixes. + +- Fix ipython display imports (:ghpull:`509`) +- Skip test_unc_paths if OS is not Windows (:ghpull:`507`) +- Allow interrupting input() on Windows, as part of effort to make pdb interruptible (:ghpull:`498`) +- Add Trio Loop (:ghpull:`479`) +- Flush from process even without newline (:ghpull:`478`) + + 5.2 --- From 8fd62975693b913f02512624fc3631f0d247fd51 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 20 May 2020 05:18:55 -0500 Subject: [PATCH 0370/1195] Release 5.3.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7e9b44ada..0a4e63349 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 3, 0, 'dev') +version_info = (5, 3, 0) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 65c7a8d262e6d2588757f215f22bc5ce7fe9bd51 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Wed, 20 May 2020 08:06:22 -0400 Subject: [PATCH 0371/1195] Better explanation --- ipykernel/kernelapp.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 51d2bc860..7c85eb925 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -535,7 +535,11 @@ def _init_asyncio_patch(self): asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) def init_pdb(self): - """Replace pdb with IPython's version that is e.g. interruptible.""" + """Replace pdb with IPython's version that is interruptible. + + With the non-interruptible version, stopping pdb() locks up the kernel in a + non-recoverable state. + """ import pdb from IPython.core import debugger debugger.Pdb = debugger.InterruptiblePdb From 599870244a831183e5c485a8013dfa3520c7f560 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 26 May 2020 11:28:32 -0400 Subject: [PATCH 0372/1195] Make sure we use interruptible version of pdb --- ipykernel/kernelapp.py | 8 +++++--- ipykernel/tests/test_kernel.py | 21 +++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 219567404..ac643dac1 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -547,9 +547,11 @@ def init_pdb(self): """ import pdb from IPython.core import debugger - debugger.Pdb = debugger.InterruptiblePdb - pdb.Pdb = debugger.Pdb - pdb.set_trace = debugger.set_trace + if hasattr(debugger, "InterruptiblePdb"): + # Only available in newer IPython releases: + debugger.Pdb = debugger.InterruptiblePdb + pdb.Pdb = debugger.Pdb + pdb.set_trace = debugger.set_trace @catch_config_error def initialize(self, argv=None): diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index e63c30956..77cea2cc4 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -379,3 +379,24 @@ def test_interrupt_during_input(): reply = kc.get_shell_msg(timeout=TIMEOUT) from .test_message_spec import validate_message validate_message(reply, 'execute_reply', msg_id) + + +def test_interrupt_during_pdb_set_trace(): + """ + The kernel exits after being interrupted while waiting in pdb.set_trace(). + + Merely testing input() isn't enough, pdb has its own issues that need + to be handled in addition. + """ + with new_kernel() as kc: + km = kc.parent + msg_id = kc.execute("import pdb; pdb.set_trace()") + msg_id2 = kc.execute("3 + 4") + time.sleep(1) # Make sure it's actually waiting for input. + km.interrupt_kernel() + # If we failed to interrupt interrupt, this will timeout: + from .test_message_spec import validate_message + reply = kc.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'execute_reply', msg_id) + reply = kc.get_shell_msg(timeout=TIMEOUT) + validate_message(reply, 'execute_reply', msg_id2) \ No newline at end of file From 700652c0c984f6cbfc800a83eed2ceac59fa296e Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 26 May 2020 11:33:39 -0400 Subject: [PATCH 0373/1195] Note a caveat. --- ipykernel/tests/test_kernel.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 77cea2cc4..7897573d0 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -387,6 +387,8 @@ def test_interrupt_during_pdb_set_trace(): Merely testing input() isn't enough, pdb has its own issues that need to be handled in addition. + + This test will fail with versions of IPython < 7.14.0. """ with new_kernel() as kc: km = kc.parent From 7b3a6f79ae52c6470890d734a3044f6f7ba89c2e Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 26 May 2020 11:45:50 -0400 Subject: [PATCH 0374/1195] Skip test on old IPythons. --- ipykernel/tests/test_kernel.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 7897573d0..4748355c4 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -12,8 +12,10 @@ import nose.tools as nt from flaky import flaky +import pytest from IPython.testing import decorators as dec, tools as tt +import IPython from ipython_genutils import py3compat from IPython.paths import locate_profile from ipython_genutils.tempdir import TemporaryDirectory @@ -381,6 +383,10 @@ def test_interrupt_during_input(): validate_message(reply, 'execute_reply', msg_id) +@pytest.mark.skipif( + tuple(map(int, IPython.__version__.split("."))) < (7, 14, 0), + reason="Need new IPython" +) def test_interrupt_during_pdb_set_trace(): """ The kernel exits after being interrupted while waiting in pdb.set_trace(). From d212586b58bc99098ad374010eccc6ecb01f6127 Mon Sep 17 00:00:00 2001 From: Itamar Turner-Trauring Date: Tue, 26 May 2020 14:04:10 -0400 Subject: [PATCH 0375/1195] More robust version-parsing method --- ipykernel/tests/test_kernel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 4748355c4..324f08caa 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -13,6 +13,7 @@ import nose.tools as nt from flaky import flaky import pytest +from packaging import version from IPython.testing import decorators as dec, tools as tt import IPython @@ -384,7 +385,7 @@ def test_interrupt_during_input(): @pytest.mark.skipif( - tuple(map(int, IPython.__version__.split("."))) < (7, 14, 0), + version.parse(IPython.__version__) < version.parse("7.14.0"), reason="Need new IPython" ) def test_interrupt_during_pdb_set_trace(): From 5c2413441d2004ee63d60378f70599cf8a49e205 Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Fri, 12 Jun 2020 13:16:04 +0300 Subject: [PATCH 0376/1195] Fix exception causes in zmqshell.py --- ipykernel/zmqshell.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 5feece09e..2538df86f 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -423,8 +423,8 @@ def autosave(self, arg_s): try: interval = int(arg_s) - except ValueError: - raise UsageError("%%autosave requires an integer, got %r" % arg_s) + except ValueError as e: + raise UsageError("%%autosave requires an integer, got %r" % arg_s) from e # javascript wants milliseconds milliseconds = 1000 * interval @@ -485,7 +485,7 @@ def enable_gui(self, gui): real_enable_gui(gui) self.active_eventloop = gui except ValueError as e: - raise UsageError("%s" % e) + raise UsageError("%s" % e) from e def init_environment(self): """Configure the user's environment.""" From 2ab863ad4a2ebadc5cccee8af7bcac43f934ee82 Mon Sep 17 00:00:00 2001 From: Joao Felipe Pimentel Date: Fri, 3 Jul 2020 19:22:51 -0300 Subject: [PATCH 0377/1195] fix #520: run post_execute and post_run_cell on async cells --- ipykernel/ipkernel.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 95e75e9ca..b8b95eab8 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -292,7 +292,13 @@ def run_cell(*args, **kwargs): coro_future = asyncio.ensure_future(coro) with self._cancel_on_sigint(coro_future): - res = yield coro_future + res = None + try: + res = yield coro_future + finally: + shell.events.trigger('post_execute') + if not silent: + shell.events.trigger('post_run_cell', res) else: # runner isn't already running, # make synchronous call, From 855b43ac120e44904493ed3fe3eb5699b71a9112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Felipe=20N=2E=20Pimentel?= Date: Sat, 4 Jul 2020 22:20:44 -0300 Subject: [PATCH 0378/1195] Change Python version on Travis to 3.7 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2baa1daac..9e1a47b7b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -57,7 +57,7 @@ after_success: - codecov matrix: include: - - python: 3.6 + - python: 3.7 env: - IPYTHON=master allow_failures: From c3db912468a5b5256ed5f8d5ace4fc769f939a0e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 5 Jul 2020 05:55:11 -0500 Subject: [PATCH 0379/1195] Release 5.3.0 --- docs/changelog.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 45f481406..14b13af42 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,13 @@ Changes in IPython kernel 5.3 --- +5.3.1 +***** + +- Fix #520: run post_execute and post_run_cell on async cells (:ghpull:`521`) +- Fix exception causes in zmqshell.py (:ghpull:`516`) +- Make pdb on Windows interruptible (:ghpull:`490`) + 5.3.0 ***** From 9181966528c79d9f67e565e85d12439ec95a43bf Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 5 Jul 2020 05:55:58 -0500 Subject: [PATCH 0380/1195] Release 5.3.1 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 0a4e63349..d4b8529f9 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 3, 0) +version_info = (5, 3, 1) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 27d210d37299cc77242f66706097ab6b45be1e89 Mon Sep 17 00:00:00 2001 From: josiahkane_refeyn Date: Wed, 8 Jul 2020 10:45:57 +0100 Subject: [PATCH 0381/1195] Putting back the old timer based event loop as a Windows-compatible fallback. --- ipykernel/eventloops.py | 67 ++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 3524a112b..58418b15a 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -216,29 +216,62 @@ def loop_tk(kernel): from tkinter import Tk, READABLE - def process_stream_events(stream, *a, **kw): - """fall back to main loop when there's a socket event""" - if stream.flush(limit=1): - app.tk.deletefilehandler(stream.getsockopt(zmq.FD)) - app.quit() + app = Tk() + # Capability detection: + # per https://docs.python.org/3/library/tkinter.html#file-handlers + # file handlers are not available on Windows + if hasattr(app, 'createfilehandler'): + # A basic wrapper for structural similarity with the Windows version + class BasicAppWrapper(object): + def __init__(self, app): + self.app = app + self.app.withdraw() + + def process_stream_events(stream, *a, **kw): + """fall back to main loop when there's a socket event""" + if stream.flush(limit=1): + app.tk.deletefilehandler(stream.getsockopt(zmq.FD)) + app.quit() - # For Tkinter, we create a Tk object and call its withdraw method. - kernel.app = app = Tk() - kernel.app.withdraw() - for stream in kernel.shell_streams: - notifier = partial(process_stream_events, stream) - # seems to be needed for tk - notifier.__name__ = 'notifier' - app.tk.createfilehandler(stream.getsockopt(zmq.FD), READABLE, notifier) - # schedule initial call after start - app.after(0, notifier) + # For Tkinter, we create a Tk object and call its withdraw method. + kernel.app_wrapper = BasicAppWrapper(app) + + for stream in kernel.shell_streams: + notifier = partial(process_stream_events, stream) + # seems to be needed for tk + notifier.__name__ = "notifier" + app.tk.createfilehandler(stream.getsockopt(zmq.FD), READABLE, notifier) + # schedule initial call after start + app.after(0, notifier) + + app.mainloop() + + else: + doi = kernel.do_one_iteration + # Tk uses milliseconds + poll_interval = int(1000 * kernel._poll_interval) + + class TimedAppWrapper(object): + def __init__(self, app, func): + self.app = app + self.app.withdraw() + self.func = func + + def on_timer(self): + self.func() + self.app.after(poll_interval, self.on_timer) + + def start(self): + self.on_timer() # Call it once to get things going. + self.app.mainloop() - app.mainloop() + kernel.app_wrapper = TimedAppWrapper(app, doi) + kernel.app_wrapper.start() @loop_tk.exit def loop_tk_exit(kernel): - kernel.app.destroy() + kernel.app_wrapper.app.destroy() @register_integration('gtk') From 58fdf30839c5bb12276123fe71cf63f86fc294b4 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 8 Jul 2020 06:13:57 -0500 Subject: [PATCH 0382/1195] update changelog --- docs/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index 14b13af42..f759686ee 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,10 @@ Changes in IPython kernel 5.3 --- +5.3.2 +***** +- Restore timer based event loop as a Windows-compatible fallback. (:ghpull:`523`) + 5.3.1 ***** From d9633f20e16f8c68b0a8cf271ca0622b22b34a57 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 8 Jul 2020 06:16:10 -0500 Subject: [PATCH 0383/1195] Add release notes --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index d4b8529f9..f49bbbd38 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 3, 1) +version_info = (5, 3, 2) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 229fce463779f6972acd4da4cc6c8f1e008458cd Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 8 Jul 2020 06:16:15 -0500 Subject: [PATCH 0384/1195] Add release notes --- RELEASE.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 RELEASE.md diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..8054f8bfd --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,19 @@ +# Release Guide + +- Update `docs/changelog.rst` +- Update `ipykernel/_version.py` +- Run the following: + +```bash +version=`python setup.py --version 2>/dev/null` +git commit -a -m "Release $version" +git tag $version; true; +git push --all +git push --tags +rm -rf dist build +python setup.py sdist +python setup.py bdist_wheel +pip install twine +twine check dist/* +twine upload dist/* +``` From 4a05435220c730605ab454001ac3ecc364299736 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 15 Jul 2020 13:13:05 -0500 Subject: [PATCH 0385/1195] Fix QSocketNotifier in the Qt event loop not being disabled for the control channel This was generating a constant warning in qtconsole and Spyder. --- ipykernel/eventloops.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 58418b15a..2815a723f 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -36,6 +36,12 @@ def process_stream_events(): if stream.flush(limit=1): notifier.setEnabled(False) kernel.app.quit() + else: + # Even if there's nothing to flush, we need to disable the + # notifier in order to connect a new one in the next + # execution. This applies to the control channel. + notifier.setEnabled(False) + kernel.app.quit() fd = stream.getsockopt(zmq.FD) notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) From cc4b3167faebad882c4dc900f86781a376cb6aa6 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 17 Jul 2020 15:13:39 -0500 Subject: [PATCH 0386/1195] Release 5.3.3 --- docs/changelog.rst | 4 ++++ ipykernel/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index f759686ee..705f06dbb 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,10 @@ Changes in IPython kernel 5.3 --- +5.3.3 +***** +- Fix QSocketNotifier in the Qt event loop not being disabled for the control channel. (:ghpull:`525`) + 5.3.2 ***** - Restore timer based event loop as a Windows-compatible fallback. (:ghpull:`523`) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index f49bbbd38..a0cf4d527 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 3, 2) +version_info = (5, 3, 3) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 9351b1ac68251516f6a979624b296d5fd191d073 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 22 Jul 2020 12:23:48 -0500 Subject: [PATCH 0387/1195] Only run Qt eventloop in the shell stream --- ipykernel/eventloops.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 2815a723f..d0014752b 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -36,12 +36,6 @@ def process_stream_events(): if stream.flush(limit=1): notifier.setEnabled(False) kernel.app.quit() - else: - # Even if there's nothing to flush, we need to disable the - # notifier in order to connect a new one in the next - # execution. This applies to the control channel. - notifier.setEnabled(False) - kernel.app.quit() fd = stream.getsockopt(zmq.FD) notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) @@ -122,8 +116,10 @@ def loop_qt4(kernel): kernel.app = get_app_qt4([" "]) kernel.app.setQuitOnLastWindowClosed(False) - for s in kernel.shell_streams: - _notify_stream_qt(kernel, s) + # Only register the eventloop for the shell stream because doing + # it for the control stream is generating a bunch of unnecessary + # warnings on Windows. + _notify_stream_qt(kernel, kernel.shell_streams[0]) _loop_qt(kernel.app) From a06fea972b2af90d20b87a024f4692edcce4b002 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 22 Jul 2020 14:14:06 -0500 Subject: [PATCH 0388/1195] Release 5.3.4 --- docs/changelog.rst | 4 ++++ ipykernel/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 705f06dbb..868320ea0 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,10 @@ Changes in IPython kernel 5.3 --- +5.3.4 +***** +- Only run Qt eventloop in the shell stream. (:ghpull:`531`) + 5.3.3 ***** - Fix QSocketNotifier in the Qt event loop not being disabled for the control channel. (:ghpull:`525`) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index a0cf4d527..30d9e62c7 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 3, 3) +version_info = (5, 3, 4) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 012cebb4dc54ea2c9e14387ad11f9634f2d6c05b Mon Sep 17 00:00:00 2001 From: Glen Takahashi Date: Wed, 12 Aug 2020 10:20:16 -0700 Subject: [PATCH 0389/1195] Use io_loop instead of dispatch queue --- ipykernel/kernelbase.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 8c808566c..63693ca90 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -795,16 +795,10 @@ def _abort_queues(self): stream.flush() self._aborting = True - self.schedule_dispatch( - ABORT_PRIORITY, - self._dispatch_abort, - ) + def stop_aborting(f): + self._aborting = False - @gen.coroutine - def _dispatch_abort(self): - self.log.info("Finishing abort") - yield gen.sleep(self.stop_on_error_timeout) - self._aborting = False + self.io_loop.add_future(gen.sleep(self.stop_on_error_timeout), stop_aborting) def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" From 38dda3b449c50c4a32806144ffb7435dc0f4eef5 Mon Sep 17 00:00:00 2001 From: Glen Takahashi Date: Wed, 12 Aug 2020 10:26:14 -0700 Subject: [PATCH 0390/1195] Remove unused --- ipykernel/kernelbase.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 63693ca90..b9a6a85b6 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -43,7 +43,6 @@ CONTROL_PRIORITY = 1 SHELL_PRIORITY = 10 -ABORT_PRIORITY = 20 class Kernel(SingletonConfigurable): From 23b513f5ccf35d465072f2c03540da9d5596cb87 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 22:24:54 +0100 Subject: [PATCH 0391/1195] CLN : Remove u-prefix from strings the u-prefixes are not needed on python 3 only code --- ipykernel/displayhook.py | 10 +++--- ipykernel/inprocess/ipkernel.py | 6 ++-- ipykernel/iostream.py | 6 ++-- ipykernel/ipkernel.py | 26 ++++++++-------- ipykernel/kernelapp.py | 4 +-- ipykernel/kernelbase.py | 46 ++++++++++++++-------------- ipykernel/tests/test_embed_kernel.py | 6 ++-- ipykernel/tests/test_jsonutil.py | 2 +- ipykernel/tests/test_kernel.py | 18 +++++------ ipykernel/tests/test_message_spec.py | 20 ++++++------ ipykernel/tests/test_start_kernel.py | 10 +++--- ipykernel/zmqshell.py | 10 +++--- 12 files changed, 82 insertions(+), 82 deletions(-) diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index 7e90ef100..22728e1ee 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -33,10 +33,10 @@ def __call__(self, obj): builtin_mod._ = obj sys.stdout.flush() sys.stderr.flush() - contents = {u'execution_count': self.get_execution_count(), - u'data': {'text/plain': repr(obj)}, - u'metadata': {}} - self.session.send(self.pub_socket, u'execute_result', contents, + contents = {'execution_count': self.get_execution_count(), + 'data': {'text/plain': repr(obj)}, + 'metadata': {}} + self.session.send(self.pub_socket, 'execute_result', contents, parent=self.parent_header, ident=self.topic) def set_parent(self, parent): @@ -58,7 +58,7 @@ def set_parent(self, parent): self.parent_header = extract_header(parent) def start_displayhook(self): - self.msg = self.session.msg(u'execute_result', { + self.msg = self.session.msg('execute_result', { 'data': {}, 'metadata': {}, }, parent=self.parent_header) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 8363709e5..3b51c8e5b 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -95,7 +95,7 @@ def _input_request(self, prompt, ident, parent, password=False): # Send the input request. content = json_clean(dict(prompt=prompt, password=password)) - msg = self.session.msg(u'input_request', content, parent) + msg = self.session.msg('input_request', content, parent) for frontend in self.frontends: if frontend.session.session == parent['header']['session']: frontend.stdin_channel.call_handlers(msg) @@ -148,11 +148,11 @@ def _default_shell_class(self): @default('stdout') def _default_stdout(self): - return OutStream(self.session, self.iopub_thread, u'stdout') + return OutStream(self.session, self.iopub_thread, 'stdout') @default('stderr') def _default_stderr(self): - return OutStream(self.session, self.iopub_thread, u'stderr') + return OutStream(self.session, self.iopub_thread, 'stderr') #----------------------------------------------------------------------------- # Interactive shell subclass diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 7ab35f377..5b7289a6c 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -379,8 +379,8 @@ def _flush(self): # since pub_thread is itself fork-safe. # There should be a better way to do this. self.session.pid = os.getpid() - content = {u'name':self.name, u'text':data} - self.session.send(self.pub_thread, u'stream', content=content, + content = {'name':self.name, 'text':data} + self.session.send(self.pub_thread, 'stream', content=content, parent=self.parent_header, ident=self.topic) def write(self, string): @@ -428,7 +428,7 @@ def _flush_buffer(self): This should only be called in the IO thread. """ - data = u'' + data = '' if self._buffer is not None: buf = self._buffer self._new_buffer() diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index b8b95eab8..56d396440 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -313,14 +313,14 @@ def run_cell(*args, **kwargs): err = res.error_in_exec if res.success: - reply_content[u'status'] = u'ok' + reply_content['status'] = 'ok' else: - reply_content[u'status'] = u'error' + reply_content['status'] = 'error' reply_content.update({ - u'traceback': shell._last_traceback or [], - u'ename': unicode_type(type(err).__name__), - u'evalue': safe_unicode(err), + 'traceback': shell._last_traceback or [], + 'ename': unicode_type(type(err).__name__), + 'evalue': safe_unicode(err), }) # FIXME: deprecated piece for ipyparallel (remove in 5.0): @@ -339,16 +339,16 @@ def run_cell(*args, **kwargs): # At this point, we can tell whether the main code execution succeeded # or not. If it did, we proceed to evaluate user_expressions if reply_content['status'] == 'ok': - reply_content[u'user_expressions'] = \ + reply_content['user_expressions'] = \ shell.user_expressions(user_expressions or {}) else: # If there was an error, don't even try to compute expressions - reply_content[u'user_expressions'] = {} + reply_content['user_expressions'] = {} # Payloads should be retrieved regardless of outcome, so we can both # recover partial output (that could have been generated early in a # block, before an error) and always clear the payload system. - reply_content[u'payload'] = shell.payload_manager.read_payload() + reply_content['payload'] = shell.payload_manager.read_payload() # Be aggressive about clearing the payload because we don't want # it to sit in memory until the next execute_request comes in. shell.payload_manager.clear_payload() @@ -504,16 +504,16 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): # invoke IPython traceback formatting shell.showtraceback() reply_content = { - u'traceback': shell._last_traceback or [], - u'ename': unicode_type(type(e).__name__), - u'evalue': safe_unicode(e), + 'traceback': shell._last_traceback or [], + 'ename': unicode_type(type(e).__name__), + 'evalue': safe_unicode(e), } # FIXME: deprecated piece for ipyparallel (remove in 5.0): e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply') reply_content['engine_info'] = e_info - self.send_response(self.iopub_socket, u'error', reply_content, - ident=self._topic('error')) + self.send_response(self.iopub_socket, 'error', reply_content, + ident=self._topic('error')) self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback'])) result_buf = [] reply_content['status'] = 'error' diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index ac643dac1..feddee57c 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -383,12 +383,12 @@ def init_io(self): e_stderr = None if self.quiet else sys.__stderr__ sys.stdout = outstream_factory(self.session, self.iopub_thread, - u'stdout', + 'stdout', echo=e_stdout) if sys.stderr is not None: sys.stderr.flush() sys.stderr = outstream_factory(self.session, self.iopub_thread, - u'stderr', + 'stderr', echo=e_stderr) if self.displayhook_class: displayhook_factory = import_item(str(self.displayhook_class)) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 8c808566c..ac455def4 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -184,10 +184,10 @@ def dispatch_control(self, msg): # Set the parent message for side effects. self.set_parent(idents, msg) - self._publish_status(u'busy') + self._publish_status('busy') if self._aborting: self._send_abort_reply(self.control_stream, msg, idents) - self._publish_status(u'idle') + self._publish_status('idle') return header = msg['header'] @@ -204,7 +204,7 @@ def dispatch_control(self, msg): sys.stdout.flush() sys.stderr.flush() - self._publish_status(u'idle') + self._publish_status('idle') # flush to ensure reply is sent self.control_stream.flush(zmq.POLLOUT) @@ -234,11 +234,11 @@ def dispatch_shell(self, stream, msg): # Set the parent message for side effects. self.set_parent(idents, msg) - self._publish_status(u'busy') + self._publish_status('busy') if self._aborting: self._send_abort_reply(stream, msg, idents) - self._publish_status(u'idle') + self._publish_status('idle') # flush to ensure reply is sent before # handling the next request stream.flush(zmq.POLLOUT) @@ -276,7 +276,7 @@ def dispatch_shell(self, stream, msg): sys.stdout.flush() sys.stderr.flush() - self._publish_status(u'idle') + self._publish_status('idle') # flush to ensure reply is sent before # handling the next request stream.flush(zmq.POLLOUT) @@ -456,16 +456,16 @@ def record_ports(self, ports): def _publish_execute_input(self, code, parent, execution_count): """Publish the code request on the iopub stream.""" - self.session.send(self.iopub_socket, u'execute_input', - {u'code':code, u'execution_count': execution_count}, - parent=parent, ident=self._topic('execute_input') + self.session.send(self.iopub_socket, 'execute_input', + {'code':code, 'execution_count': execution_count}, + parent=parent, ident=self._topic('execute_input') ) def _publish_status(self, status, parent=None): """send status (busy/idle) on IOPub""" self.session.send(self.iopub_socket, - u'status', - {u'execution_state': status}, + 'status', + {'execution_state': status}, parent=parent or self._parent_header, ident=self._topic('status'), ) @@ -518,10 +518,10 @@ def execute_request(self, stream, ident, parent): """handle an execute_request""" try: - content = parent[u'content'] - code = py3compat.cast_unicode_py2(content[u'code']) - silent = content[u'silent'] - store_history = content.get(u'store_history', not silent) + content = parent['content'] + code = py3compat.cast_unicode_py2(content['code']) + silent = content['silent'] + store_history = content.get('store_history', not silent) user_expressions = content.get('user_expressions', {}) allow_stdin = content.get('allow_stdin', False) except: @@ -559,13 +559,13 @@ def execute_request(self, stream, ident, parent): reply_content = json_clean(reply_content) metadata = self.finish_metadata(parent, metadata, reply_content) - reply_msg = self.session.send(stream, u'execute_reply', + reply_msg = self.session.send(stream, 'execute_reply', reply_content, parent, metadata=metadata, ident=ident) self.log.debug("%s", reply_msg) - if not silent and reply_msg['content']['status'] == u'error' and stop_on_error: + if not silent and reply_msg['content']['status'] == 'error' and stop_on_error: yield self._abort_queues() def do_execute(self, code, silent, store_history=True, @@ -681,9 +681,9 @@ def comm_info_request(self, stream, ident, parent): @gen.coroutine def shutdown_request(self, stream, ident, parent): content = yield gen.maybe_future(self.do_shutdown(parent['content']['restart'])) - self.session.send(stream, u'shutdown_reply', content, parent, ident=ident) + self.session.send(stream, 'shutdown_reply', content, parent, ident=ident) # same content, but different msg_id for broadcasting on IOPub - self._shutdown_message = self.session.msg(u'shutdown_reply', + self._shutdown_message = self.session.msg('shutdown_reply', content, parent ) @@ -722,8 +722,8 @@ def do_is_complete(self, code): def apply_request(self, stream, ident, parent): self.log.warning("apply_request is deprecated in kernel_base, moving to ipyparallel.") try: - content = parent[u'content'] - bufs = parent[u'buffers'] + content = parent['content'] + bufs = parent['buffers'] msg_id = parent['header']['msg_id'] except: self.log.error("Got bad msg: %s", parent, exc_info=True) @@ -739,7 +739,7 @@ def apply_request(self, stream, ident, parent): md = self.finish_metadata(parent, md, reply_content) - self.session.send(stream, u'apply_reply', reply_content, + self.session.send(stream, 'apply_reply', reply_content, parent=parent, ident=ident,buffers=result_buf, metadata=md) def do_apply(self, content, bufs, msg_id, reply_metadata): @@ -880,7 +880,7 @@ def _input_request(self, prompt, ident, parent, password=False): # Send the input request. content = json_clean(dict(prompt=prompt, password=password)) - self.session.send(self.stdin_socket, u'input_request', content, parent, + self.session.send(self.stdin_socket, 'input_request', content, parent, ident=ident) # Await a response. diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 8bc776b4c..2174ea0f8 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -103,7 +103,7 @@ def test_embed_kernel_basic(): msg_id = client.execute("c=a*2") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - assert content['status'] == u'ok' + assert content['status'] == 'ok' # oinfo c (should be 10) msg_id = client.inspect('c') @@ -134,7 +134,7 @@ def test_embed_kernel_namespace(): content = msg['content'] assert content['found'] text = content['data']['text/plain'] - assert u'5' in text + assert '5' in text # oinfo b (str) msg_id = client.inspect('b') @@ -142,7 +142,7 @@ def test_embed_kernel_namespace(): content = msg['content'] assert content['found'] text = content['data']['text/plain'] - assert u'hi there' in text + assert 'hi there' in text # oinfo c (undefined) msg_id = client.inspect('c') diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 8db3cf35a..2b2a9cffe 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -105,6 +105,6 @@ def test_exception(): def test_unicode_dict(): - data = {u'üniço∂e': u'üniço∂e'} + data = {'üniço∂e': 'üniço∂e'} clean = jsonutil.json_clean(data) assert data == clean diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 324f08caa..2f465dc7c 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -166,7 +166,7 @@ def test_raw_input(): code = 'print({input_f}("{theprompt}"))'.format(**locals()) msg_id = kc.execute(code, allow_stdin=True) msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT) - assert msg['header']['msg_type'] == u'input_request' + assert msg['header']['msg_type'] == 'input_request' content = msg['content'] assert content['prompt'] == theprompt text = "some text" @@ -188,7 +188,7 @@ def test_eval_input(): code = 'print(input("{theprompt}"))'.format(**locals()) msg_id = kc.execute(code, allow_stdin=True) msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT) - assert msg['header']['msg_type'] == u'input_request' + assert msg['header']['msg_type'] == 'input_request' content = msg['content'] assert content['prompt'] == theprompt kc.input("1+1") @@ -203,23 +203,23 @@ def test_save_history(): # unicode problems on Python 2. with kernel() as kc, TemporaryDirectory() as td: file = os.path.join(td, 'hist.out') - execute(u'a=1', kc=kc) + execute('a=1', kc=kc) wait_for_idle(kc) - execute(u'b=u"abcþ"', kc=kc) + execute('b="abcþ"', kc=kc) wait_for_idle(kc) _, reply = execute("%hist -f " + file, kc=kc) assert reply['status'] == 'ok' with io.open(file, encoding='utf-8') as f: content = f.read() - assert u'a=1' in content - assert u'b=u"abcþ"' in content + assert 'a=1' in content + assert 'b="abcþ"' in content @dec.skip_without('faulthandler') def test_smoke_faulthandler(): with kernel() as kc: # Note: faulthandler.register is not available on windows. - code = u'\n'.join([ + code = '\n'.join([ 'import sys', 'import faulthandler', 'import signal', @@ -262,7 +262,7 @@ def test_is_complete(): @dec.skipif(sys.platform != 'win32', "only run on Windows") def test_complete(): with kernel() as kc: - execute(u'a = 1', kc=kc) + execute('a = 1', kc=kc) wait_for_idle(kc) cell = 'import IPython\nb = a.' kc.complete(cell) @@ -355,7 +355,7 @@ def test_shutdown(): """Kernel exits after polite shutdown_request""" with new_kernel() as kc: km = kc.parent - execute(u'a = 1', kc=kc) + execute('a = 1', kc=kc) wait_for_idle(kc) kc.shutdown() for i in range(300): # 30s timeout diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index cafb99ba1..751c8f159 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -106,7 +106,7 @@ def _data_changed(self, name, old, new): # shell replies class Reply(Reference): - status = Enum((u'ok', u'error'), default_value=u'ok') + status = Enum(('ok', 'error'), default_value='ok') class ExecuteReply(Reply): @@ -143,7 +143,7 @@ class ArgSpec(Reference): class Status(Reference): - execution_state = Enum((u'busy', u'idle', u'starting'), default_value=u'busy') + execution_state = Enum(('busy', 'idle', 'starting'), default_value='busy') class CompleteReply(Reply): @@ -183,7 +183,7 @@ class CommInfoReply(Reply): class IsCompleteReply(Reference): - status = Enum((u'complete', u'incomplete', u'invalid', u'unknown'), default_value=u'complete') + status = Enum(('complete', 'incomplete', 'invalid', 'unknown'), default_value='complete') def check(self, d): Reference.check(self, d) @@ -208,7 +208,7 @@ class Error(ExecuteReplyError): class Stream(Reference): - name = Enum((u'stdout', u'stderr'), default_value=u'stdout') + name = Enum(('stdout', 'stderr'), default_value='stdout') text = Unicode() @@ -357,10 +357,10 @@ def test_user_expressions(): msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1')) user_expressions = reply['user_expressions'] - nt.assert_equal(user_expressions, {u'foo': { - u'status': u'ok', - u'data': {u'text/plain': u'2'}, - u'metadata': {}, + nt.assert_equal(user_expressions, {'foo': { + 'status': 'ok', + 'data': {'text/plain': '2'}, + 'metadata': {}, }}) @@ -535,7 +535,7 @@ def test_stream(): stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT) validate_message(stdout, 'stream', msg_id) content = stdout['content'] - assert content['text'] == u'hi\n' + assert content['text'] == 'hi\n' def test_display_data(): @@ -546,4 +546,4 @@ def test_display_data(): display = KC.iopub_channel.get_msg(timeout=TIMEOUT) validate_message(display, 'display_data', parent=msg_id) data = display['content']['data'] - assert data['text/plain'] == u'1' + assert data['text/plain'] == '1' diff --git a/ipykernel/tests/test_start_kernel.py b/ipykernel/tests/test_start_kernel.py index b69785453..8251b2c40 100644 --- a/ipykernel/tests/test_start_kernel.py +++ b/ipykernel/tests/test_start_kernel.py @@ -16,19 +16,19 @@ def test_ipython_start_kernel_userns(): content = msg['content'] assert content['found'] text = content['data']['text/plain'] - assert u'123' in text + assert '123' in text # user_module should be an instance of DummyMod msg_id = client.execute("usermod = get_ipython().user_module") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - assert content['status'] == u'ok' + assert content['status'] == 'ok' msg_id = client.inspect('usermod') msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] - assert u'DummyMod' in text + assert 'DummyMod' in text @flaky(max_runs=3) @@ -42,10 +42,10 @@ def test_ipython_start_kernel_no_userns(): msg_id = client.execute("usermod = get_ipython().user_module") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] - assert content['status'] == u'ok' + assert content['status'] == 'ok' msg_id = client.inspect('usermod') msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] - assert u'DummyMod' not in text + assert 'DummyMod' not in text diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 2538df86f..1e9d417d0 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -156,7 +156,7 @@ def clear_output(self, wait=False): content = dict(wait=wait) self._flush_streams() self.session.send( - self.pub_socket, u'clear_output', content, + self.pub_socket, 'clear_output', content, parent=self.parent_header, ident=self.topic, ) @@ -541,9 +541,9 @@ def _showtraceback(self, etype, evalue, stb): sys.stderr.flush() exc_content = { - u'traceback' : stb, - u'ename' : unicode_type(etype.__name__), - u'evalue' : py3compat.safe_unicode(evalue), + 'traceback' : stb, + 'ename' : unicode_type(etype.__name__), + 'evalue' : py3compat.safe_unicode(evalue), } dh = self.displayhook @@ -553,7 +553,7 @@ def _showtraceback(self, etype, evalue, stb): if dh.topic: topic = dh.topic.replace(b'execute_result', b'error') - exc_msg = dh.session.send(dh.pub_socket, u'error', json_clean(exc_content), + exc_msg = dh.session.send(dh.pub_socket, 'error', json_clean(exc_content), dh.parent_header, ident=topic) # FIXME - Once we rely on Python 3, the traceback is stored on the From 5a50191ef0fe5fae5c0d4bf19ce09e6f194a2df6 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 21:24:58 +0100 Subject: [PATCH 0392/1195] CLN : Remove __future__ imports --- examples/embedding/inprocess_qtconsole.py | 1 - examples/embedding/inprocess_terminal.py | 1 - ipykernel/connect.py | 2 -- ipykernel/inprocess/tests/test_kernel.py | 2 -- ipykernel/inprocess/tests/test_kernelmanager.py | 2 -- ipykernel/iostream.py | 1 - ipykernel/kernelapp.py | 2 -- ipykernel/kernelbase.py | 2 -- ipykernel/kernelspec.py | 2 -- ipykernel/pylab/backend_inline.py | 2 -- ipykernel/tests/test_kernel.py | 1 - ipykernel/tests/utils.py | 2 -- ipykernel/zmqshell.py | 2 -- setup.py | 2 -- 14 files changed, 24 deletions(-) diff --git a/examples/embedding/inprocess_qtconsole.py b/examples/embedding/inprocess_qtconsole.py index 8ab5d7389..cb6d79e1d 100644 --- a/examples/embedding/inprocess_qtconsole.py +++ b/examples/embedding/inprocess_qtconsole.py @@ -1,4 +1,3 @@ -from __future__ import print_function import os import sys diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index 1d1ddc0bf..cb04a2716 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -1,4 +1,3 @@ -from __future__ import print_function import os import sys diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 0cbcada70..bc1101198 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -3,8 +3,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import absolute_import - import json import sys from subprocess import Popen, PIPE diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 31509e9e3..6f9a9247a 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -1,8 +1,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - import sys import unittest diff --git a/ipykernel/inprocess/tests/test_kernelmanager.py b/ipykernel/inprocess/tests/test_kernelmanager.py index 9de5994b3..e498798a9 100644 --- a/ipykernel/inprocess/tests/test_kernelmanager.py +++ b/ipykernel/inprocess/tests/test_kernelmanager.py @@ -1,8 +1,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - import unittest from ipykernel.inprocess.blocking import BlockingInProcessKernelClient diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 5b7289a6c..c029debbd 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -4,7 +4,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function import atexit from binascii import b2a_hex from collections import deque diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index feddee57c..1d78c20a9 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -3,8 +3,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - import atexit import os import sys diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index ac455def4..753870584 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -3,8 +3,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - from datetime import datetime from functools import partial import itertools diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index bce8051e0..2bc1fa8d7 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -3,8 +3,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - import errno import json import os diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index f93c98e4d..b43509275 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -3,8 +3,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - import matplotlib from matplotlib.backends.backend_agg import ( new_figure_manager, diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 2f465dc7c..4873853aa 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -90,7 +90,6 @@ def test_subprocess_print(): flush_channels(kc) np = 5 code = '\n'.join([ - "from __future__ import print_function", "import time", "import multiprocessing as mp", "pool = [mp.Process(target=print, args=('hello', i,)) for i in range(%i)]" % np, diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 47eab54df..b0c626723 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -3,8 +3,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - import atexit import os import sys diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 1e9d417d0..1f1493858 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -15,8 +15,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - import os import sys import time diff --git a/setup.py b/setup.py index ee4c50f72..6feae4e55 100644 --- a/setup.py +++ b/setup.py @@ -4,8 +4,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from __future__ import print_function - # the name of the package name = 'ipykernel' From a00f797822d684f6263f755be60d425a6cccd4b7 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 21:26:37 +0100 Subject: [PATCH 0393/1195] CLN : Remove coding cookies only because the encoding set was utf-8 and utf-8 is the default encoding on python 3 --- docs/conf.py | 1 - ipykernel/codeutil.py | 2 -- ipykernel/eventloops.py | 1 - ipykernel/iostream.py | 1 - ipykernel/pickleutil.py | 1 - ipykernel/tests/test_jsonutil.py | 1 - ipykernel/tests/test_kernel.py | 1 - ipykernel/tests/test_zmq_shell.py | 1 - ipykernel/zmqshell.py | 1 - setup.py | 1 - 10 files changed, 11 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index d7a215ba3..74dcf4d48 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- # # IPython Kernel documentation build configuration file, created by # sphinx-quickstart on Mon Oct 5 11:32:44 2015. diff --git a/ipykernel/codeutil.py b/ipykernel/codeutil.py index bac32bed0..61fb53521 100644 --- a/ipykernel/codeutil.py +++ b/ipykernel/codeutil.py @@ -1,5 +1,3 @@ -# encoding: utf-8 - """Utilities to enable code objects to be pickled. Any process that import this module will be able to pickle code objects. This diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index d0014752b..087fad7fc 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -1,4 +1,3 @@ -# encoding: utf-8 """Event loop integration for the ZeroMQ-based kernels.""" # Copyright (c) IPython Development Team. diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index c029debbd..39b665349 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -1,4 +1,3 @@ -# coding: utf-8 """Wrappers for forwarding stdout/stderr over zmq""" # Copyright (c) IPython Development Team. diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 205a00687..073178d1a 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -1,4 +1,3 @@ -# encoding: utf-8 """Pickle related utilities. Perhaps this should be called 'can'.""" # Copyright (c) IPython Development Team. diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 2b2a9cffe..2220688a2 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -1,4 +1,3 @@ -# coding: utf-8 """Test suite for our JSON utilities.""" # Copyright (c) IPython Development Team. diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 4873853aa..774344858 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -1,4 +1,3 @@ -# coding: utf-8 """test the IPython Kernel""" # Copyright (c) IPython Development Team. diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index 9fb5aaa69..fd0e35c89 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """ Tests for zmq shell / display publisher. """ # Copyright (c) IPython Development Team. diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 1f1493858..0be169450 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- """A ZMQ-based subclass of InteractiveShell. This code is meant to ease the refactoring of the base InteractiveShell into diff --git a/setup.py b/setup.py index 6feae4e55..4b3190d77 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python -# coding: utf-8 # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. From b682d100d605479e01f256cc235c943100ce41f0 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 21:38:06 +0100 Subject: [PATCH 0394/1195] CLN : Remove try/except around import stmts e.g. unittest.mock vs mock and queue vs Queue. --- ipykernel/codeutil.py | 5 +---- ipykernel/inprocess/blocking.py | 5 +---- ipykernel/inprocess/socket.py | 5 +---- ipykernel/iostream.py | 5 +---- ipykernel/parentpoller.py | 5 +---- ipykernel/pickleutil.py | 11 ++--------- ipykernel/tests/__init__.py | 6 +----- ipykernel/tests/test_kernelspec.py | 6 +----- ipykernel/tests/test_message_spec.py | 5 +---- ipykernel/tests/test_zmq_shell.py | 6 +----- ipykernel/tests/utils.py | 5 +---- 11 files changed, 12 insertions(+), 52 deletions(-) diff --git a/ipykernel/codeutil.py b/ipykernel/codeutil.py index 61fb53521..84a7ed7ec 100644 --- a/ipykernel/codeutil.py +++ b/ipykernel/codeutil.py @@ -14,12 +14,9 @@ import warnings warnings.warn("ipykernel.codeutil is deprecated since IPykernel 4.3.1. It has moved to ipyparallel.serialize", DeprecationWarning) +import copyreg import sys import types -try: - import copyreg # Py 3 -except ImportError: - import copy_reg as copyreg # Py 2 def code_ctor(*args): return types.CodeType(*args) diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index 983694034..ead193699 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -8,11 +8,8 @@ # Distributed under the terms of the BSD License. The full license is in # the file COPYING.txt, distributed as part of this software. #----------------------------------------------------------------------------- +from queue import Queue, Empty import sys -try: - from queue import Queue, Empty # Py 3 -except ImportError: - from Queue import Queue, Empty # Py 2 # IPython imports from traitlets import Type diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index 4026c6f77..606d20725 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -4,11 +4,8 @@ # Distributed under the terms of the Modified BSD License. import abc +from queue import Queue import warnings -try: - from queue import Queue # Py 3 -except ImportError: - from Queue import Queue # Py 2 import zmq diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 39b665349..16b9757d7 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -6,10 +6,7 @@ import atexit from binascii import b2a_hex from collections import deque -try: - from importlib import lock_held as import_lock_held -except ImportError: - from imp import lock_held as import_lock_held +from importlib import lock_held as import_lock_held import os import sys import threading diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 446f656df..38bf72e72 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -9,10 +9,7 @@ import platform import signal import time -try: - from _thread import interrupt_main # Py 3 -except ImportError: - from thread import interrupt_main # Py 2 +from _thread import interrupt_main # Py 3 from threading import Thread from traitlets.log import get_logger diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 073178d1a..bcb17f522 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -8,13 +8,9 @@ import copy import sys +import pickle from types import FunctionType -try: - import cPickle as pickle -except ImportError: - import pickle - from ipython_genutils import py3compat from ipython_genutils.importstring import import_item from ipython_genutils.py3compat import string_types, iteritems, buffer_to_bytes, buffer_to_bytes_py2 @@ -36,10 +32,7 @@ from types import ClassType class_type = (type, ClassType) -try: - PICKLE_PROTOCOL = pickle.DEFAULT_PROTOCOL -except AttributeError: - PICKLE_PROTOCOL = pickle.HIGHEST_PROTOCOL +PICKLE_PROTOCOL = pickle.DEFAULT_PROTOCOL def _get_cell_type(a=None): """the type of a closure cell doesn't seem to be importable, diff --git a/ipykernel/tests/__init__.py b/ipykernel/tests/__init__.py index 1a392137c..c646095d1 100644 --- a/ipykernel/tests/__init__.py +++ b/ipykernel/tests/__init__.py @@ -5,11 +5,7 @@ import shutil import sys import tempfile - -try: - from unittest.mock import patch -except ImportError: - from mock import patch +from unittest.mock import patch from jupyter_core import paths as jpaths from IPython import paths as ipaths diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index a3b9771c7..ed6b90828 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -7,11 +7,7 @@ import shutil import sys import tempfile - -try: - from unittest import mock -except ImportError: - import mock # py2 +from unittest import mock from jupyter_core.paths import jupyter_data_dir diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 751c8f159..ac9854964 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -6,10 +6,7 @@ import re import sys from distutils.version import LooseVersion as V -try: - from queue import Empty # Py 3 -except ImportError: - from Queue import Empty # Py 2 +from queue import Empty import nose.tools as nt from nose.plugins.skip import SkipTest diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index fd0e35c89..4cf2ecc67 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -4,11 +4,7 @@ # Distributed under the terms of the Modified BSD License. import os -try: - from queue import Queue -except ImportError: - # py2 - from Queue import Queue +from queue import Queue from threading import Thread import unittest diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index b0c626723..f5d432070 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -8,11 +8,8 @@ import sys from contextlib import contextmanager +from queue import Empty from subprocess import PIPE, STDOUT -try: - from queue import Empty # Py 3 -except ImportError: - from Queue import Empty # Py 2 import nose From 8a3e8bbd07fb86c6bf29be8c173893c430f967d0 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 21:42:12 +0100 Subject: [PATCH 0395/1195] CLN : Use str instead of py3compat.unicode_type --- ipykernel/iostream.py | 3 +-- ipykernel/ipkernel.py | 6 +++--- ipykernel/jsonutil.py | 8 ++++---- ipykernel/kernelbase.py | 4 ++-- ipykernel/tests/test_embed_kernel.py | 3 +-- ipykernel/zmqshell.py | 3 +-- 6 files changed, 12 insertions(+), 15 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 16b9757d7..427fe0572 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -20,7 +20,6 @@ from jupyter_client.session import extract_header from ipython_genutils import py3compat -from ipython_genutils.py3compat import unicode_type #----------------------------------------------------------------------------- # Globals @@ -391,7 +390,7 @@ def write(self, string): raise ValueError('I/O operation on closed file') else: # Make sure that we're handling unicode - if not isinstance(string, unicode_type): + if not isinstance(string, str): string = string.decode(self.encoding, 'replace') is_child = (not self._is_master_process()) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 56d396440..d18d57195 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -8,7 +8,7 @@ import sys from IPython.core import release -from ipython_genutils.py3compat import builtin_mod, PY3, unicode_type, safe_unicode +from ipython_genutils.py3compat import builtin_mod, PY3, safe_unicode from IPython.utils.tokenutil import token_at_cursor, line_at_cursor from tornado import gen from traitlets import Instance, Type, Any, List, Bool @@ -319,7 +319,7 @@ def run_cell(*args, **kwargs): reply_content.update({ 'traceback': shell._last_traceback or [], - 'ename': unicode_type(type(err).__name__), + 'ename': str(type(err).__name__), 'evalue': safe_unicode(err), }) @@ -505,7 +505,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): shell.showtraceback() reply_content = { 'traceback': shell._last_traceback or [], - 'ename': unicode_type(type(e).__name__), + 'ename': str(type(e).__name__), 'evalue': safe_unicode(e), } # FIXME: deprecated piece for ipyparallel (remove in 5.0): diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index df669f7aa..427662a41 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -12,7 +12,7 @@ from ipython_genutils import py3compat -from ipython_genutils.py3compat import unicode_type, iteritems +from ipython_genutils.py3compat import iteritems from ipython_genutils.encoding import DEFAULT_ENCODING next_attr_name = '__next__' if py3compat.PY3 else 'next' @@ -130,7 +130,7 @@ def json_clean(obj): """ # types that are 'atomic' and ok in json as-is. - atomic_ok = (unicode_type, type(None)) + atomic_ok = (str, type(None)) # containers that we need to convert into lists container_to_list = (tuple, set, types.GeneratorType) @@ -181,14 +181,14 @@ def json_clean(obj): # key collisions after stringification. This can happen with keys like # True and 'true' or 1 and '1', which collide in JSON. nkeys = len(obj) - nkeys_collapsed = len(set(map(unicode_type, obj))) + nkeys_collapsed = len(set(map(str, obj))) if nkeys != nkeys_collapsed: raise ValueError('dict cannot be safely converted to JSON: ' 'key collision would lead to dropped values') # If all OK, proceed by making the new dict that will be json-safe out = {} for k,v in iteritems(obj): - out[unicode_type(k)] = json_clean(v) + out[str(k)] = json_clean(v) return out if isinstance(obj, datetime): return obj.strftime(ISO8601) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 753870584..e69a0ff02 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -28,7 +28,7 @@ from traitlets.config.configurable import SingletonConfigurable from IPython.core.error import StdinNotImplementedError from ipython_genutils import py3compat -from ipython_genutils.py3compat import unicode_type, string_types +from ipython_genutils.py3compat import string_types from ipykernel.jsonutil import json_clean from traitlets import ( Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool, @@ -75,7 +75,7 @@ def _update_eventloop(self, change): @default('ident') def _default_ident(self): - return unicode_type(uuid.uuid4()) + return str(uuid.uuid4()) # This should be overridden by wrapper kernels that implement any real # language. diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 2174ea0f8..b339621f7 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -15,7 +15,6 @@ from jupyter_client import BlockingKernelClient from jupyter_core import paths from ipython_genutils import py3compat -from ipython_genutils.py3compat import unicode_type SETUP_TIMEOUT = 60 @@ -173,7 +172,7 @@ def test_embed_kernel_reentrant(): content = msg['content'] assert content['found'] text = content['data']['text/plain'] - assert unicode_type(i) in text + assert str(i) in text # exit from embed_kernel client.execute("get_ipython().exit_now = True") diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 0be169450..b4056e54e 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -41,7 +41,6 @@ from ipykernel.jsonutil import json_clean, encode_images from IPython.utils.process import arg_split, system from ipython_genutils import py3compat -from ipython_genutils.py3compat import unicode_type from traitlets import ( Instance, Type, Dict, CBool, CBytes, Any, default, observe ) @@ -539,7 +538,7 @@ def _showtraceback(self, etype, evalue, stb): exc_content = { 'traceback' : stb, - 'ename' : unicode_type(etype.__name__), + 'ename' : str(etype.__name__), 'evalue' : py3compat.safe_unicode(evalue), } From b934ee196e35856974f68034b279705e9179a1a0 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 21:44:14 +0100 Subject: [PATCH 0396/1195] CLN : Use str instead of py3compat.string_types --- ipykernel/comm/manager.py | 3 +-- ipykernel/kernelbase.py | 3 +-- ipykernel/pickleutil.py | 10 +++++----- ipykernel/tests/test_message_spec.py | 4 ++-- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index d7b290de1..f1ef1f93c 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -9,7 +9,6 @@ from traitlets.config import LoggingConfigurable from ipython_genutils.importstring import import_item -from ipython_genutils.py3compat import string_types from traitlets import Instance, Unicode, Dict, Any, default from .comm import Comm @@ -34,7 +33,7 @@ def register_target(self, target_name, f): f can be a Python callable or an import string for one. """ - if isinstance(f, string_types): + if isinstance(f, str): f = import_item(f) self.targets[target_name] = f diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e69a0ff02..29de650cb 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -28,7 +28,6 @@ from traitlets.config.configurable import SingletonConfigurable from IPython.core.error import StdinNotImplementedError from ipython_genutils import py3compat -from ipython_genutils.py3compat import string_types from ipykernel.jsonutil import json_clean from traitlets import ( Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool, @@ -752,7 +751,7 @@ def abort_request(self, stream, ident, parent): """abort a specific msg by id""" self.log.warning("abort_request is deprecated in kernel_base. It is only part of IPython parallel") msg_ids = parent['content'].get('msg_ids', None) - if isinstance(msg_ids, string_types): + if isinstance(msg_ids, str): msg_ids = [msg_ids] if not msg_ids: self._abort_queues() diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index bcb17f522..9a2a9dbeb 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -13,7 +13,7 @@ from ipython_genutils import py3compat from ipython_genutils.importstring import import_item -from ipython_genutils.py3compat import string_types, iteritems, buffer_to_bytes, buffer_to_bytes_py2 +from ipython_genutils.py3compat import iteritems, buffer_to_bytes, buffer_to_bytes_py2 # This registers a hook when it's imported try: @@ -159,7 +159,7 @@ def get_object(self, g=None): class Reference(CannedObject): """object for wrapping a remote reference by name.""" def __init__(self, name): - if not isinstance(name, string_types): + if not isinstance(name, str): raise TypeError("illegal name: %r"%name) self.name = name self.buffers = [] @@ -315,7 +315,7 @@ def _import_mapping(mapping, original=None): log = get_logger() log.debug("Importing canning map") for key,value in list(mapping.items()): - if isinstance(key, string_types): + if isinstance(key, str): try: cls = import_item(key) except Exception: @@ -345,7 +345,7 @@ def can(obj): import_needed = False for cls,canner in iteritems(can_map): - if isinstance(cls, string_types): + if isinstance(cls, str): import_needed = True break elif istype(obj, cls): @@ -390,7 +390,7 @@ def uncan(obj, g=None): import_needed = False for cls,uncanner in iteritems(uncan_map): - if isinstance(cls, string_types): + if isinstance(cls, str): import_needed = True break elif isinstance(obj, cls): diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index ac9854964..38f356bc0 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -14,7 +14,7 @@ from traitlets import ( HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum ) -from ipython_genutils.py3compat import string_types, iteritems +from ipython_genutils.py3compat import iteritems from .utils import TIMEOUT, start_global_kernel, flush_channels, execute @@ -98,7 +98,7 @@ class MimeBundle(Reference): def _data_changed(self, name, old, new): for k,v in iteritems(new): assert mime_pat.match(k) - assert isinstance(v, string_types) + assert isinstance(v, str) # shell replies From b76b17e8b05948d636510c7e22f19fa4515e88c5 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 21:46:10 +0100 Subject: [PATCH 0397/1195] CLN : Use dict.items instead of py3compat.iteritems --- ipykernel/jsonutil.py | 3 +-- ipykernel/pickleutil.py | 10 +++++----- ipykernel/tests/test_message_spec.py | 3 +-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 427662a41..0cbb2f498 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -12,7 +12,6 @@ from ipython_genutils import py3compat -from ipython_genutils.py3compat import iteritems from ipython_genutils.encoding import DEFAULT_ENCODING next_attr_name = '__next__' if py3compat.PY3 else 'next' @@ -187,7 +186,7 @@ def json_clean(obj): 'key collision would lead to dropped values') # If all OK, proceed by making the new dict that will be json-safe out = {} - for k,v in iteritems(obj): + for k,v in obj.items(): out[str(k)] = json_clean(v) return out if isinstance(obj, datetime): diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 9a2a9dbeb..1ab8c9415 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -13,7 +13,7 @@ from ipython_genutils import py3compat from ipython_genutils.importstring import import_item -from ipython_genutils.py3compat import iteritems, buffer_to_bytes, buffer_to_bytes_py2 +from ipython_genutils.py3compat import buffer_to_bytes, buffer_to_bytes_py2 # This registers a hook when it's imported try: @@ -344,7 +344,7 @@ def can(obj): import_needed = False - for cls,canner in iteritems(can_map): + for cls,canner in can_map.items(): if isinstance(cls, str): import_needed = True break @@ -369,7 +369,7 @@ def can_dict(obj): """can the *values* of a dict""" if istype(obj, dict): newobj = {} - for k, v in iteritems(obj): + for k, v in obj.items(): newobj[k] = can(v) return newobj else: @@ -389,7 +389,7 @@ def uncan(obj, g=None): """invert canning""" import_needed = False - for cls,uncanner in iteritems(uncan_map): + for cls,uncanner in uncan_map.items(): if isinstance(cls, str): import_needed = True break @@ -407,7 +407,7 @@ def uncan(obj, g=None): def uncan_dict(obj, g=None): if istype(obj, dict): newobj = {} - for k, v in iteritems(obj): + for k, v in obj.items(): newobj[k] = uncan(v,g) return newobj else: diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 38f356bc0..51a699fe8 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -14,7 +14,6 @@ from traitlets import ( HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum ) -from ipython_genutils.py3compat import iteritems from .utils import TIMEOUT, start_global_kernel, flush_channels, execute @@ -96,7 +95,7 @@ class MimeBundle(Reference): metadata = Dict() data = Dict() def _data_changed(self, name, old, new): - for k,v in iteritems(new): + for k,v in new.items(): assert mime_pat.match(k) assert isinstance(v, str) From 266d82a259163f6bfad885b460d2921434fa2fed Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 21:47:32 +0100 Subject: [PATCH 0398/1195] CLN : Remove a python 2 only test and update docstring in a similar one nearby --- ipykernel/tests/test_kernel.py | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 774344858..feeb95f29 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -16,7 +16,6 @@ from IPython.testing import decorators as dec, tools as tt import IPython -from ipython_genutils import py3compat from IPython.paths import locate_profile from ipython_genutils.tempdir import TemporaryDirectory @@ -155,11 +154,11 @@ def test_subprocess_error(): # raw_input tests def test_raw_input(): - """test [raw_]input""" + """test input""" with kernel() as kc: iopub = kc.iopub_channel - input_f = "input" if py3compat.PY3 else "raw_input" + input_f = "input" theprompt = "prompt> " code = 'print({input_f}("{theprompt}"))'.format(**locals()) msg_id = kc.execute(code, allow_stdin=True) @@ -175,27 +174,6 @@ def test_raw_input(): assert stdout == text + "\n" -@dec.skipif(py3compat.PY3) -def test_eval_input(): - """test input() on Python 2""" - with kernel() as kc: - iopub = kc.iopub_channel - - input_f = "input" if py3compat.PY3 else "raw_input" - theprompt = "prompt> " - code = 'print(input("{theprompt}"))'.format(**locals()) - msg_id = kc.execute(code, allow_stdin=True) - msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT) - assert msg['header']['msg_type'] == 'input_request' - content = msg['content'] - assert content['prompt'] == theprompt - kc.input("1+1") - reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) - assert reply['content']['status'] == 'ok' - stdout, stderr = assemble_output(iopub) - assert stdout == "2\n" - - def test_save_history(): # Saving history from the kernel with %hist -f was failing because of # unicode problems on Python 2. From 5fb9e68987187ffdd2b320a92606b5d3e4088669 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 21:54:54 +0100 Subject: [PATCH 0399/1195] CLN : Remove py3compat.PY3 conditionals --- ipykernel/connect.py | 9 ++-- ipykernel/inprocess/tests/test_kernel.py | 12 +----- ipykernel/ipkernel.py | 21 +++------ ipykernel/jsonutil.py | 55 +++--------------------- ipykernel/pickleutil.py | 12 +----- 5 files changed, 19 insertions(+), 90 deletions(-) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index bc1101198..55939aae2 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -11,7 +11,7 @@ from IPython.core.profiledir import ProfileDir from IPython.paths import get_ipython_dir from ipython_genutils.path import filefind -from ipython_genutils.py3compat import str_to_bytes, PY3 +from ipython_genutils.py3compat import str_to_bytes import jupyter_client from jupyter_client import write_connection_file @@ -168,10 +168,9 @@ def connect_qtconsole(connection_file=None, argv=None, profile=None): ]) kwargs = {} - if PY3: - # Launch the Qt console in a separate session & process group, so - # interrupting the kernel doesn't kill it. This kwarg is not on Py2. - kwargs['start_new_session'] = True + # Launch the Qt console in a separate session & process group, so + # interrupting the kernel doesn't kill it. + kwargs['start_new_session'] = True return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'), diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 6f9a9247a..4921d3f5e 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -1,6 +1,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from io import StringIO import sys import unittest @@ -10,12 +11,6 @@ from ipykernel.tests.utils import assemble_output from IPython.testing.decorators import skipif_not_matplotlib from IPython.utils.io import capture_output -from ipython_genutils import py3compat - -if py3compat.PY3: - from io import StringIO -else: - from StringIO import StringIO def _init_asyncio_patch(): @@ -76,10 +71,7 @@ def test_raw_input(self): sys_stdin = sys.stdin sys.stdin = io try: - if py3compat.PY3: - self.kc.execute('x = input()') - else: - self.kc.execute('x = raw_input()') + self.kc.execute('x = input()') finally: sys.stdin = sys_stdin assert self.km.kernel.shell.user_ns.get('x') == 'foobar' diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index d18d57195..63b223841 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -8,7 +8,7 @@ import sys from IPython.core import release -from ipython_genutils.py3compat import builtin_mod, PY3, safe_unicode +from ipython_genutils.py3compat import builtin_mod, safe_unicode from IPython.utils.tokenutil import token_at_cursor, line_at_cursor from tornado import gen from traitlets import Instance, Type, Any, List, Bool @@ -127,7 +127,7 @@ def __init__(self, **kwargs): 'name': 'ipython', 'version': sys.version_info[0] }, - 'pygments_lexer': 'ipython%d' % (3 if PY3 else 2), + 'pygments_lexer': 'ipython%d' % 3, 'nbconvert_exporter': 'python', 'file_extension': '.py' } @@ -181,24 +181,15 @@ def _forward_input(self, allow_stdin=False): """ self._allow_stdin = allow_stdin - if PY3: - self._sys_raw_input = builtin_mod.input - builtin_mod.input = self.raw_input - else: - self._sys_raw_input = builtin_mod.raw_input - self._sys_eval_input = builtin_mod.input - builtin_mod.raw_input = self.raw_input - builtin_mod.input = lambda prompt='': eval(self.raw_input(prompt)) + self._sys_raw_input = builtin_mod.input + builtin_mod.input = self.raw_input + self._save_getpass = getpass.getpass getpass.getpass = self.getpass def _restore_input(self): """Restore raw_input, getpass""" - if PY3: - builtin_mod.input = self._sys_raw_input - else: - builtin_mod.raw_input = self._sys_raw_input - builtin_mod.input = self._sys_eval_input + builtin_mod.input = self._sys_raw_input getpass.getpass = self._save_getpass diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 0cbb2f498..3029ad462 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -10,10 +10,8 @@ from datetime import datetime import numbers - -from ipython_genutils import py3compat from ipython_genutils.encoding import DEFAULT_ENCODING -next_attr_name = '__next__' if py3compat.PY3 else 'next' +next_attr_name = '__next__' #----------------------------------------------------------------------------- # Globals and constants @@ -69,40 +67,7 @@ def encode_images(format_dict): # no need for handling of ambiguous bytestrings on Python 3, # where bytes objects always represent binary data and thus # base64-encoded. - if py3compat.PY3: - return format_dict - - encoded = format_dict.copy() - - pngdata = format_dict.get('image/png') - if isinstance(pngdata, bytes): - # make sure we don't double-encode - if not pngdata.startswith(PNG64): - pngdata = b2a_base64(pngdata) - encoded['image/png'] = pngdata.decode('ascii') - - jpegdata = format_dict.get('image/jpeg') - if isinstance(jpegdata, bytes): - # make sure we don't double-encode - if not jpegdata.startswith(JPEG64): - jpegdata = b2a_base64(jpegdata) - encoded['image/jpeg'] = jpegdata.decode('ascii') - - gifdata = format_dict.get('image/gif') - if isinstance(gifdata, bytes): - # make sure we don't double-encode - if not gifdata.startswith((GIF_64, GIF89_64)): - gifdata = b2a_base64(gifdata) - encoded['image/gif'] = gifdata.decode('ascii') - - pdfdata = format_dict.get('application/pdf') - if isinstance(pdfdata, bytes): - # make sure we don't double-encode - if not pdfdata.startswith(PDF64): - pdfdata = b2a_base64(pdfdata) - encoded['application/pdf'] = pdfdata.decode('ascii') - - return encoded + return format_dict def json_clean(obj): @@ -154,19 +119,9 @@ def json_clean(obj): return obj if isinstance(obj, bytes): - if py3compat.PY3: - # unanmbiguous binary data is base64-encoded - # (this probably should have happened upstream) - return b2a_base64(obj).decode('ascii') - else: - # Python 2 bytestr is ambiguous, - # needs special handling for possible binary bytestrings. - # imperfect workaround: if ascii, assume text. - # otherwise assume binary, base64-encode (py3 behavior). - try: - return obj.decode('ascii') - except UnicodeDecodeError: - return b2a_base64(obj).decode('ascii') + # unanmbiguous binary data is base64-encoded + # (this probably should have happened upstream) + return b2a_base64(obj).decode('ascii') if isinstance(obj, container_to_list) or ( hasattr(obj, '__iter__') and hasattr(obj, next_attr_name)): diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 1ab8c9415..d2650390e 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -25,12 +25,8 @@ from traitlets.log import get_logger -if py3compat.PY3: - buffer = memoryview - class_type = type -else: - from types import ClassType - class_type = (type, ClassType) +buffer = memoryview +class_type = type PICKLE_PROTOCOL = pickle.DEFAULT_PROTOCOL @@ -281,10 +277,6 @@ def get_object(self, g=None): # we just pickled it return pickle.loads(buffer_to_bytes_py2(data)) else: - if not py3compat.PY3 and isinstance(data, memoryview): - # frombuffer doesn't accept memoryviews on Python 2, - # so cast to old-style buffer - data = buffer(data.tobytes()) return frombuffer(data, dtype=self.dtype).reshape(self.shape) From b88c71bd6e8428f4fd24473c85ded53eb341aac9 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 21:56:33 +0100 Subject: [PATCH 0400/1195] CLN : Use metaclass kwarg --- ipykernel/inprocess/socket.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index 606d20725..d1c398b01 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -10,13 +10,12 @@ import zmq from traitlets import HasTraits, Instance, Int -from ipython_genutils.py3compat import with_metaclass #----------------------------------------------------------------------------- # Generic socket interface #----------------------------------------------------------------------------- -class SocketABC(with_metaclass(abc.ABCMeta, object)): +class SocketABC(object, metaclass=abc.ABCMeta): @abc.abstractmethod def recv_multipart(self, flags=0, copy=True, track=False): From 3486dfbb744d5db435c994325d48402b73332706 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 22:00:22 +0100 Subject: [PATCH 0401/1195] CLN : Use builtins instead of py3compat.builtin_mod --- ipykernel/ipkernel.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 63b223841..3e1002604 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -8,7 +8,7 @@ import sys from IPython.core import release -from ipython_genutils.py3compat import builtin_mod, safe_unicode +from ipython_genutils.py3compat import safe_unicode from IPython.utils.tokenutil import token_at_cursor, line_at_cursor from tornado import gen from traitlets import Instance, Type, Any, List, Bool @@ -181,15 +181,15 @@ def _forward_input(self, allow_stdin=False): """ self._allow_stdin = allow_stdin - self._sys_raw_input = builtin_mod.input - builtin_mod.input = self.raw_input + self._sys_raw_input = builtins.input + builtins.input = self.raw_input self._save_getpass = getpass.getpass getpass.getpass = self.getpass def _restore_input(self): """Restore raw_input, getpass""" - builtin_mod.input = self._sys_raw_input + builtins.input = self._sys_raw_input getpass.getpass = self._save_getpass From 5fed6591a1531d82f7b93593b463d2fd3a6d63c0 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 22:02:55 +0100 Subject: [PATCH 0402/1195] CLN : Use __closure__ instead of py3compat.get_closure --- ipykernel/pickleutil.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index d2650390e..a8f62b4f0 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -11,7 +11,6 @@ import pickle from types import FunctionType -from ipython_genutils import py3compat from ipython_genutils.importstring import import_item from ipython_genutils.py3compat import buffer_to_bytes, buffer_to_bytes_py2 @@ -36,7 +35,7 @@ def _get_cell_type(a=None): """ def inner(): return a - return type(py3compat.get_closure(inner)[0]) + return type(inner.__closure__[0]) cell_type = _get_cell_type() @@ -179,7 +178,7 @@ def get_object(self, g=None): cell_contents = uncan(self.cell_contents, g) def inner(): return cell_contents - return py3compat.get_closure(inner)[0] + return inner.__closure__[0] class CannedFunction(CannedObject): @@ -192,7 +191,7 @@ def __init__(self, f): else: self.defaults = None - closure = py3compat.get_closure(f) + closure = f.__closure__ if closure: self.closure = tuple( can(cell) for cell in closure ) else: From a869dab90ee03e1b94a82060841db5c85423c9b9 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 22:03:51 +0100 Subject: [PATCH 0403/1195] CLN : Add a missed builtins replacement --- ipykernel/displayhook.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index 22728e1ee..73b5a61e4 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -7,7 +7,6 @@ from IPython.core.displayhook import DisplayHook from ipykernel.jsonutil import encode_images, json_clean -from ipython_genutils.py3compat import builtin_mod from traitlets import Instance, Dict, Any from jupyter_client.session import extract_header, Session @@ -30,7 +29,7 @@ def __call__(self, obj): if obj is None: return - builtin_mod._ = obj + builtins._ = obj sys.stdout.flush() sys.stderr.flush() contents = {'execution_count': self.get_execution_count(), From a021669d3993176dd010834f94d605d681264035 Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 22:07:10 +0100 Subject: [PATCH 0404/1195] CLN : Remove *_py2 functions which are no-op on py3 --- ipykernel/kernelbase.py | 2 +- ipykernel/pickleutil.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 29de650cb..5a988d37e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -516,7 +516,7 @@ def execute_request(self, stream, ident, parent): try: content = parent['content'] - code = py3compat.cast_unicode_py2(content['code']) + code = content['code'] silent = content['silent'] store_history = content.get('store_history', not silent) user_expressions = content.get('user_expressions', {}) diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index a8f62b4f0..669b6ac91 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -12,7 +12,7 @@ from types import FunctionType from ipython_genutils.importstring import import_item -from ipython_genutils.py3compat import buffer_to_bytes, buffer_to_bytes_py2 +from ipython_genutils.py3compat import buffer_to_bytes # This registers a hook when it's imported try: @@ -274,7 +274,7 @@ def get_object(self, g=None): data = self.buffers[0] if self.pickled: # we just pickled it - return pickle.loads(buffer_to_bytes_py2(data)) + return pickle.loads(data) else: return frombuffer(data, dtype=self.dtype).reshape(self.shape) From 4317d8bd6df8ae84989699f936ac30c8997c92da Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 22:29:45 +0100 Subject: [PATCH 0405/1195] FIX : imp.lock_held, not importlib.lock_held modified: ipykernel/iostream.py --- ipykernel/iostream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 427fe0572..64df8c7f6 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -6,7 +6,7 @@ import atexit from binascii import b2a_hex from collections import deque -from importlib import lock_held as import_lock_held +from imp import lock_held as import_lock_held import os import sys import threading From 072b9da9d36a0dfafab5e0c1cf7b7221490cc59a Mon Sep 17 00:00:00 2001 From: Sai Rahul Poruri Date: Mon, 17 Aug 2020 22:49:05 +0100 Subject: [PATCH 0406/1195] FIX : Actually import builtins to use it modified: ipykernel/displayhook.py modified: ipykernel/ipkernel.py --- ipykernel/displayhook.py | 1 + ipykernel/ipkernel.py | 1 + 2 files changed, 2 insertions(+) diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index 73b5a61e4..fe26c7a20 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -3,6 +3,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import builtins import sys from IPython.core.displayhook import DisplayHook diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 3e1002604..c799518f4 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -1,6 +1,7 @@ """The IPython kernel implementation""" import asyncio +import builtins from contextlib import contextmanager from functools import partial import getpass From dbdf8647ab42531d2a6e6186eacfc94c477f555e Mon Sep 17 00:00:00 2001 From: Glen Takahashi Date: Tue, 18 Aug 2020 16:53:49 -0700 Subject: [PATCH 0407/1195] Bring back log --- ipykernel/kernelbase.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index b9a6a85b6..0c3fccaf2 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -795,6 +795,7 @@ def _abort_queues(self): self._aborting = True def stop_aborting(f): + self.log.info("Finishing abort") self._aborting = False self.io_loop.add_future(gen.sleep(self.stop_on_error_timeout), stop_aborting) From 968fff0691d902315a996376a17ef9265e2f4279 Mon Sep 17 00:00:00 2001 From: Emmanuel Roux Date: Sun, 23 Aug 2020 11:58:13 +0200 Subject: [PATCH 0408/1195] Add env parameter to kernel installation Basic support of env parameter for setting kernel environment variable --- ipykernel/kernelspec.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 2bc1fa8d7..36d3bbcfc 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -82,7 +82,7 @@ def write_kernel_spec(path=None, overrides=None, extra_arguments=None): def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, display_name=None, - prefix=None, profile=None): + prefix=None, profile=None, env=None): """Install the IPython kernelspec for Jupyter Parameters @@ -103,6 +103,10 @@ def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, displ prefix: str, optional Specify an install prefix for the kernelspec. This is needed to install into a non-default location, such as a conda/virtual-env. + env: dict, optional + A dictionary of extra environment variables for the kernel. + These will be added to the current environment variables before the + kernel is started Returns ------- @@ -126,6 +130,8 @@ def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, displ overrides["display_name"] = 'Python %i [profile=%s]' % (sys.version_info[0], profile) else: extra_arguments = None + if env: + overrides['env'] = env path = write_kernel_spec(overrides=overrides, extra_arguments=extra_arguments) dest = kernel_spec_manager.install_kernel_spec( path, kernel_name=kernel_name, user=user, prefix=prefix) @@ -168,10 +174,14 @@ def start(self): parser.add_argument('--sys-prefix', action='store_const', const=sys.prefix, dest='prefix', help="Install to Python's sys.prefix." " Shorthand for --prefix='%s'. For use in conda/virtual-envs." % sys.prefix) + parser.add_argument('--env', action='append', nargs=2, metavar=('ENV', 'VALUE'), + help="Set environment variables for the kernel.") opts = parser.parse_args(self.argv) + if opts.env: + opts.env = {k:v for (k, v) in opts.env} try: dest = install(user=opts.user, kernel_name=opts.name, profile=opts.profile, - prefix=opts.prefix, display_name=opts.display_name) + prefix=opts.prefix, display_name=opts.display_name, env=opts.env) except OSError as e: if e.errno == errno.EACCES: print(e, file=sys.stderr) From d22e1e440e2bb59b0852edc75a6085d885836491 Mon Sep 17 00:00:00 2001 From: Emmanuel Roux Date: Mon, 24 Aug 2020 22:50:15 +0200 Subject: [PATCH 0409/1195] Test pull request #541 --- ipykernel/tests/test_kernelspec.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index ed6b90828..0acd448c8 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -21,6 +21,8 @@ RESOURCES, ) +import pytest + import nose.tools as nt pjoin = os.path.join @@ -140,3 +142,26 @@ def test_install_display_name_overrides_profile(): with open(spec) as f: spec = json.load(f) assert spec["display_name"] == "Display" + + +@pytest.mark.parametrize("env", [ + None, + dict(spam="spam"), + dict(spam="spam", foo='bar') +]) +def test_install_env(tmp_path, env): + with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', + [tmp_path]): + install(env=env) + + spec = tmp_path / 'kernels' / KERNEL_NAME / "kernel.json" + with open(spec) as f: + spec = json.load(f) + + if env: + assert len(env) == len(spec['env']) + for k, v in env.items(): + assert spec['env'][k] == v + else: + assert 'env' not in spec + From c25821293c1beca0906528393f8d33dcc27a3de1 Mon Sep 17 00:00:00 2001 From: Emmanuel Roux Date: Mon, 24 Aug 2020 23:22:07 +0200 Subject: [PATCH 0410/1195] Test pull request #541, fix python 3.5 AppVeyor error --- ipykernel/tests/test_kernelspec.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index 0acd448c8..6e9b7f0f8 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -150,12 +150,13 @@ def test_install_display_name_overrides_profile(): dict(spam="spam", foo='bar') ]) def test_install_env(tmp_path, env): + # python 3.5 // tmp_path must be converted to str with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', - [tmp_path]): + [str(tmp_path)]): install(env=env) spec = tmp_path / 'kernels' / KERNEL_NAME / "kernel.json" - with open(spec) as f: + with spec.open() as f: spec = json.load(f) if env: From 6b3a4813a4d2793a295ea681fd5b9606ea045187 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Wed, 9 Sep 2020 11:57:05 +0200 Subject: [PATCH 0411/1195] Fix stack levels for ipykernel's deprecation warnings --- ipykernel/codeutil.py | 5 ++++- ipykernel/datapub.py | 10 ++++++++-- ipykernel/log.py | 5 ++++- ipykernel/pickleutil.py | 5 ++++- ipykernel/serialize.py | 5 ++++- 5 files changed, 24 insertions(+), 6 deletions(-) diff --git a/ipykernel/codeutil.py b/ipykernel/codeutil.py index 84a7ed7ec..08212a068 100644 --- a/ipykernel/codeutil.py +++ b/ipykernel/codeutil.py @@ -12,7 +12,10 @@ # Distributed under the terms of the Modified BSD License. import warnings -warnings.warn("ipykernel.codeutil is deprecated since IPykernel 4.3.1. It has moved to ipyparallel.serialize", DeprecationWarning) +warnings.warn("ipykernel.codeutil is deprecated since IPykernel 4.3.1. It has moved to ipyparallel.serialize", + DeprecationWarning, + stacklevel=2 +) import copyreg import sys diff --git a/ipykernel/datapub.py b/ipykernel/datapub.py index 0f765719c..9eec561e3 100644 --- a/ipykernel/datapub.py +++ b/ipykernel/datapub.py @@ -2,7 +2,10 @@ """ import warnings -warnings.warn("ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", DeprecationWarning) +warnings.warn("ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", + DeprecationWarning, + stacklevel=2 +) # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. @@ -56,7 +59,10 @@ def publish_data(data): data : dict The data to be published. Think of it as a namespace. """ - warnings.warn("ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", DeprecationWarning) + warnings.warn("ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", + DeprecationWarning, + stacklevel=2 + ) from ipykernel.zmqshell import ZMQInteractiveShell ZMQInteractiveShell.instance().data_pub.publish_data(data) diff --git a/ipykernel/log.py b/ipykernel/log.py index b46b22ab8..728623fc7 100644 --- a/ipykernel/log.py +++ b/ipykernel/log.py @@ -3,7 +3,10 @@ from zmq.log.handlers import PUBHandler import warnings -warnings.warn("ipykernel.log is deprecated. It has moved to ipyparallel.engine.log", DeprecationWarning) +warnings.warn("ipykernel.log is deprecated. It has moved to ipyparallel.engine.log", + DeprecationWarning + stacklevel=2 +) class EnginePUBHandler(PUBHandler): """A simple PUBHandler subclass that sets root_topic""" diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 669b6ac91..c0dd4a807 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -4,7 +4,10 @@ # Distributed under the terms of the Modified BSD License. import warnings -warnings.warn("ipykernel.pickleutil is deprecated. It has moved to ipyparallel.", DeprecationWarning) +warnings.warn("ipykernel.pickleutil is deprecated. It has moved to ipyparallel.", + DeprecationWarning, + stacklevel=2 +) import copy import sys diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 437ebb591..1739aadb2 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -4,7 +4,10 @@ # Distributed under the terms of the Modified BSD License. import warnings -warnings.warn("ipykernel.serialize is deprecated. It has moved to ipyparallel.serialize", DeprecationWarning) +warnings.warn("ipykernel.serialize is deprecated. It has moved to ipyparallel.serialize", + DeprecationWarning, + stacklevel=2 +) import pickle From c799c97cbcaaedad59156c922f5762f8bcef86c7 Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Wed, 9 Sep 2020 11:56:47 +0200 Subject: [PATCH 0412/1195] Stop using deprecated trailets api for observing --- ipykernel/ipkernel.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index c799518f4..661627352 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -12,7 +12,7 @@ from ipython_genutils.py3compat import safe_unicode from IPython.utils.tokenutil import token_at_cursor, line_at_cursor from tornado import gen -from traitlets import Instance, Type, Any, List, Bool +from traitlets import Instance, Type, Any, List, Bool, observe, observe_compat from .comm import CommManager from .kernelbase import Kernel as KernelBase @@ -43,14 +43,18 @@ class IPythonKernel(KernelBase): ).tag(config=True) user_module = Any() - def _user_module_changed(self, name, old, new): + @observe('user_module') + @observe_compat + def _user_module_changed(self, change): if self.shell is not None: - self.shell.user_module = new + self.shell.user_module = change['new'] user_ns = Instance(dict, args=None, allow_none=True) - def _user_ns_changed(self, name, old, new): + @observe('user_ns') + @observe_compat + def _user_ns_changed(self, change): if self.shell is not None: - self.shell.user_ns = new + self.shell.user_ns = change['new'] self.shell.init_user_ns() # A reference to the Python builtin 'raw_input' function. From 2b72a63a3651a398d9ca7c0f9c0be73e79ec9e4c Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Wed, 9 Sep 2020 14:53:43 +0200 Subject: [PATCH 0413/1195] Use the non-deprecated ipyparallel interfaces when available --- ipykernel/datapub.py | 7 ++++++- ipykernel/serialize.py | 17 +++++++++++++---- ipykernel/zmqshell.py | 9 ++++++++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/ipykernel/datapub.py b/ipykernel/datapub.py index 9eec561e3..19db14a6c 100644 --- a/ipykernel/datapub.py +++ b/ipykernel/datapub.py @@ -13,7 +13,12 @@ from traitlets.config import Configurable from traitlets import Instance, Dict, CBytes, Any from ipykernel.jsonutil import json_clean -from ipykernel.serialize import serialize_object +try: + # available since ipyparallel 5.0.0 + from ipyparallel.serialize import serialize_object +except ImportError: + # Deprecated since ipykernel 4.3.0 + from ipykernel.serialize import serialize_object from jupyter_client.session import Session, extract_header diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 1739aadb2..a8558e80a 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -13,10 +13,19 @@ from itertools import chain -from ipykernel.pickleutil import ( - can, uncan, can_sequence, uncan_sequence, CannedObject, - istype, sequence_types, PICKLE_PROTOCOL, -) +try: + # available since ipyparallel 5.0.0 + from ipyparallel.serialize.canning import ( + can, uncan, can_sequence, uncan_sequence, CannedObject, + istype, sequence_types, + ) + from ipyparallel.serialize.serialize import PICKLE_PROTOCOL +except ImportError: + # Deprecated since ipykernel 4.3.0 + from ipykernel.pickleutil import ( + can, uncan, can_sequence, uncan_sequence, CannedObject, + istype, sequence_types, PICKLE_PROTOCOL, + ) from jupyter_client.session import MAX_ITEMS, MAX_BYTES diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index b4056e54e..1e68569d1 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -49,6 +49,13 @@ from jupyter_core.paths import jupyter_runtime_dir from jupyter_client.session import extract_header, Session +try: + # available since ipyparallel 5.0.0 + from ipyparallel.engine.datapub import ZMQDataPublisher +except ImportError: + # Deprecated since ipykernel 4.3.0 + from ipykernel.datapub import ZMQDataPublisher + #----------------------------------------------------------------------------- # Functions and classes #----------------------------------------------------------------------------- @@ -438,7 +445,7 @@ class ZMQInteractiveShell(InteractiveShell): displayhook_class = Type(ZMQShellDisplayHook) display_pub_class = Type(ZMQDisplayPublisher) - data_pub_class = Type('ipykernel.datapub.ZMQDataPublisher') + data_pub_class = Type(ZMQDataPublisher) kernel = Any() parent_header = Any() From c943abe8d74466621c0ac0090ed3e4da3918d82e Mon Sep 17 00:00:00 2001 From: jack1142 <6032823+jack1142@users.noreply.github.com> Date: Wed, 9 Sep 2020 15:15:25 +0200 Subject: [PATCH 0414/1195] Use the tornado's IOLoop with pyzmq>=17 --- ipykernel/iostream.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 64df8c7f6..4d1e6c95d 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -14,7 +14,11 @@ from io import StringIO, TextIOBase import zmq -from zmq.eventloop.ioloop import IOLoop +if zmq.pyzmq_version_info() >= (17, 0): + from tornado.ioloop import IOLoop +else: + # deprecated since pyzmq 17 + from zmq.eventloop.ioloop import IOLoop from zmq.eventloop.zmqstream import ZMQStream from jupyter_client.session import extract_header From f01c2c070ae0f57aebc8ac9f461940e4ef3ce33c Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 12 Oct 2020 11:08:29 -0700 Subject: [PATCH 0415/1195] Start testing on Python 3.9 --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 9e1a47b7b..9336d27c9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,8 @@ matrix: python: 3.8 - arch: amd64 python: 3.8 + - arch: amd64 + python: "3.9-dev" sudo: false dist: xenial install: @@ -57,7 +59,7 @@ after_success: - codecov matrix: include: - - python: 3.7 + - python: 3.8 env: - IPYTHON=master allow_failures: From 8c3aac838120d3eb2f2d6d87a130df07abc98f43 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 10 Dec 2020 16:24:56 -0500 Subject: [PATCH 0416/1195] add github actions, bail on asyncio patch for tornado 6.1 (#1) --- .github/workflows/ci.yml | 65 ++++++++++++++++++++++ .travis.yml | 66 ----------------------- appveyor.yml | 36 ------------- examples/embedding/inprocess_qtconsole.py | 4 +- examples/embedding/inprocess_terminal.py | 4 +- ipykernel/inprocess/tests/test_kernel.py | 10 +++- ipykernel/kernelapp.py | 6 ++- ipykernel/tests/test_kernel.py | 16 ++++-- 8 files changed, 95 insertions(+), 112 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..3f3457aa2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: ipykernel tests + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu, macos, windows] + python-version: [ '3.6', '3.7', '3.8', '3.9', 'pypy3' ] + exclude: + - os: windows + python-version: pypy3 + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Install Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + architecture: 'x64' + - name: Upgrade packaging dependencies + run: | + pip install --upgrade pip setuptools wheel --user + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: Cache pip + uses: actions/cache@v1 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.python-version }}- + ${{ runner.os }}-pip- + - name: Install the Python dependencies + run: | + pip install --pre --upgrade --upgrade-strategy=eager .[test] codecov + - name: Install matplotlib + if: ${{ matrix.os != 'macos' && matrix.python-version != 'pypy3' }} + run: | + pip install matplotlib || echo 'failed to install matplolib' + - name: Install alternate event loops + if: ${{ matrix.os != 'windows' }} + run: | + pip install curio || echo 'ignoring curio install failure' + pip install trio || echo 'ignoring trio install failure' + - name: List installed packages + run: | + pip freeze + pip check + - name: Run the tests + timeout-minutes: 30 + run: | + pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 + - name: Coverage + run: | + codecov diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9336d27c9..000000000 --- a/.travis.yml +++ /dev/null @@ -1,66 +0,0 @@ -language: python -matrix: - include: - - arch: arm64 - python: "nightly" - dist: bionic - - arch: amd64 - python: "nightly" - - arch: arm64 - python: 3.5 - - arch: amd64 - python: 3.5 - - arch: arm64 - python: 3.6 - - arch: amd64 - python: 3.6 - - arch: arm64 - python: 3.7 - - arch: amd64 - python: 3.7 - - arch: arm64 - python: 3.8 - - arch: amd64 - python: 3.8 - - arch: amd64 - python: "3.9-dev" -sudo: false -dist: xenial -install: - - | - # pip install - pip install --upgrade setuptools pip - pip install --pre --upgrade --upgrade-strategy=eager .[test] codecov - - | - # install matplotlib - if [[ "$TRAVIS_PYTHON_VERSION" == "3.6" ]]; then - pip install matplotlib curio trio - fi - - | - # pin tornado - if [[ ! -z "$TORNADO" ]]; then - pip install tornado=="$TORNADO" - fi - - | - # pin IPython - if [[ ! -z "$IPYTHON" ]]; then - if [[ "$IPYTHON" == "master" ]]; then - SPEC=git+https://github.com/ipython/ipython#egg=ipython - else - SPEC="ipython==$IPYTHON" - fi - pip install --upgrade --pre "$SPEC" - fi - - pip freeze -script: - - jupyter kernelspec list - - pytest --cov ipykernel --durations 10 -v ipykernel -after_success: - - codecov -matrix: - include: - - python: 3.8 - env: - - IPYTHON=master - allow_failures: - - python: "nightly" diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 7d564a918..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,36 +0,0 @@ -build: false -shallow_clone: false -skip_branch_with_pr: true -clone_depth: 1 - -environment: - - matrix: - - python: "C:/Python38-x64" - - python: "C:/Python36-x64" - - python: "C:/Python35" - -cache: - - C:\Users\appveyor\AppData\Local\pip\Cache - -init: - - cmd: set PATH=%python%;%python%\scripts;%PATH% - -install: - - cmd: | - python -m pip install --upgrade setuptools pip wheel - pip --version - - cmd: | - pip install --pre -e . - pip install ipykernel[test] - - cmd: | - pip install matplotlib numpy - pip freeze - - cmd: python -c "import ipykernel.kernelspec; ipykernel.kernelspec.install(user=True)" - -test_script: - - cmd: pytest -v -x --cov ipykernel ipykernel - -on_success: - - cmd: pip install codecov - - cmd: codecov diff --git a/examples/embedding/inprocess_qtconsole.py b/examples/embedding/inprocess_qtconsole.py index cb6d79e1d..5d9998bff 100644 --- a/examples/embedding/inprocess_qtconsole.py +++ b/examples/embedding/inprocess_qtconsole.py @@ -1,6 +1,8 @@ import os import sys +import tornado + from qtconsole.rich_ipython_widget import RichIPythonWidget from qtconsole.inprocess import QtInProcessKernelManager from IPython.lib import guisupport @@ -21,7 +23,7 @@ def init_asyncio_patch(): FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8): + if sys.platform.startswith("win") and sys.version_info >= (3, 8) and tornado.version_info < (6, 1): import asyncio try: from asyncio import ( diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index cb04a2716..5ccab1654 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -1,6 +1,8 @@ import os import sys +import tornado + from ipykernel.inprocess import InProcessKernelManager from jupyter_console.ptshell import ZMQTerminalInteractiveShell @@ -20,7 +22,7 @@ def init_asyncio_patch(): FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8): + if sys.platform.startswith("win") and sys.version_info >= (3, 8) and tornado.version_info < (6, 1): import asyncio try: from asyncio import ( diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 4921d3f5e..92c8ea538 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -5,6 +5,10 @@ import sys import unittest +import pytest + +import tornado + from ipykernel.inprocess.blocking import BlockingInProcessKernelClient from ipykernel.inprocess.manager import InProcessKernelManager from ipykernel.inprocess.ipkernel import InProcessKernel @@ -29,7 +33,7 @@ def _init_asyncio_patch(): FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8): + if sys.platform.startswith("win") and sys.version_info >= (3, 8) and tornado.version_info < (6, 1): import asyncio try: from asyncio import ( @@ -76,6 +80,10 @@ def test_raw_input(self): sys.stdin = sys_stdin assert self.km.kernel.shell.user_ns.get('x') == 'foobar' + @pytest.mark.skipif( + '__pypy__' in sys.builtin_module_names, + reason="fails on pypy" + ) def test_stdout(self): """ Does the in-process kernel correctly capture IO? """ diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 1d78c20a9..ee691508e 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -11,7 +11,9 @@ import traceback import logging +import tornado from tornado import ioloop + import zmq from zmq.eventloop import ioloop as zmq_ioloop from zmq.eventloop.zmqstream import ZMQStream @@ -521,7 +523,7 @@ def _init_asyncio_patch(self): FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8): + if sys.platform.startswith("win") and sys.version_info >= (3, 8) and tornado.version_info < (6, 1): import asyncio try: from asyncio import ( @@ -539,7 +541,7 @@ def _init_asyncio_patch(self): def init_pdb(self): """Replace pdb with IPython's version that is interruptible. - + With the non-interruptible version, stopping pdb() locks up the kernel in a non-recoverable state. """ diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index feeb95f29..8cfcd3b2a 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -78,7 +78,10 @@ def test_sys_path_profile_dir(): @flaky(max_runs=3) -@dec.skipif(sys.platform == 'win32', "subprocess prints fail on Windows") +@dec.skipif( + sys.platform == 'win32' or (sys.platform == "darwin" and sys.version_info >=(3, 8)), + "subprocess prints fail on Windows and MacOS Python 3.8+" +) def test_subprocess_print(): """printing from forked mp.Process""" with new_kernel() as kc: @@ -130,7 +133,10 @@ def test_subprocess_noprint(): @flaky(max_runs=3) -@dec.skipif(sys.platform == 'win32', "subprocess prints fail on Windows") +@dec.skipif( + sys.platform == 'win32' or (sys.platform == "darwin" and sys.version_info >=(3, 8)), + "subprocess prints fail on Windows and MacOS Python 3.8+" +) def test_subprocess_error(): """error in mp.Process doesn't crash""" with new_kernel() as kc: @@ -297,7 +303,7 @@ def test_message_order(): assert reply['parent_header']['msg_id'] == msg_id -@dec.skipif(sys.platform.startswith('linux')) +@dec.skipif(sys.platform.startswith('linux') or sys.platform.startswith('darwin')) def test_unc_paths(): with kernel() as kc, TemporaryDirectory() as td: drive_file_path = os.path.join(td, 'unc.txt') @@ -345,7 +351,7 @@ def test_shutdown(): def test_interrupt_during_input(): """ The kernel exits after being interrupted while waiting in input(). - + input() appears to have issues other functions don't, and it needs to be interruptible in order for pdb to be interruptible. """ @@ -384,4 +390,4 @@ def test_interrupt_during_pdb_set_trace(): reply = kc.get_shell_msg(timeout=TIMEOUT) validate_message(reply, 'execute_reply', msg_id) reply = kc.get_shell_msg(timeout=TIMEOUT) - validate_message(reply, 'execute_reply', msg_id2) \ No newline at end of file + validate_message(reply, 'execute_reply', msg_id2) From 9098bcdc30de72cd9b1de31080d54798182424ad Mon Sep 17 00:00:00 2001 From: Jovyan 123 Date: Thu, 10 Dec 2020 16:53:11 -0600 Subject: [PATCH 0417/1195] Add changelog for 5.4 --- docs/changelog.rst | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 868320ea0..c85461386 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,22 @@ Changes in IPython kernel ========================= +5.4 +--- + +5.4.0 +***** + +5.4.0 is generally focused on code quality improvements and tornado asyncio compatibility. + +- Add github actions, bail on asyncio patch for tornado 6.1. (:ghpull:`564`) +- Start testing on Python 3.9. (:ghpull:`551`) +- Fix stack levels for ipykernel's deprecation warnings and stop using some deprecated APIs. (:ghpull:`547`) +- Add env parameter to kernel installation (:ghpull:`541`) +- Fix stop_on_error_timeout blocking other messages in queue. (:ghpull:`539`) +- Remove most of the python 2 compat code. (:ghpull:`537`) +- Remove u-prefix from strings. (:ghpull:`538`) + 5.3 --- @@ -94,8 +110,7 @@ nbconvert. - Add a no-op ``flush`` method to ``DummySocket`` and comply with stream API (:ghpull: `405`) - Update kernel version to indicate kernel v5.3 support (:ghpull: `394`) -- Add testing for upcoming Python 3.8 and PEP 570 positional parameters - (:ghpull: `396`, :ghpull: `408`) +- Add testing for upcoming Python 3.8 and PEP 570 positional parameters (:ghpull: `396`, :ghpull: `408`) 5.1.1 From ddd59b06fab7c2ff51959ce367681b29ac6a86e0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 10 Dec 2020 17:13:06 -0600 Subject: [PATCH 0418/1195] Release 5.4.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 30d9e62c7..e93501b29 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 3, 4) +version_info = (5, 4, 0) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From fb3bcc2d2c45a4bca5118fe0f5aabf0223b34ed1 Mon Sep 17 00:00:00 2001 From: Gordon Ball Date: Fri, 11 Dec 2020 08:45:24 +0000 Subject: [PATCH 0419/1195] log.py: fix invalid syntax --- ipykernel/log.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/log.py b/ipykernel/log.py index 728623fc7..97789c378 100644 --- a/ipykernel/log.py +++ b/ipykernel/log.py @@ -4,7 +4,7 @@ import warnings warnings.warn("ipykernel.log is deprecated. It has moved to ipyparallel.engine.log", - DeprecationWarning + DeprecationWarning, stacklevel=2 ) From 2585955dd66c0e86ec5526c51869802da79c48b3 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Dec 2020 10:38:56 -0600 Subject: [PATCH 0420/1195] Release 5.4.1 --- docs/changelog.rst | 4 ++++ ipykernel/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index c85461386..7233f9b69 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,10 @@ Changes in IPython kernel 5.4 --- +5.4.1 +***** +- Invalid syntax in ipykernel/log.py. (:ghpull:`567`) + 5.4.0 ***** diff --git a/ipykernel/_version.py b/ipykernel/_version.py index e93501b29..60873ab7a 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 4, 0) +version_info = (5, 4, 1) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From dc8461c3dee2132fca3d789e685bf8700ec742b9 Mon Sep 17 00:00:00 2001 From: Afshin Taylor Darian Date: Fri, 11 Dec 2020 21:59:57 +0000 Subject: [PATCH 0421/1195] Revert "Fix stop_on_error_timeout blocking other messages in queue" --- ipykernel/kernelbase.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index af051067b..5a988d37e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -40,6 +40,7 @@ CONTROL_PRIORITY = 1 SHELL_PRIORITY = 10 +ABORT_PRIORITY = 20 class Kernel(SingletonConfigurable): @@ -791,11 +792,16 @@ def _abort_queues(self): stream.flush() self._aborting = True - def stop_aborting(f): - self.log.info("Finishing abort") - self._aborting = False + self.schedule_dispatch( + ABORT_PRIORITY, + self._dispatch_abort, + ) - self.io_loop.add_future(gen.sleep(self.stop_on_error_timeout), stop_aborting) + @gen.coroutine + def _dispatch_abort(self): + self.log.info("Finishing abort") + yield gen.sleep(self.stop_on_error_timeout) + self._aborting = False def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" From 003ac85f695c026847fb2a4373c2da2bd7aa88a5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Dec 2020 16:21:58 -0600 Subject: [PATCH 0422/1195] Release 5.4.2 --- docs/changelog.rst | 4 ++++ ipykernel/_version.py | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 7233f9b69..fe0c3f3fd 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,10 @@ Changes in IPython kernel 5.4 --- +5.4.2 +***** +- Revert "Fix stop_on_error_timeout blocking other messages in queue". (:ghpull:`570`) + 5.4.1 ***** - Invalid syntax in ipykernel/log.py. (:ghpull:`567`) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 60873ab7a..adf820552 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 4, 1) +version_info = (5, 4, 2) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 45afaab2d0779455b1bc9fbd2cc216af8bc21abc Mon Sep 17 00:00:00 2001 From: Glen Takahashi Date: Mon, 14 Dec 2020 13:23:47 -0800 Subject: [PATCH 0423/1195] Revert "Revert "Fix stop_on_error_timeout blocking other messages in queue"" This reverts commit dc8461c3dee2132fca3d789e685bf8700ec742b9. --- ipykernel/kernelbase.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 5a988d37e..af051067b 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -40,7 +40,6 @@ CONTROL_PRIORITY = 1 SHELL_PRIORITY = 10 -ABORT_PRIORITY = 20 class Kernel(SingletonConfigurable): @@ -792,16 +791,11 @@ def _abort_queues(self): stream.flush() self._aborting = True - self.schedule_dispatch( - ABORT_PRIORITY, - self._dispatch_abort, - ) + def stop_aborting(f): + self.log.info("Finishing abort") + self._aborting = False - @gen.coroutine - def _dispatch_abort(self): - self.log.info("Finishing abort") - yield gen.sleep(self.stop_on_error_timeout) - self._aborting = False + self.io_loop.add_future(gen.sleep(self.stop_on_error_timeout), stop_aborting) def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" From 9655cfdc52d85dfffb38c48b806679f4b14e69aa Mon Sep 17 00:00:00 2001 From: Glen Takahashi Date: Mon, 14 Dec 2020 14:16:25 -0800 Subject: [PATCH 0424/1195] Only abort execute_requests --- ipykernel/kernelbase.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index af051067b..9c272c5e9 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -181,10 +181,6 @@ def dispatch_control(self, msg): # Set the parent message for side effects. self.set_parent(idents, msg) self._publish_status('busy') - if self._aborting: - self._send_abort_reply(self.control_stream, msg, idents) - self._publish_status('idle') - return header = msg['header'] msg_type = header['msg_type'] @@ -232,7 +228,10 @@ def dispatch_shell(self, stream, msg): self.set_parent(idents, msg) self._publish_status('busy') - if self._aborting: + msg_type = msg['header']['msg_type'] + + # Only abort execute requests + if self._aborting and msg_type == 'execute_request': self._send_abort_reply(stream, msg, idents) self._publish_status('idle') # flush to ensure reply is sent before @@ -240,8 +239,6 @@ def dispatch_shell(self, stream, msg): stream.flush(zmq.POLLOUT) return - msg_type = msg['header']['msg_type'] - # Print some info about this message and leave a '--->' marker, so it's # easier to trace visually the message chain when debugging. Each # handler prints its message at the end. From 297e06c7eb00a83eaf24681f5a881850828c1df9 Mon Sep 17 00:00:00 2001 From: Glen Takahashi Date: Mon, 14 Dec 2020 15:38:43 -0800 Subject: [PATCH 0425/1195] Add test --- ipykernel/tests/test_kernel.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 8cfcd3b2a..9602dd737 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -391,3 +391,29 @@ def test_interrupt_during_pdb_set_trace(): validate_message(reply, 'execute_reply', msg_id) reply = kc.get_shell_msg(timeout=TIMEOUT) validate_message(reply, 'execute_reply', msg_id2) + +def test_abort_execute_requests(): + """test that execute_request's are aborted after an error""" + with kernel() as kc: + msg_id1 = kc.execute(code="assert False") + msg_id2 = kc.execute(code="assert True") + reply1 = kc.get_shell_msg(timeout=TIMEOUT) + reply2 = kc.get_shell_msg(timeout=TIMEOUT) + assert reply1['content']['status'] == 'error' + assert reply2['content']['status'] == 'aborted' + +def test_dont_abort_non_execute_requests(): + """test that kernel_info, comm_info and inspect requests are not aborted after an error""" + with kernel() as kc: + msg_id1 = kc.execute(code="assert False") + msg_id2 = kc.kernel_info() + msg_id3 = kc.comm_info() + msg_id4 = kc.inspect(code="pri") + reply1 = kc.get_shell_msg(timeout=TIMEOUT) # execute + reply2 = kc.get_shell_msg(timeout=TIMEOUT) # kernel_info + reply3 = kc.get_shell_msg(timeout=TIMEOUT) # comm_info + reply4 = kc.get_shell_msg(timeout=TIMEOUT) # inspect + assert reply1['content']['status'] == 'error' + assert reply2['content']['status'] == 'ok' + assert reply3['content']['status'] == 'ok' + assert reply4['content']['status'] == 'ok' \ No newline at end of file From e213664980d7657640247f117a3e9e9e62d800a3 Mon Sep 17 00:00:00 2001 From: Glen Takahashi Date: Mon, 14 Dec 2020 15:47:21 -0800 Subject: [PATCH 0426/1195] Add newline --- ipykernel/tests/test_kernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 9602dd737..0481b7cf6 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -416,4 +416,4 @@ def test_dont_abort_non_execute_requests(): assert reply1['content']['status'] == 'error' assert reply2['content']['status'] == 'ok' assert reply3['content']['status'] == 'ok' - assert reply4['content']['status'] == 'ok' \ No newline at end of file + assert reply4['content']['status'] == 'ok' From 3c652dec940fdeac47e021894fdf649cd6fbb7d2 Mon Sep 17 00:00:00 2001 From: Glen Takahashi Date: Mon, 14 Dec 2020 16:06:36 -0800 Subject: [PATCH 0427/1195] Add aborted message_spec --- ipykernel/tests/test_message_spec.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 51a699fe8..5ca293be1 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -114,6 +114,8 @@ def check(self, d): ExecuteReplyOkay().check(d) elif d['status'] == 'error': ExecuteReplyError().check(d) + elif d['status'] == 'aborted': + ExecuteReplyAborted().check(d) class ExecuteReplyOkay(Reply): @@ -122,11 +124,16 @@ class ExecuteReplyOkay(Reply): class ExecuteReplyError(Reply): + status = Enum(('error',)) ename = Unicode() evalue = Unicode() traceback = List(Unicode()) +class ExecuteReplyAborted(Reply): + status = Enum(('aborted',)) + + class InspectReply(Reply, MimeBundle): found = Bool() From 983d5708154061126aea2fe5ca1b32051fe723f1 Mon Sep 17 00:00:00 2001 From: Glen Takahashi Date: Mon, 14 Dec 2020 16:19:14 -0800 Subject: [PATCH 0428/1195] Move test --- ipykernel/tests/test_kernel.py | 26 -------------------------- ipykernel/tests/test_message_spec.py | 24 ++++++++++++++++++++++++ 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 0481b7cf6..8cfcd3b2a 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -391,29 +391,3 @@ def test_interrupt_during_pdb_set_trace(): validate_message(reply, 'execute_reply', msg_id) reply = kc.get_shell_msg(timeout=TIMEOUT) validate_message(reply, 'execute_reply', msg_id2) - -def test_abort_execute_requests(): - """test that execute_request's are aborted after an error""" - with kernel() as kc: - msg_id1 = kc.execute(code="assert False") - msg_id2 = kc.execute(code="assert True") - reply1 = kc.get_shell_msg(timeout=TIMEOUT) - reply2 = kc.get_shell_msg(timeout=TIMEOUT) - assert reply1['content']['status'] == 'error' - assert reply2['content']['status'] == 'aborted' - -def test_dont_abort_non_execute_requests(): - """test that kernel_info, comm_info and inspect requests are not aborted after an error""" - with kernel() as kc: - msg_id1 = kc.execute(code="assert False") - msg_id2 = kc.kernel_info() - msg_id3 = kc.comm_info() - msg_id4 = kc.inspect(code="pri") - reply1 = kc.get_shell_msg(timeout=TIMEOUT) # execute - reply2 = kc.get_shell_msg(timeout=TIMEOUT) # kernel_info - reply3 = kc.get_shell_msg(timeout=TIMEOUT) # comm_info - reply4 = kc.get_shell_msg(timeout=TIMEOUT) # inspect - assert reply1['content']['status'] == 'error' - assert reply2['content']['status'] == 'ok' - assert reply3['content']['status'] == 'ok' - assert reply4['content']['status'] == 'ok' diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 5ca293be1..c6404a626 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -355,6 +355,30 @@ def test_execute_stop_on_error(): assert reply['content']['status'] == 'ok' +def test_non_execute_stop_on_error(): + """test that non-execute_request's are not aborted after an error""" + flush_channels() + + fail = '\n'.join([ + # sleep to ensure subsequent message is waiting in the queue to be aborted + 'import time', + 'time.sleep(0.5)', + 'raise ValueError', + ]) + KC.execute(code=fail) + KC.kernel_info() + KC.comm_info() + KC.inspect(code="print") + reply = KC.get_shell_msg(timeout=TIMEOUT) # execute + assert reply['content']['status'] == 'error' + reply = KC.get_shell_msg(timeout=TIMEOUT) # kernel_info + assert reply['content']['status'] == 'ok' + reply = KC.get_shell_msg(timeout=TIMEOUT) # comm_info + assert reply['content']['status'] == 'ok' + reply = KC.get_shell_msg(timeout=TIMEOUT) # inspect + assert reply['content']['status'] == 'ok' + + def test_user_expressions(): flush_channels() From 5b2e8c6589c9e3e6ec2199c43be4258c12e7f65f Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Wed, 16 Dec 2020 17:15:21 +0100 Subject: [PATCH 0429/1195] Deacrease lag time for eventloop While using interactive matplotlib figure, the figure will freeze for 1 seconds very often. This get rid of this freeze. --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 9c272c5e9..deb23ef63 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -318,7 +318,7 @@ def schedule_next(): # flush the eventloop every so often, # giving us a chance to handle messages in the meantime self.log.debug("Scheduling eventloop advance") - self.io_loop.call_later(1, advance_eventloop) + self.io_loop.call_later(0.001, advance_eventloop) # begin polling the eventloop schedule_next() From d4b59d5caeb534c544c8fd22d0bb7dc796f84de0 Mon Sep 17 00:00:00 2001 From: Cj-bc Date: Sat, 9 Jan 2021 15:18:04 +0900 Subject: [PATCH 0430/1195] Fix comment typo --- ipykernel/kernelbase.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 9c272c5e9..aba3223a9 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -808,7 +808,7 @@ def _send_abort_reply(self, stream, msg, idents): ) def _no_raw_input(self): - """Raise StdinNotImplentedError if active frontend doesn't support + """Raise StdinNotImplementedError if active frontend doesn't support stdin.""" raise StdinNotImplementedError("raw_input was called, but this " "frontend does not support stdin.") @@ -818,7 +818,7 @@ def getpass(self, prompt='', stream=None): Raises ------ - StdinNotImplentedError if active frontend doesn't support stdin. + StdinNotImplementedError if active frontend doesn't support stdin. """ if not self._allow_stdin: raise StdinNotImplementedError( @@ -839,7 +839,7 @@ def raw_input(self, prompt=''): Raises ------ - StdinNotImplentedError if active frontend doesn't support stdin. + StdinNotImplementedError if active frontend doesn't support stdin. """ if not self._allow_stdin: raise StdinNotImplementedError( From d1065430a269ee6c3d1795e43ca472d15050b6ab Mon Sep 17 00:00:00 2001 From: Cj-bc Date: Sat, 9 Jan 2021 15:28:14 +0900 Subject: [PATCH 0431/1195] Update URL of Jupyter Contributing Guide --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2be71db2b..98696576f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Welcome! -For contributing tips, follow the [Jupyter Contributing Guide](https://jupyter.readthedocs.io/en/latest/contributor/content-contributor.html). +For contributing tips, follow the [Jupyter Contributing Guide](https://jupyter.readthedocs.io/en/latest/contributing/content-contributor.html). Please make sure to follow the [Jupyter Code of Conduct](https://github.com/jupyter/governance/blob/master/conduct/code_of_conduct.md). ## Installing ipykernel for development From 45115b8d785e607010893d11f18b060e78a0ebdc Mon Sep 17 00:00:00 2001 From: David Brochart Date: Mon, 11 Jan 2021 10:22:19 +0100 Subject: [PATCH 0432/1195] Rework wait_for_ready logic --- ipykernel/inprocess/blocking.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index ead193699..cc995a51b 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -76,10 +76,21 @@ class BlockingInProcessKernelClient(InProcessKernelClient): def wait_for_ready(self): # Wait for kernel info reply on shell channel while True: - msg = self.shell_channel.get_msg(block=True) - if msg['msg_type'] == 'kernel_info_reply': - self._handle_kernel_info_reply(msg) - break + self.kernel_info() + try: + msg = self.shell_channel.get_msg(block=True, timeout=1) + except Empty: + pass + else: + if msg['msg_type'] == 'kernel_info_reply': + # Checking that IOPub is connected. If it is not connected, start over. + try: + self.iopub_channel.get_msg(block=True, timeout=0.2) + except Empty: + pass + else: + self._handle_kernel_info_reply(msg) + break # Flush IOPub channel while True: From 61fb170985bc1d1cef380ed9c5a137c78f9ef120 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Mon, 11 Jan 2021 14:25:44 +0100 Subject: [PATCH 0433/1195] Pin jedi<=0.17.2 in tests --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 4b3190d77..d9f51c2c7 100644 --- a/setup.py +++ b/setup.py @@ -97,6 +97,7 @@ def run(self): 'pytest-cov', 'flaky', 'nose', # nose because there are still a few nose.tools imports hanging around + 'jedi<=0.17.2' ], }, classifiers=[ From a6a68b67abf657516027c52a24d22858eb8f1f7d Mon Sep 17 00:00:00 2001 From: David Brochart Date: Mon, 11 Jan 2021 15:11:02 +0100 Subject: [PATCH 0434/1195] Fix tests --- ipykernel/tests/test_kernel.py | 13 +++++++------ ipykernel/tests/test_message_spec.py | 29 ++++++++++++++-------------- ipykernel/tests/utils.py | 16 ++++++++++++++- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 8cfcd3b2a..bdd74e24e 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -21,7 +21,7 @@ from .utils import ( new_kernel, kernel, TIMEOUT, assemble_output, execute, - flush_channels, wait_for_idle, + flush_channels, wait_for_idle, get_reply, ) @@ -360,9 +360,9 @@ def test_interrupt_during_input(): msg_id = kc.execute("input()") time.sleep(1) # Make sure it's actually waiting for input. km.interrupt_kernel() - # If we failed to interrupt interrupt, this will timeout: - reply = kc.get_shell_msg(timeout=TIMEOUT) from .test_message_spec import validate_message + # If we failed to interrupt interrupt, this will timeout: + reply = get_reply(kc, msg_id, TIMEOUT) validate_message(reply, 'execute_reply', msg_id) @@ -385,9 +385,10 @@ def test_interrupt_during_pdb_set_trace(): msg_id2 = kc.execute("3 + 4") time.sleep(1) # Make sure it's actually waiting for input. km.interrupt_kernel() - # If we failed to interrupt interrupt, this will timeout: from .test_message_spec import validate_message - reply = kc.get_shell_msg(timeout=TIMEOUT) + # If we failed to interrupt interrupt, this will timeout: + reply = get_reply(kc, msg_id, TIMEOUT) validate_message(reply, 'execute_reply', msg_id) - reply = kc.get_shell_msg(timeout=TIMEOUT) + # If we failed to interrupt interrupt, this will timeout: + reply = get_reply(kc, msg_id2, TIMEOUT) validate_message(reply, 'execute_reply', msg_id2) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index c6404a626..a142165f1 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -15,7 +15,8 @@ HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum ) -from .utils import TIMEOUT, start_global_kernel, flush_channels, execute +from .utils import (TIMEOUT, start_global_kernel, flush_channels, execute, + get_reply, ) #----------------------------------------------------------------------------- # Globals @@ -278,7 +279,7 @@ def test_execute(): flush_channels() msg_id = KC.execute(code='x=1') - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'execute_reply', msg_id) @@ -405,7 +406,7 @@ def test_oinfo(): flush_channels() msg_id = KC.inspect('a') - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'inspect_reply', msg_id) @@ -415,7 +416,7 @@ def test_oinfo_found(): msg_id, reply = execute(code='a=5') msg_id = KC.inspect('a') - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'inspect_reply', msg_id) content = reply['content'] assert content['found'] @@ -430,7 +431,7 @@ def test_oinfo_detail(): msg_id, reply = execute(code='ip=get_ipython()') msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1) - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'inspect_reply', msg_id) content = reply['content'] assert content['found'] @@ -443,7 +444,7 @@ def test_oinfo_not_found(): flush_channels() msg_id = KC.inspect('dne') - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'inspect_reply', msg_id) content = reply['content'] assert not content['found'] @@ -455,7 +456,7 @@ def test_complete(): msg_id, reply = execute(code="alpha = albert = 5") msg_id = KC.complete('al', 2) - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'complete_reply', msg_id) matches = reply['content']['matches'] for name in ('alpha', 'albert'): @@ -466,7 +467,7 @@ def test_kernel_info_request(): flush_channels() msg_id = KC.kernel_info() - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'kernel_info_reply', msg_id) @@ -477,7 +478,7 @@ def test_connect_request(): return msg['header']['msg_id'] msg_id = KC.kernel_info() - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'connect_reply', msg_id) @@ -486,7 +487,7 @@ def test_comm_info_request(): if not hasattr(KC, 'comm_info'): raise SkipTest() msg_id = KC.comm_info() - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'comm_info_reply', msg_id) @@ -512,7 +513,7 @@ def test_is_complete(): flush_channels() msg_id = KC.is_complete("a = 1") - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'is_complete_reply', msg_id) def test_history_range(): @@ -522,7 +523,7 @@ def test_history_range(): reply_exec = KC.get_shell_msg(timeout=TIMEOUT) msg_id = KC.history(hist_access_type = 'range', raw = True, output = True, start = 1, stop = 2, session = 0) - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'history_reply', msg_id) content = reply['content'] assert len(content['history']) == 1 @@ -534,7 +535,7 @@ def test_history_tail(): reply_exec = KC.get_shell_msg(timeout=TIMEOUT) msg_id = KC.history(hist_access_type = 'tail', raw = True, output = True, n = 1, session = 0) - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'history_reply', msg_id) content = reply['content'] assert len(content['history']) == 1 @@ -546,7 +547,7 @@ def test_history_search(): reply_exec = KC.get_shell_msg(timeout=TIMEOUT) msg_id = KC.history(hist_access_type = 'search', raw = True, output = True, n = 1, pattern = '*', session = 0) - reply = KC.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'history_reply', msg_id) content = reply['content'] assert len(content['history']) == 1 diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index f5d432070..b16d10507 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -6,6 +6,7 @@ import atexit import os import sys +from time import time from contextlib import contextmanager from queue import Empty @@ -52,13 +53,26 @@ def flush_channels(kc=None): validate_message(msg) +def get_reply(kc, msg_id, timeout): + timeout = TIMEOUT + t0 = time() + while True: + reply = kc.get_shell_msg(timeout=timeout) + if reply['parent_header']['msg_id'] == msg_id: + break + t1 = time() + timeout -= t1 - t0 + t0 = t1 + return reply + + def execute(code='', kc=None, **kwargs): """wrapper for doing common steps for validating an execution request""" from .test_message_spec import validate_message if kc is None: kc = KC msg_id = kc.execute(code=code, **kwargs) - reply = kc.get_shell_msg(timeout=TIMEOUT) + reply = get_reply(kc, msg_id, TIMEOUT) validate_message(reply, 'execute_reply', msg_id) busy = kc.get_iopub_msg(timeout=TIMEOUT) validate_message(busy, 'status', msg_id) From 02a25a38620b2a76f5d2a206d7a9c2279649affb Mon Sep 17 00:00:00 2001 From: Jovyan 123 Date: Mon, 11 Jan 2021 10:33:40 -0600 Subject: [PATCH 0435/1195] update changelog --- docs/changelog.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index fe0c3f3fd..a4b495956 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -4,6 +4,10 @@ Changes in IPython kernel 5.4 --- +5.4.3 +***** +- Rework wait_for_ready logic. (:ghpull:`578`) + 5.4.2 ***** - Revert "Fix stop_on_error_timeout blocking other messages in queue". (:ghpull:`570`) From aba2179420a3fa81ee6b8a13f928bf9e5ce50716 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 11 Jan 2021 11:11:34 -0600 Subject: [PATCH 0436/1195] set to dev version --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index adf820552..7f959f69c 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 4, 2) +version_info = (5, 5, 0, 'dev0') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 3e69ce7802558d620c75d3799b0781dc46c27c52 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Tue, 9 Feb 2021 16:51:04 +0100 Subject: [PATCH 0437/1195] Add configure_inline_support and call it in the shell This removes a circular dependency between ipykernel and IPython. --- ipykernel/pylab/backend_inline.py | 52 ++++++++++++++++++++++++++++++- ipykernel/zmqshell.py | 9 ++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index b43509275..f5333d517 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -13,6 +13,7 @@ from matplotlib._pylab_helpers import Gcf from IPython.core.getipython import get_ipython +from IPython.core.pylabtools import select_figure_formats from IPython.display import display from .config import InlineBackend @@ -150,13 +151,62 @@ def flush_figures(): # See https://github.com/matplotlib/matplotlib/pull/1125 FigureCanvas = FigureCanvasAgg + +def configure_inline_support(shell, backend): + """Configure an IPython shell object for matplotlib use. + + Parameters + ---------- + shell : InteractiveShell instance + + backend : matplotlib backend + """ + # If using our svg payload backend, register the post-execution + # function that will pick up the results for display. This can only be + # done with access to the real shell object. + + cfg = InlineBackend.instance(parent=shell) + cfg.shell = shell + if cfg not in shell.configurables: + shell.configurables.append(cfg) + + if backend == 'module://ipykernel.pylab.backend_inline': + shell.events.register('post_execute', flush_figures) + + # Save rcParams that will be overwrittern + shell._saved_rcParams = {} + for k in cfg.rc: + shell._saved_rcParams[k] = matplotlib.rcParams[k] + # load inline_rc + matplotlib.rcParams.update(cfg.rc) + new_backend_name = "inline" + else: + try: + shell.events.unregister('post_execute', flush_figures) + except ValueError: + pass + if hasattr(shell, '_saved_rcParams'): + matplotlib.rcParams.update(shell._saved_rcParams) + del shell._saved_rcParams + new_backend_name = "other" + + # only enable the formats once -> don't change the enabled formats (which the user may + # has changed) when getting another "%matplotlib inline" call. + # See https://github.com/ipython/ipykernel/issues/29 + cur_backend = getattr(configure_inline_support, "current_backend", "unset") + if new_backend_name != cur_backend: + # Setup the default figure format + select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs) + configure_inline_support.current_backend = new_backend_name + + def _enable_matplotlib_integration(): """Enable extra IPython matplotlib integration when we are loaded as the matplotlib backend.""" from matplotlib import get_backend ip = get_ipython() backend = get_backend() if ip and backend == 'module://%s' % __name__: - from IPython.core.pylabtools import configure_inline_support, activate_matplotlib + from IPython.core.pylabtools import activate_matplotlib try: activate_matplotlib(backend) configure_inline_support(ip, backend) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 1e68569d1..8c9cfecce 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -597,6 +597,15 @@ def init_magics(self): self.register_magics(KernelMagics) self.magics_manager.register_alias('ed', 'edit') + def enable_matplotlib(self, gui=None): + gui, backend = super(ZMQInteractiveShell, self).enable_matplotlib(gui) + + from ipykernel.pylab.backend_inline import configure_inline_support + + configure_inline_support(self, backend) + + return gui, backend + def init_virtualenv(self): # Overridden not to do virtualenv detection, because it's probably # not appropriate in a kernel. To use a kernel in a virtualenv, install From c9102ddeb55bd4f39d3b6c8499a3f1a8353edec9 Mon Sep 17 00:00:00 2001 From: Jelle Licht Date: Thu, 18 Feb 2021 21:16:31 +0100 Subject: [PATCH 0438/1195] kernelspec: ensure path is writable before writing kernel.json. --- ipykernel/kernelspec.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 36d3bbcfc..f37f98e19 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -7,6 +7,7 @@ import json import os import shutil +import stat import sys import tempfile @@ -70,6 +71,12 @@ def write_kernel_spec(path=None, overrides=None, extra_arguments=None): # stage resources shutil.copytree(RESOURCES, path) + + # ensure path is writable + mask = os.stat(path).st_mode + if not mask & stat.S_IWUSR: + os.chmod(path, mask | stat.S_IWUSR) + # write kernel.json kernel_dict = get_kernel_dict(extra_arguments) From 82556b9406b96f4327b628f6fe0f1807d6c2b3d6 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 19 Feb 2021 10:20:13 -0600 Subject: [PATCH 0439/1195] add changelog for 5.5 --- docs/changelog.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/changelog.rst b/docs/changelog.rst index a4b495956..ad06796ca 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,6 +1,14 @@ Changes in IPython kernel ========================= +5.5 +--- + +5.5.0 +----- +- Kernelspec: ensure path is writable before writing ``kernel.json``. (:ghpull:`593`) +- Add ``configure_inline_support`` and call it in the shell. (:ghpull:`590`) + 5.4 --- From b53143f48f451276c3c19736c5348636b1658552 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 19 Feb 2021 11:46:22 -0600 Subject: [PATCH 0440/1195] Release 5.5.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7f959f69c..426163978 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 5, 0, 'dev0') +version_info = (5, 5, 0) __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From b020c6c2ccc34aaf0e65bedfc000e64ab50f634e Mon Sep 17 00:00:00 2001 From: "Afshin T. Darian" Date: Mon, 22 Feb 2021 14:35:50 +0000 Subject: [PATCH 0441/1195] Change to markdown for changelog --- docs/changelog.md | 434 +++++++++++++++++++++++++++++++++++++++++ docs/changelog.rst | 436 ------------------------------------------ docs/conf.py | 1 + docs/index.rst | 2 +- docs/requirements.txt | 1 + 5 files changed, 437 insertions(+), 437 deletions(-) create mode 100644 docs/changelog.md delete mode 100644 docs/changelog.rst diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 000000000..0c9a7792e --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,434 @@ +# Changes in IPython kernel + +## 5.5 + +### 5.5.0 +* kernelspec: ensure path is writable before writing `kernel.json`. [#593](https://github.com/ipython/ipykernel/pull/593) +* Add `configure_inline_support` and call it in the shell. [#590](https://github.com/ipython/ipykernel/pull/590) + +## 5.4 + +### 5.4.3 + +* Rework `wait_for_ready` logic. [#578](https://github.com/ipython/ipykernel/pull/578) + +### 5.4.2 + +* Revert \"Fix stop_on_error_timeout blocking other messages in + queue\". [#570](https://github.com/ipython/ipykernel/pull/570) + +### 5.4.1 + +* Invalid syntax in `ipykernel/log.py`. [#567](https://github.com/ipython/ipykernel/pull/567) + +### 5.4.0 + +5.4.0 is generally focused on code quality improvements and tornado +asyncio compatibility. + +* Add github actions, bail on asyncio patch for tornado 6.1. + [#564](https://github.com/ipython/ipykernel/pull/564) +* Start testing on Python 3.9. [#551](https://github.com/ipython/ipykernel/pull/551) +* Fix stack levels for ipykernel\'s deprecation warnings and stop + using some deprecated APIs. [#547](https://github.com/ipython/ipykernel/pull/547) +* Add env parameter to kernel installation [#541](https://github.com/ipython/ipykernel/pull/541) +* Fix stop_on_error_timeout blocking other messages in queue. + [#539](https://github.com/ipython/ipykernel/pull/539) +* Remove most of the python 2 compat code. [#537](https://github.com/ipython/ipykernel/pull/537) +* Remove u-prefix from strings. [#538](https://github.com/ipython/ipykernel/pull/538) + +## 5.3 + +### 5.3.4 + +* Only run Qt eventloop in the shell stream. [#531](https://github.com/ipython/ipykernel/pull/531) + +### 5.3.3 + +* Fix QSocketNotifier in the Qt event loop not being disabled for the + control channel. [#525](https://github.com/ipython/ipykernel/pull/525) + +### 5.3.2 + +* Restore timer based event loop as a Windows-compatible fallback. + [#523](https://github.com/ipython/ipykernel/pull/523) + +### 5.3.1 + +* Fix \#520: run post_execute and post_run_cell on async cells + [#521](https://github.com/ipython/ipykernel/pull/521) +* Fix exception causes in zmqshell.py [#516](https://github.com/ipython/ipykernel/pull/516) +* Make pdb on Windows interruptible [#490](https://github.com/ipython/ipykernel/pull/490) + +### 5.3.0 + +5.3.0 Adds support for Trio event loops and has some bug fixes. + +* Fix ipython display imports [#509](https://github.com/ipython/ipykernel/pull/509) +* Skip test_unc_paths if OS is not Windows [#507](https://github.com/ipython/ipykernel/pull/507) +* Allow interrupting input() on Windows, as part of effort to make pdb + interruptible [#498](https://github.com/ipython/ipykernel/pull/498) +* Add Trio Loop [#479](https://github.com/ipython/ipykernel/pull/479) +* Flush from process even without newline [#478](https://github.com/ipython/ipykernel/pull/478) + +## 5.2 + +### 5.2.1 + +* Handle system commands that use UNC paths on Windows + [#500](https://github.com/ipython/ipykernel/pull/500) +* Add offset argument to seek in io test [#496](https://github.com/ipython/ipykernel/pull/496) + +### 5.2.0 + +5.2.0 Includes several bugfixes and internal logic improvements. + +* Produce better traceback when kernel is interrupted + [#491](https://github.com/ipython/ipykernel/pull/491) +* Add `InProcessKernelClient.control_channel` for compatibility with + jupyter-client v6.0.0 [#489](https://github.com/ipython/ipykernel/pull/489) +* Drop support for Python 3.4 [#483](https://github.com/ipython/ipykernel/pull/483) +* Work around issue related to Tornado with python3.8 on Windows + ([#480](https://github.com/ipython/ipykernel/pull/480), [#481](https://github.com/ipython/ipykernel/pull/481)) +* Prevent entering event loop if it is None [#464](https://github.com/ipython/ipykernel/pull/464) +* Use `shell.input_transformer_manager` when available + [#411](https://github.com/ipython/ipykernel/pull/411) + +## 5.1 + +### 5.1.4 + +5.1.4 Includes a few bugfixes, especially for compatibility with Python +3.8 on Windows. + +* Fix pickle issues when using inline matplotlib backend + [#476](https://github.com/ipython/ipykernel/pull/476) +* Fix an error during kernel shutdown [#463](https://github.com/ipython/ipykernel/pull/463) +* Fix compatibility issues with Python 3.8 ([#456](https://github.com/ipython/ipykernel/pull/456), [#461](https://github.com/ipython/ipykernel/pull/461)) +* Remove some dead code ([#474](https://github.com/ipython/ipykernel/pull/474), + [#467](https://github.com/ipython/ipykernel/pull/467)) + +### 5.1.3 + +5.1.3 Includes several bugfixes and internal logic improvements. + +* Fix comm shutdown behavior by adding a `deleting` option to `close` + which can be set to prevent registering new comm channels during + shutdown ([#433](https://github.com/ipython/ipykernel/pull/433), [#435](https://github.com/ipython/ipykernel/pull/435)) +* Fix `Heartbeat._bind_socket` to return on the first bind ([#431](https://github.com/ipython/ipykernel/pull/431)) +* Moved `InProcessKernelClient.flush` to `DummySocket` ([#437](https://github.com/ipython/ipykernel/pull/437)) +* Don\'t redirect stdout if nose machinery is not present ([#427](https://github.com/ipython/ipykernel/pull/427)) +* Rename `_asyncio.py` to + `_asyncio_utils.py` to avoid name conflicts on Python + 3.6+ ([#426](https://github.com/ipython/ipykernel/pull/426)) +* Only generate kernelspec when installing or building wheel ([#425](https://github.com/ipython/ipykernel/pull/425)) +* Fix priority ordering of control-channel messages in some cases + [#443](https://github.com/ipython/ipykernel/pull/443) + +### 5.1.2 + +5.1.2 fixes some socket-binding race conditions that caused testing +failures in nbconvert. + +* Fix socket-binding race conditions ([#412](https://github.com/ipython/ipykernel/pull/412), + [#419](https://github.com/ipython/ipykernel/pull/419)) +* Add a no-op `flush` method to `DummySocket` and comply with stream + API ([#405](https://github.com/ipython/ipykernel/pull/405)) +* Update kernel version to indicate kernel v5.3 support ([#394](https://github.com/ipython/ipykernel/pull/394)) +* Add testing for upcoming Python 3.8 and PEP 570 positional + parameters ([#396](https://github.com/ipython/ipykernel/pull/396), [#408](https://github.com/ipython/ipykernel/pull/408)) + +### 5.1.1 + +5.1.1 fixes a bug that caused cells to get stuck in a busy state. + +* Flush after sending replies [#390](https://github.com/ipython/ipykernel/pull/390) + +### 5.1.0 + +5.1.0 fixes some important regressions in 5.0, especially on Windows. + +[5.1.0 on GitHub](https://github.com/ipython/ipykernel/milestones/5.1) + +* Fix message-ordering bug that could result in out-of-order + executions, especially on Windows [#356](https://github.com/ipython/ipykernel/pull/356) +* Fix classifiers to indicate dropped Python 2 support + [#354](https://github.com/ipython/ipykernel/pull/354) +* Remove some dead code [#355](https://github.com/ipython/ipykernel/pull/355) +* Support rich-media responses in `inspect_requests` (tooltips) + [#361](https://github.com/ipython/ipykernel/pull/361) + +## 5.0 + +### 5.0.0 + +[5.0.0 on GitHub](https://github.com/ipython/ipykernel/milestones/5.0) + +* Drop support for Python 2. `ipykernel` 5.0 requires Python \>= 3.4 +* Add support for IPython\'s asynchronous code execution + [#323](https://github.com/ipython/ipykernel/pull/323) +* Update release process in `CONTRIBUTING.md` [#339](https://github.com/ipython/ipykernel/pull/339) + +## 4.10 + +[4.10 on GitHub](https://github.com/ipython/ipykernel/milestones/4.10) + +* Fix compatibility with IPython 7.0 [#348](https://github.com/ipython/ipykernel/pull/348) +* Fix compatibility in cases where sys.stdout can be None + [#344](https://github.com/ipython/ipykernel/pull/344) + +## 4.9 + +### 4.9.0 + +[4.9.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.9) + +* Python 3.3 is no longer supported [#336](https://github.com/ipython/ipykernel/pull/336) +* Flush stdout/stderr in KernelApp before replacing + [#314](https://github.com/ipython/ipykernel/pull/314) +* Allow preserving stdout and stderr in KernelApp + [#315](https://github.com/ipython/ipykernel/pull/315) +* Override writable method on OutStream [#316](https://github.com/ipython/ipykernel/pull/316) +* Add metadata to help display matplotlib figures legibly + [#336](https://github.com/ipython/ipykernel/pull/336) + +## 4.8 + +### 4.8.2 + +[4.8.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8.2) + +* Fix compatibility issue with qt eventloop and pyzmq 17 + [#307](https://github.com/ipython/ipykernel/pull/307). + +### 4.8.1 + +[4.8.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8.1) + +* set zmq.ROUTER_HANDOVER socket option when available to workaround + libzmq reconnect bug [#300](https://github.com/ipython/ipykernel/pull/300). +* Fix sdists including absolute paths for kernelspec files, which + prevented installation from sdist on Windows + [#306](https://github.com/ipython/ipykernel/pull/306). + +### 4.8.0 + +[4.8.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8) + +* Cleanly shutdown integrated event loops when shutting down the + kernel. [#290](https://github.com/ipython/ipykernel/pull/290) +* `%gui qt` now uses Qt 5 by default rather than Qt 4, following a + similar change in terminal IPython. [#293](https://github.com/ipython/ipykernel/pull/293) +* Fix event loop integration for `asyncio` when run with Tornado 5, which uses asyncio where + available. [#296](https://github.com/ipython/ipykernel/pull/296) + +## 4.7 + +### 4.7.0 + +[4.7.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.7) + +* Add event loop integration for `asyncio`. +* Use the new IPython completer API. +* Add support for displaying GIF images (mimetype `image/gif`). +* Allow the kernel to be interrupted without killing the Qt console. +* Fix `is_complete` response with cell magics. +* Clean up encoding of bytes objects. +* Clean up help links to use `https` and improve display titles. +* Clean up ioloop handling in preparation for tornado 5. + +## 4.6 + +### 4.6.1 + +[4.6.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.6.1) + +* Fix eventloop-integration bug preventing Qt windows/widgets from + displaying with ipykernel 4.6.0 and IPython ≥ 5.2. +* Avoid deprecation warnings about naive datetimes when working with + jupyter_client ≥ 5.0. + +### 4.6.0 + +[4.6.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.6) + +* Add to API `DisplayPublisher.publish` two new fully + backward-compatible keyword-args: + + > * `update: bool` + > * `transient: dict` + +* Support new `transient` key in + `display_data` messages spec for `publish`. + For a display data message, `transient` contains data + that shouldn\'t be persisted to files or documents. Add a + `display_id` to this `transient` dict by + `display(obj, display_id=\...)` + +* Add `ipykernel_launcher` module which removes the + current working directory from `sys.path` before + launching the kernel. This helps to reduce the cases where the + kernel won\'t start because there\'s a `random.py` (or + similar) module in the current working directory. + +* Add busy/idle messages on IOPub during processing of aborted + requests + +* Add active event loop setting to GUI, which enables the correct + response to IPython\'s `is_event_loop_running_xxx` + +* Include IPython kernelspec in wheels to reduce reliance on \"native + kernel spec\" in jupyter_client + +* Modify `OutStream` to inherit from + `TextIOBase` instead of object to improve API support + and error reporting + +* Fix IPython kernel death messages at start, such as \"Kernel + Restarting\...\" and \"Kernel appears to have died\", when + parent-poller handles PID 1 + +* Various bugfixes + +## 4.5 + +### 4.5.2 + +[4.5.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5.2) + +* Fix bug when instantiating Comms outside of the IPython kernel + (introduced in 4.5.1). + +### 4.5.1 + +[4.5.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5.1) + +* Add missing `stream` parameter to overridden + `getpass` +* Remove locks from iopub thread, which could cause deadlocks during + debugging +* Fix regression where KeyboardInterrupt was treated as an aborted + request, rather than an error +* Allow instantiating Comms outside of the IPython kernel + +### 4.5.0 + +[4.5 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5) + +* Use figure.dpi instead of savefig.dpi to set DPI for inline figures +* Support ipympl matplotlib backend (requires IPython update as well + to fully work) +* Various bugfixes, including fixes for output coming from threads, + and `input` when called with + non-string prompts, which stdlib allows. + +## 4.4 + +### 4.4.1 + +[4.4.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.4.1) + +* Fix circular import of matplotlib on Python 2 caused by the inline + backend changes in 4.4.0. + +### 4.4.0 + +[4.4.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.4) + +* Use + [MPLBACKEND](http://matplotlib.org/devel/coding_guide.html?highlight=mplbackend#developing-a-new-backend) + environment variable to tell matplotlib \>= 1.5 use use the inline + backend by default. This is only done if MPLBACKEND is not already + set and no backend has been explicitly loaded, so setting + `MPLBACKEND=Qt4Agg` or calling `%matplotlib notebook` or + `matplotlib.use('Agg')` will take precedence. +* Fixes for logging problems caused by 4.3, where logging could go to + the terminal instead of the notebook. +* Add `--sys-prefix` and `--profile` arguments to + `ipython kernel install`. +* Allow Comm (Widget) messages to be sent from background threads. +* Select inline matplotlib backend by default if `%matplotlib` magic + or `matplotlib.use()` are not called explicitly (for matplotlib \>= + 1.5). +* Fix some longstanding minor deviations from the message protocol + (missing status: ok in a few replies, connect_reply format). +* Remove calls to NoOpContext from IPython, deprecated in 5.0. + +## 4.3 + +### 4.3.2 + +* Use a nonempty dummy session key for inprocess kernels to avoid + security warnings. + +### 4.3.1 + +* Fix Windows Python 3.5 incompatibility caused by faulthandler patch + in 4.3 + +### 4.3.0 + +[4.3.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.3) + +* Publish all IO in a thread, via `IOPubThread`. This solves the problem of requiring + `sys.stdout.flush` to be called in + the notebook to produce output promptly during long-running cells. +* Remove references to outdated IPython guiref in kernel banner. +* Patch faulthandler to use `sys.__stderr__` instead of forwarded + `sys.stderr`, which has no fileno when forwarded. +* Deprecate some vestiges of the Big Split: + * `ipykernel.find_connection_file` + is deprecated. Use + `jupyter_client.find_connection_file` instead. + + \- Various pieces of code specific to IPython parallel are + deprecated in ipykernel and moved to ipyparallel. + +## 4.2 + +### 4.2.2 + +[4.2.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2.2) + +* Don\'t show interactive debugging info when kernel crashes +* Fix handling of numerical types in json_clean +* Testing fixes for output capturing + +### 4.2.1 + +[4.2.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2.1) + +* Fix default display name back to \"Python X\" instead of \"pythonX\" + +### 4.2.0 + +[4.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2) + +* Support sending a full message in initial opening of comms + (metadata, buffers were not previously allowed) +* When using `ipython kernel install --name` to install the IPython + kernelspec, default display-name to the same value as `--name`. + +## 4.1 + +### 4.1.1 + +[4.1.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.1.1) + +* Fix missing `ipykernel.__version__` on Python 2. +* Fix missing `target_name` when opening comms from the frontend. + +### 4.1.0 + +[4.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.1) + +* add `ipython kernel install` entrypoint for installing the IPython + kernelspec +* provisional implementation of `comm_info` request/reply for msgspec + v5.1 + +## 4.0 + +[4.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.0) + +4.0 is the first release of ipykernel as a standalone package. diff --git a/docs/changelog.rst b/docs/changelog.rst deleted file mode 100644 index ad06796ca..000000000 --- a/docs/changelog.rst +++ /dev/null @@ -1,436 +0,0 @@ -Changes in IPython kernel -========================= - -5.5 ---- - -5.5.0 ------ -- Kernelspec: ensure path is writable before writing ``kernel.json``. (:ghpull:`593`) -- Add ``configure_inline_support`` and call it in the shell. (:ghpull:`590`) - -5.4 ---- - -5.4.3 -***** -- Rework wait_for_ready logic. (:ghpull:`578`) - -5.4.2 -***** -- Revert "Fix stop_on_error_timeout blocking other messages in queue". (:ghpull:`570`) - -5.4.1 -***** -- Invalid syntax in ipykernel/log.py. (:ghpull:`567`) - -5.4.0 -***** - -5.4.0 is generally focused on code quality improvements and tornado asyncio compatibility. - -- Add github actions, bail on asyncio patch for tornado 6.1. (:ghpull:`564`) -- Start testing on Python 3.9. (:ghpull:`551`) -- Fix stack levels for ipykernel's deprecation warnings and stop using some deprecated APIs. (:ghpull:`547`) -- Add env parameter to kernel installation (:ghpull:`541`) -- Fix stop_on_error_timeout blocking other messages in queue. (:ghpull:`539`) -- Remove most of the python 2 compat code. (:ghpull:`537`) -- Remove u-prefix from strings. (:ghpull:`538`) - -5.3 ---- - -5.3.4 -***** -- Only run Qt eventloop in the shell stream. (:ghpull:`531`) - -5.3.3 -***** -- Fix QSocketNotifier in the Qt event loop not being disabled for the control channel. (:ghpull:`525`) - -5.3.2 -***** -- Restore timer based event loop as a Windows-compatible fallback. (:ghpull:`523`) - -5.3.1 -***** - -- Fix #520: run post_execute and post_run_cell on async cells (:ghpull:`521`) -- Fix exception causes in zmqshell.py (:ghpull:`516`) -- Make pdb on Windows interruptible (:ghpull:`490`) - -5.3.0 -***** - -5.3.0 Adds support for Trio event loops and has some bug fixes. - -- Fix ipython display imports (:ghpull:`509`) -- Skip test_unc_paths if OS is not Windows (:ghpull:`507`) -- Allow interrupting input() on Windows, as part of effort to make pdb interruptible (:ghpull:`498`) -- Add Trio Loop (:ghpull:`479`) -- Flush from process even without newline (:ghpull:`478`) - - -5.2 ---- - -5.2.1 -***** - -- Handle system commands that use UNC paths on Windows (:ghpull:`500`) -- Add offset argument to seek in io test (:ghpull:`496`) - -5.2.0 -***** - -5.2.0 Includes several bugfixes and internal logic improvements. - -- Produce better traceback when kernel is interrupted (:ghpull:`491`) -- Add ``InProcessKernelClient.control_channel`` for compatibility with jupyter-client v6.0.0 (:ghpull:`489`) -- Drop support for Python 3.4 (:ghpull:`483`) -- Work around issue related to Tornado with python3.8 on Windows (:ghpull:`480`, :ghpull:`481`) -- Prevent entering event loop if it is None (:ghpull:`464`) -- Use ``shell.input_transformer_manager`` when available (:ghpull:`411`) - -5.1 ---- - -5.1.4 -***** - -5.1.4 Includes a few bugfixes, -especially for compatibility with Python 3.8 on Windows. - -- Fix pickle issues when using inline matplotlib backend (:ghpull:`476`) -- Fix an error during kernel shutdown (:ghpull:`463`) -- Fix compatibility issues with Python 3.8 (:ghpull:`456`, :ghpull:`461`) -- Remove some dead code (:ghpull:`474`, :ghpull:`467`) - -5.1.3 -***** - -5.1.3 Includes several bugfixes and internal logic improvements. - -- Fix comm shutdown behavior by adding a ``deleting`` option to ``close`` which can be set to prevent registering new comm channels during shutdown (:ghpull: `433`, :ghpull: `435`) -- Fix ``Heartbeat._bind_socket`` to return on the first bind (:ghpull: `431`) -- Moved ``InProcessKernelClient.flush`` to ``DummySocket`` (:gphull: `437`) -- Don't redirect stdout if nose machinery is not present (:ghpull: `427`) -- Rename `_asyncio.py` to `_asyncio_utils.py` to avoid name conflicts on Python 3.6+ (:ghpull: `426`) -- Only generate kernelspec when installing or building wheel (:ghpull: `425`) -- Fix priority ordering of control-channel messages in some cases (:ghpull:`443`) - - -5.1.2 -***** - -5.1.2 fixes some socket-binding race conditions that caused testing failures in -nbconvert. - -- Fix socket-binding race conditions (:ghpull: `412`, :ghpull: `419`) -- Add a no-op ``flush`` method to ``DummySocket`` and comply with stream API - (:ghpull: `405`) -- Update kernel version to indicate kernel v5.3 support (:ghpull: `394`) -- Add testing for upcoming Python 3.8 and PEP 570 positional parameters (:ghpull: `396`, :ghpull: `408`) - - -5.1.1 -***** -5.1.1 fixes a bug that caused cells to get stuck in a busy state. - -- Flush after sending replies (:ghpull:`390`) - - -5.1.0 -***** - -5.1.0 fixes some important regressions in 5.0, especially on Windows. - -`5.1.0 on GitHub `__ - -- Fix message-ordering bug that could result in out-of-order executions, - especially on Windows (:ghpull:`356`) -- Fix classifiers to indicate dropped Python 2 support (:ghpull:`354`) -- Remove some dead code (:ghpull:`355`) -- Support rich-media responses in ``inspect_requests`` (tooltips) (:ghpull:`361`) - - -5.0 ---- - -5.0.0 -***** - -`5.0.0 on GitHub `__ - -- Drop support for Python 2. ``ipykernel`` 5.0 requires Python >= 3.4 -- Add support for IPython's asynchronous code execution (:ghpull:`323`) -- Update release process in ``CONTRIBUTING.md`` (:ghpull:`339`) - - -4.10 ----- - -`4.10 on GitHub `__ - -- Fix compatibility with IPython 7.0 (:ghpull:`348`) -- Fix compatibility in cases where sys.stdout can be None (:ghpull:`344`) - -4.9 ---- - -4.9.0 -***** - -`4.9.0 on GitHub `__ - -- Python 3.3 is no longer supported (:ghpull:`336`) -- Flush stdout/stderr in KernelApp before replacing (:ghpull:`314`) -- Allow preserving stdout and stderr in KernelApp (:ghpull:`315`) -- Override writable method on OutStream (:ghpull:`316`) -- Add metadata to help display matplotlib figures legibly (:ghpull:`336`) - - -4.8 ---- - -4.8.2 -***** - -`4.8.2 on GitHub `__ - -- Fix compatibility issue with qt eventloop and pyzmq 17 (:ghpull:`307`). - -4.8.1 -***** - -`4.8.1 on GitHub `__ - -- set zmq.ROUTER_HANDOVER socket option when available - to workaround libzmq reconnect bug (:ghpull:`300`). -- Fix sdists including absolute paths for kernelspec files, - which prevented installation from sdist on Windows - (:ghpull:`306`). - -4.8.0 -***** - -`4.8.0 on GitHub `__ - -- Cleanly shutdown integrated event loops when shutting down the kernel. - (:ghpull:`290`) -- ``%gui qt`` now uses Qt 5 by default rather than Qt 4, following a similar - change in terminal IPython. (:ghpull:`293`) -- Fix event loop integration for :mod:`asyncio` when run with Tornado 5, - which uses asyncio where available. (:ghpull:`296`) - -4.7 ---- - -4.7.0 -***** - -`4.7.0 on GitHub `__ - -- Add event loop integration for :mod:`asyncio`. -- Use the new IPython completer API. -- Add support for displaying GIF images (mimetype ``image/gif``). -- Allow the kernel to be interrupted without killing the Qt console. -- Fix ``is_complete`` response with cell magics. -- Clean up encoding of bytes objects. -- Clean up help links to use ``https`` and improve display titles. -- Clean up ioloop handling in preparation for tornado 5. - - -4.6 ---- - -4.6.1 -***** - -`4.6.1 on GitHub `__ - -- Fix eventloop-integration bug preventing Qt windows/widgets from displaying with ipykernel 4.6.0 and IPython ≥ 5.2. -- Avoid deprecation warnings about naive datetimes when working with jupyter_client ≥ 5.0. - - -4.6.0 -***** - -`4.6.0 on GitHub `__ - -- Add to API `DisplayPublisher.publish` two new fully backward-compatible - keyword-args: - - - `update: bool` - - `transient: dict` - -- Support new `transient` key in `display_data` messages spec for `publish`. - For a display data message, `transient` contains data that shouldn't be - persisted to files or documents. Add a `display_id` to this `transient` - dict by `display(obj, display_id=...)` -- Add `ipykernel_launcher` module which removes the current working directory - from `sys.path` before launching the kernel. This helps to reduce the cases - where the kernel won't start because there's a `random.py` (or similar) - module in the current working directory. -- Add busy/idle messages on IOPub during processing of aborted requests -- Add active event loop setting to GUI, which enables the correct response - to IPython's `is_event_loop_running_xxx` -- Include IPython kernelspec in wheels to reduce reliance on "native kernel - spec" in jupyter_client -- Modify `OutStream` to inherit from `TextIOBase` instead of object to improve - API support and error reporting -- Fix IPython kernel death messages at start, such as "Kernel Restarting..." - and "Kernel appears to have died", when parent-poller handles PID 1 -- Various bugfixes - - -4.5 ---- - -4.5.2 -***** - -`4.5.2 on GitHub `__ - -- Fix bug when instantiating Comms outside of the IPython kernel (introduced in 4.5.1). - - -4.5.1 -***** - -`4.5.1 on GitHub `__ - -- Add missing ``stream`` parameter to overridden :func:`getpass` -- Remove locks from iopub thread, which could cause deadlocks during debugging -- Fix regression where KeyboardInterrupt was treated as an aborted request, rather than an error -- Allow instantiating Comms outside of the IPython kernel - -4.5.0 -***** - -`4.5 on GitHub `__ - -- Use figure.dpi instead of savefig.dpi to set DPI for inline figures -- Support ipympl matplotlib backend (requires IPython update as well to fully work) -- Various bugfixes, including fixes for output coming from threads, - and :func:`input` when called with non-string prompts, which stdlib allows. - - -4.4 ---- - -4.4.1 -***** - -`4.4.1 on GitHub `__ - -- Fix circular import of matplotlib on Python 2 caused by the inline backend changes in 4.4.0. - - -4.4.0 -***** - -`4.4.0 on GitHub `__ - -- Use `MPLBACKEND`_ environment variable to tell matplotlib >= 1.5 use use the inline backend by default. - This is only done if MPLBACKEND is not already set and no backend has been explicitly loaded, - so setting ``MPLBACKEND=Qt4Agg`` or calling ``%matplotlib notebook`` or ``matplotlib.use('Agg')`` - will take precedence. -- Fixes for logging problems caused by 4.3, - where logging could go to the terminal instead of the notebook. -- Add ``--sys-prefix`` and ``--profile`` arguments to :command:`ipython kernel install` -- Allow Comm (Widget) messages to be sent from background threads. -- Select inline matplotlib backend by default if ``%matplotlib`` magic or - ``matplotlib.use()`` are not called explicitly (for matplotlib >= 1.5). -- Fix some longstanding minor deviations from the message protocol - (missing status: ok in a few replies, connect_reply format). -- Remove calls to NoOpContext from IPython, deprecated in 5.0. - -.. _MPLBACKEND: http://matplotlib.org/devel/coding_guide.html?highlight=mplbackend#developing-a-new-backend - - -4.3 ---- - -4.3.2 -***** - -- Use a nonempty dummy session key for inprocess kernels to avoid security - warnings. - -4.3.1 -***** - -- Fix Windows Python 3.5 incompatibility caused by faulthandler patch in 4.3 - -4.3.0 -***** - -`4.3.0 on GitHub `__ - -- Publish all IO in a thread, via :class:`IOPubThread`. - This solves the problem of requiring :meth:`sys.stdout.flush` to be called in the notebook to produce output promptly during long-running cells. -- Remove references to outdated IPython guiref in kernel banner. -- Patch faulthandler to use ``sys.__stderr__`` instead of forwarded ``sys.stderr``, - which has no fileno when forwarded. -- Deprecate some vestiges of the Big Split: - - :func:`ipykernel.find_connection_file` is deprecated. Use :func:`jupyter_client.find_connection_file` instead. - - Various pieces of code specific to IPython parallel are deprecated in ipykernel - and moved to ipyparallel. - - -4.2 ---- - -4.2.2 -***** - -`4.2.2 on GitHub `__ - -- Don't show interactive debugging info when kernel crashes -- Fix handling of numerical types in json_clean -- Testing fixes for output capturing - -4.2.1 -***** - -`4.2.1 on GitHub `__ - -- Fix default display name back to "Python X" instead of "pythonX" - -4.2.0 -***** - -`4.2 on GitHub `_ - -- Support sending a full message in initial opening of comms (metadata, buffers were not previously allowed) -- When using ``ipython kernel install --name`` to install the IPython kernelspec, default display-name to the same value as ``--name``. - -4.1 ---- - -4.1.1 -***** - -`4.1.1 on GitHub `_ - -- Fix missing ``ipykernel.__version__`` on Python 2. -- Fix missing ``target_name`` when opening comms from the frontend. - -4.1.0 -***** - -`4.1 on GitHub `_ - - -- add ``ipython kernel install`` entrypoint for installing the IPython - kernelspec -- provisional implementation of ``comm_info`` request/reply for msgspec - v5.1 - -4.0 ---- - -`4.0 on GitHub `_ - -4.0 is the first release of ipykernel as a standalone package. diff --git a/docs/conf.py b/docs/conf.py index 74dcf4d48..eefd27475 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -30,6 +30,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ + 'myst_parser', 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinxcontrib_github_alt', diff --git a/docs/index.rst b/docs/index.rst index 770904d7e..c83dff2be 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,7 +11,7 @@ Contents: .. toctree:: :maxdepth: 2 - changelog.rst + changelog Indices and tables diff --git a/docs/requirements.txt b/docs/requirements.txt index 623e487ab..3026b0bb2 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1 +1,2 @@ sphinxcontrib_github_alt +myst_parser From ba43397383669218a33a0df717681c9a92eae914 Mon Sep 17 00:00:00 2001 From: "Afshin T. Darian" Date: Mon, 22 Feb 2021 14:43:06 +0000 Subject: [PATCH 0442/1195] Clean up release process and add tests --- .github/workflows/ci.yml | 5 +++++ RELEASE.md | 4 ++-- ipykernel/_version.py | 2 +- pyproject.toml | 1 + setup.py | 7 ++++++- 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f3457aa2..7c4898c6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,11 @@ jobs: timeout-minutes: 30 run: | pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 + - name: Build and check the dist files + run: | + pip install build twine + python -m build . + twine check dist/* - name: Coverage run: | codecov diff --git a/RELEASE.md b/RELEASE.md index 8054f8bfd..aacb2ae2c 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -11,8 +11,8 @@ git tag $version; true; git push --all git push --tags rm -rf dist build -python setup.py sdist -python setup.py bdist_wheel +pip install build twine +python -m build . pip install twine twine check dist/* twine upload dist/* diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 426163978..7b5660bea 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 5, 0) +version_info = (5, 6, 0, 'dev0') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools diff --git a/pyproject.toml b/pyproject.toml index e220b587e..36d4cabc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,4 +1,5 @@ [build-system] +build-backend = "setuptools.build_meta" requires=[ "setuptools", "wheel", diff --git a/setup.py b/setup.py index d9f51c2c7..106eceaa6 100644 --- a/setup.py +++ b/setup.py @@ -65,6 +65,9 @@ def run(self): raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % current_version) +with open(pjoin(here, 'README.md')) as fid: + LONG_DESCRIPTION = fid.read() + setup_args = dict( name=name, version=current_version, @@ -76,11 +79,12 @@ def run(self): py_modules=['ipykernel_launcher'], package_data=package_data, description="IPython Kernel for Jupyter", + long_description_content_type="text/markdown", author='IPython Development Team', author_email='ipython-dev@scipy.org', url='https://ipython.org', license='BSD', - long_description="The IPython kernel for Jupyter", + long_description=LONG_DESCRIPTION, platforms="Linux, Mac OS X, Windows", keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], python_requires='>=3.5', @@ -112,6 +116,7 @@ def run(self): if any(a.startswith(('bdist', 'install')) for a in sys.argv): + sys.path.insert(0, here) from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME # When building a wheel, the executable specified in the kernelspec is simply 'python'. From d2e306ed69f3d33891eaa673639306adce5ebb13 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Wed, 27 Jan 2021 23:30:07 +0100 Subject: [PATCH 0443/1195] Run control channel in separate thread --- ipykernel/control.py | 21 +++++++++ ipykernel/eventloops.py | 41 +++++++--------- ipykernel/inprocess/client.py | 5 +- ipykernel/inprocess/ipkernel.py | 4 +- ipykernel/kernelapp.py | 24 +++++++--- ipykernel/kernelbase.py | 84 +++++++++++++-------------------- 6 files changed, 91 insertions(+), 88 deletions(-) create mode 100644 ipykernel/control.py diff --git a/ipykernel/control.py b/ipykernel/control.py new file mode 100644 index 000000000..d06d9ea34 --- /dev/null +++ b/ipykernel/control.py @@ -0,0 +1,21 @@ + +from threading import Thread +import zmq +if zmq.pyzmq_version_info() >= (17, 0): + from tornado.ioloop import IOLoop +else: + # deprecated since pyzmq 17 + from zmq.eventloop.ioloop import IOLoop + + +class ControlThread(Thread): + + def __init__(self, context): + Thread.__init__(self) + self.context = context + self.io_loop = IOLoop(make_current=False) + + def run(self): + self.io_loop.make_current() + self.io_loop.start() + self.io_loop.close(all_fds=True) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 087fad7fc..a71121bdd 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -114,11 +114,7 @@ def loop_qt4(kernel): kernel.app = get_app_qt4([" "]) kernel.app.setQuitOnLastWindowClosed(False) - - # Only register the eventloop for the shell stream because doing - # it for the control stream is generating a bunch of unnecessary - # warnings on Windows. - _notify_stream_qt(kernel, kernel.shell_streams[0]) + _notify_stream_qt(kernel, kernel.shell_stream) _loop_qt(kernel.app) @@ -160,10 +156,9 @@ def loop_wx(kernel): def wake(): """wake from wx""" - for stream in kernel.shell_streams: - if stream.flush(limit=1): - kernel.app.ExitMainLoop() - return + if kernel.shell_stream.flush(limit=1): + kernel.app.ExitMainLoop() + return # We have to put the wx.Timer in a wx.Frame for it to fire properly. # We make the Frame hidden when we create it in the main app below. @@ -237,13 +232,12 @@ def process_stream_events(stream, *a, **kw): # For Tkinter, we create a Tk object and call its withdraw method. kernel.app_wrapper = BasicAppWrapper(app) - for stream in kernel.shell_streams: - notifier = partial(process_stream_events, stream) - # seems to be needed for tk - notifier.__name__ = "notifier" - app.tk.createfilehandler(stream.getsockopt(zmq.FD), READABLE, notifier) - # schedule initial call after start - app.after(0, notifier) + notifier = partial(process_stream_events, shell_stream) + # seems to be needed for tk + notifier.__name__ = "notifier" + app.tk.createfilehandler(shell_stream.getsockopt(zmq.FD), READABLE, notifier) + # schedule initial call after start + app.after(0, notifier) app.mainloop() @@ -330,10 +324,9 @@ def handle_int(etype, value, tb): # don't let interrupts during mainloop invoke crash_handler: sys.excepthook = handle_int mainloop(kernel._poll_interval) - for stream in kernel.shell_streams: - if stream.flush(limit=1): - # events to process, return control to kernel - return + if kernel_shell_stream.flush(limit=1): + # events to process, return control to kernel + return except: raise except KeyboardInterrupt: @@ -371,11 +364,9 @@ def process_stream_events(stream): if stream.flush(limit=1): loop.stop() - for stream in kernel.shell_streams: - fd = stream.getsockopt(zmq.FD) - notifier = partial(process_stream_events, stream) - loop.add_reader(fd, notifier) - loop.call_soon(notifier) + notifier = partial(process_stream_events, shell_stream) + loop.add_reader(shell_stream.getsockopt(zmq.FD), notifier) + loop.call_soon(notifier) while True: error = None diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 5de4f774d..e6b3b358a 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -12,7 +12,6 @@ #----------------------------------------------------------------------------- # IPython imports -from ipykernel.inprocess.socket import DummySocket from traitlets import Type, Instance, default from jupyter_client.clientabc import KernelClientABC from jupyter_client.client import KernelClient @@ -171,10 +170,10 @@ def _dispatch_to_kernel(self, msg): if kernel is None: raise RuntimeError('Cannot send request. No kernel exists.') - stream = DummySocket() + stream = kernel.shell_stream self.session.send(stream, msg) msg_parts = stream.recv_multipart() - kernel.dispatch_shell(stream, msg_parts) + kernel.dispatch_shell(msg_parts) idents, reply_msg = self.session.recv(stream, copy=False) self.shell_channel.call_handlers_later(reply_msg) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 3b51c8e5b..dd1bc6581 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -49,11 +49,11 @@ class InProcessKernel(IPythonKernel): #------------------------------------------------------------------------- shell_class = Type(allow_none=True) - shell_streams = List() - control_stream = Any() _underlying_iopub_socket = Instance(DummySocket, ()) iopub_thread = Instance(IOPubThread) + shell_stream = Instance(DummySocket, ()) + @default('iopub_thread') def _default_iopub_thread(self): thread = IOPubThread(self._underlying_iopub_socket) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index ee691508e..6bda6f29a 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -36,6 +36,7 @@ # local imports from .iostream import IOPubThread +from .control import ControlThread from .heartbeat import Heartbeat from .ipkernel import IPythonKernel from .parentpoller import ParentPollerUnix, ParentPollerWindows @@ -124,6 +125,7 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, stdin_socket = Any() iopub_socket = Any() iopub_thread = Any() + control_thread = Any() ports = Dict() @@ -276,6 +278,17 @@ def init_sockets(self): self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port) self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port) + if hasattr(zmq, 'ROUTER_HANDOVER'): + # set router-handover to workaround zeromq reconnect problems + # in certain rare circumstances + # see ipython/ipykernel#270 and zeromq/libzmq#2892 + self.shell_socket.router_handover = \ + self.stdin_socket.router_handover = 1 + + self.init_control(context) + self.init_iopub(context) + + def init_control(self, context): self.control_socket = context.socket(zmq.ROUTER) self.control_socket.linger = 1000 self.control_port = self._bind_socket(self.control_socket, self.control_port) @@ -285,11 +298,10 @@ def init_sockets(self): # set router-handover to workaround zeromq reconnect problems # in certain rare circumstances # see ipython/ipykernel#270 and zeromq/libzmq#2892 - self.shell_socket.router_handover = \ - self.control_socket.router_handover = \ - self.stdin_socket.router_handover = 1 + self.control_socket.router_handover = 1 - self.init_iopub(context) + self.control_thread = ControlThread(self.control_socket) + self.control_thread.start() def init_iopub(self, context): self.iopub_socket = context.socket(zmq.PUB) @@ -437,13 +449,13 @@ def init_signal(self): def init_kernel(self): """Create the Kernel object itself""" shell_stream = ZMQStream(self.shell_socket) - control_stream = ZMQStream(self.control_socket) + control_stream = ZMQStream(self.control_socket, self.control_thread.io_loop) kernel_factory = self.kernel_class.instance kernel = kernel_factory(parent=self, session=self.session, control_stream=control_stream, - shell_streams=[shell_stream, control_stream], + shell_stream=shell_stream, iopub_thread=self.iopub_thread, iopub_socket=self.iopub_socket, stdin_socket=self.stdin_socket, diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index aba3223a9..67dd939fe 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -21,7 +21,7 @@ from tornado import ioloop from tornado import gen -from tornado.queues import PriorityQueue, QueueEmpty +from tornado.queues import Queue, QueueEmpty import zmq from zmq.eventloop.zmqstream import ZMQStream @@ -38,9 +38,6 @@ from ._version import kernel_protocol_version -CONTROL_PRIORITY = 1 -SHELL_PRIORITY = 10 - class Kernel(SingletonConfigurable): @@ -60,7 +57,7 @@ def _update_eventloop(self, change): session = Instance(Session, allow_none=True) profile_dir = Instance('IPython.core.profiledir.ProfileDir', allow_none=True) - shell_streams = List() + shell_stream = Instance(ZMQStream, allow_none=True) control_stream = Instance(ZMQStream, allow_none=True) iopub_socket = Any() iopub_thread = Any() @@ -215,7 +212,7 @@ def should_handle(self, stream, msg, idents): return True @gen.coroutine - def dispatch_shell(self, stream, msg): + def dispatch_shell(self, msg): """dispatch shell requests""" idents, msg = self.session.feed_identities(msg, copy=False) try: @@ -232,11 +229,11 @@ def dispatch_shell(self, stream, msg): # Only abort execute requests if self._aborting and msg_type == 'execute_request': - self._send_abort_reply(stream, msg, idents) + self._send_abort_reply(self.shell_stream, msg, idents) self._publish_status('idle') # flush to ensure reply is sent before # handling the next request - stream.flush(zmq.POLLOUT) + self.shell_stream.flush(zmq.POLLOUT) return # Print some info about this message and leave a '--->' marker, so it's @@ -245,7 +242,7 @@ def dispatch_shell(self, stream, msg): self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type) self.log.debug(' Content: %s\n --->\n ', msg['content']) - if not self.should_handle(stream, msg, idents): + if not self.should_handle(self.shell_stream, msg, idents): return handler = self.shell_handlers.get(msg_type, None) @@ -258,7 +255,7 @@ def dispatch_shell(self, stream, msg): except Exception: self.log.debug("Unable to signal in pre_handler_hook:", exc_info=True) try: - yield gen.maybe_future(handler(stream, idents, msg)) + yield gen.maybe_future(handler(self.shell_stream, idents, msg)) except Exception: self.log.error("Exception in message handler:", exc_info=True) finally: @@ -272,7 +269,7 @@ def dispatch_shell(self, stream, msg): self._publish_status('idle') # flush to ensure reply is sent before # handling the next request - stream.flush(zmq.POLLOUT) + self.shell_stream.flush(zmq.POLLOUT) def pre_handler_hook(self): """Hook to execute before calling message handler""" @@ -332,27 +329,22 @@ def do_one_iteration(self): .. versionchanged:: 5 This is now a coroutine """ - # flush messages off of shell streams into the message queue - for stream in self.shell_streams: - stream.flush() - # process all messages higher priority than shell (control), - # and at most one shell message per iteration - priority = 0 - while priority is not None and priority < SHELL_PRIORITY: - priority = yield self.process_one(wait=False) + # flush messages off of shell stream into the message queue + self.shell_stream.flush() + # process at most one shell message per iteration + yield self.process_one(wait=False) @gen.coroutine def process_one(self, wait=True): """Process one request - Returns priority of the message handled. Returns None if no message was handled. """ if wait: - priority, t, dispatch, args = yield self.msg_queue.get() + t, dispatch, args = yield self.msg_queue.get() else: try: - priority, t, dispatch, args = self.msg_queue.get_nowait() + t, dispatch, args = self.msg_queue.get_nowait() except QueueEmpty: return None yield gen.maybe_future(dispatch(*args)) @@ -377,21 +369,18 @@ def dispatch_queue(self): _message_counter = Any( help="""Monotonic counter of messages - - Ensures messages of the same priority are handled in arrival order. """, ) @default('_message_counter') def _message_counter_default(self): return itertools.count() - def schedule_dispatch(self, priority, dispatch, *args): + def schedule_dispatch(self, dispatch, *args): """schedule a message for dispatch""" idx = next(self._message_counter) self.msg_queue.put_nowait( ( - priority, idx, dispatch, args, @@ -403,32 +392,24 @@ def schedule_dispatch(self, priority, dispatch, *args): def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() - self.msg_queue = PriorityQueue() + self.msg_queue = Queue() self.io_loop.add_callback(self.dispatch_queue) + self.control_stream.on_recv( + partial( + self.schedule_dispatch, + self.dispatch_control, + ), + copy=False, + ) - if self.control_stream: - self.control_stream.on_recv( - partial( - self.schedule_dispatch, - CONTROL_PRIORITY, - self.dispatch_control, - ), - copy=False, - ) - - for s in self.shell_streams: - if s is self.control_stream: - continue - s.on_recv( - partial( - self.schedule_dispatch, - SHELL_PRIORITY, - self.dispatch_shell, - s, - ), - copy=False, - ) + self.shell_stream.on_recv( + partial( + self.schedule_dispatch, + self.dispatch_shell, + ), + copy=False, + ) # publish idle status self._publish_status('starting') @@ -784,8 +765,7 @@ def _topic(self, topic): @gen.coroutine def _abort_queues(self): - for stream in self.shell_streams: - stream.flush() + self.shell_stream.flush() self._aborting = True def stop_aborting(f): @@ -909,4 +889,4 @@ def _at_shutdown(self): if self._shutdown_message is not None: self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown')) self.log.debug("%s", self._shutdown_message) - [ s.flush(zmq.POLLOUT) for s in self.shell_streams ] + self.shell_stream.flush(zmq.POLLOUT) From 3fe14eecf1c3aadf561db3c387466dc02a9352c4 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 5 Feb 2021 16:48:17 +0100 Subject: [PATCH 0444/1195] Per-channel parent header --- ipykernel/ipkernel.py | 6 +++--- ipykernel/kernelbase.py | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 661627352..62628971a 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -145,11 +145,11 @@ def start(self): self.shell.exit_now = False super(IPythonKernel, self).start() - def set_parent(self, ident, parent): + def set_parent(self, ident, parent, channel='shell'): """Overridden from parent to tell the display hook and output streams about the parent message. """ - super(IPythonKernel, self).set_parent(ident, parent) + super(IPythonKernel, self).set_parent(ident, parent, channel) self.shell.set_parent(parent) def init_metadata(self, parent): @@ -509,7 +509,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): reply_content['engine_info'] = e_info self.send_response(self.iopub_socket, 'error', reply_content, - ident=self._topic('error')) + ident=self._topic('error'), channel='shell') self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback'])) result_buf = [] reply_content['status'] = 'error' diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 67dd939fe..fb74206da 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -90,8 +90,8 @@ def _default_ident(self): # track associations with current request _allow_stdin = Bool(False) - _parent_header = Dict() - _parent_ident = Any(b'') + _parent_header = Dict({'shell': {}, 'control': {}}) + _parent_ident = Dict({'shell': b'', 'control': b''}) # Time to sleep after flushing the stdout/err buffers in each execute # cycle. While this introduces a hard limit on the minimal latency of the # execute cycle, it helps prevent output synchronization problems for @@ -176,7 +176,7 @@ def dispatch_control(self, msg): self.log.debug("Control received: %s", msg) # Set the parent message for side effects. - self.set_parent(idents, msg) + self.set_parent(idents, msg, channel='control') self._publish_status('busy') header = msg['header'] @@ -222,7 +222,7 @@ def dispatch_shell(self, msg): return # Set the parent message for side effects. - self.set_parent(idents, msg) + self.set_parent(idents, msg, channel='shell') self._publish_status('busy') msg_type = msg['header']['msg_type'] @@ -440,11 +440,11 @@ def _publish_status(self, status, parent=None): self.session.send(self.iopub_socket, 'status', {'execution_state': status}, - parent=parent or self._parent_header, + parent=parent or self._parent_header['shell'], ident=self._topic('status'), ) - def set_parent(self, ident, parent): + def set_parent(self, ident, parent, channel='shell'): """Set the current parent_header Side effects (IOPub messages) and replies are associated with @@ -453,11 +453,11 @@ def set_parent(self, ident, parent): The parent identity is used to route input_request messages on the stdin channel. """ - self._parent_ident = ident - self._parent_header = parent + self._parent_ident[channel] = ident + self._parent_header[channel] = parent def send_response(self, stream, msg_or_type, content=None, ident=None, - buffers=None, track=False, header=None, metadata=None): + buffers=None, track=False, header=None, metadata=None, channel='shell'): """Send a response to the message we're currently processing. This accepts all the parameters of :meth:`jupyter_client.session.Session.send` @@ -466,7 +466,7 @@ def send_response(self, stream, msg_or_type, content=None, ident=None, This relies on :meth:`set_parent` having been called for the current message. """ - return self.session.send(stream, msg_or_type, content, self._parent_header, + return self.session.send(stream, msg_or_type, content, self._parent_header[channel], ident, buffers, track, header, metadata) def init_metadata(self, parent): @@ -809,8 +809,8 @@ def getpass(self, prompt='', stream=None): warnings.warn("The `stream` parameter of `getpass.getpass` will have no effect when using ipykernel", UserWarning, stacklevel=2) return self._input_request(prompt, - self._parent_ident, - self._parent_header, + self._parent_ident['shell'], + self._parent_header['shell'], password=True, ) @@ -826,8 +826,8 @@ def raw_input(self, prompt=''): "raw_input was called, but this frontend does not support input requests." ) return self._input_request(str(prompt), - self._parent_ident, - self._parent_header, + self._parent_ident['shell'], + self._parent_header['shell'], password=False, ) From fe0999999cb992d8088de6ca577932021993ff52 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 5 Feb 2021 16:56:39 +0100 Subject: [PATCH 0445/1195] Add deprecated property shell_streams --- ipykernel/kernelbase.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index fb74206da..d61937556 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -11,6 +11,7 @@ import sys import time import uuid +import warnings try: # jupyter_client >= 5, use tz-aware now @@ -58,6 +59,15 @@ def _update_eventloop(self, change): session = Instance(Session, allow_none=True) profile_dir = Instance('IPython.core.profiledir.ProfileDir', allow_none=True) shell_stream = Instance(ZMQStream, allow_none=True) + + @property + def shell_streams(self): + warnings.warn( + 'Property shell_streams is deprecated in favor of shell_stream', + DeprecationWarning + ) + return [shell_stream] + control_stream = Instance(ZMQStream, allow_none=True) iopub_socket = Any() iopub_thread = Any() From c46dca1765f24c361461170f15b136a0312fe395 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 5 Feb 2021 22:20:48 +0100 Subject: [PATCH 0446/1195] Make ControlThread daemonic --- ipykernel/control.py | 5 ++--- ipykernel/kernelapp.py | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index d06d9ea34..9220dffba 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -10,9 +10,8 @@ class ControlThread(Thread): - def __init__(self, context): - Thread.__init__(self) - self.context = context + def __init__(self, **kwargs): + Thread.__init__(self, **kwargs) self.io_loop = IOLoop(make_current=False) def run(self): diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 6bda6f29a..da8f5598d 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -300,7 +300,7 @@ def init_control(self, context): # see ipython/ipykernel#270 and zeromq/libzmq#2892 self.control_socket.router_handover = 1 - self.control_thread = ControlThread(self.control_socket) + self.control_thread = ControlThread(daemon=True) self.control_thread.start() def init_iopub(self, context): From deeca80266f0ae5087196348e1b5cd504c1f60f1 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Sat, 6 Feb 2021 16:49:12 +0100 Subject: [PATCH 0447/1195] Attempt fixing shutdown --- ipykernel/control.py | 1 - ipykernel/kernelbase.py | 14 +++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index 9220dffba..10853046d 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -1,4 +1,3 @@ - from threading import Thread import zmq if zmq.pyzmq_version_info() >= (17, 0): diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index d61937556..38b12e82d 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -672,9 +672,17 @@ def shutdown_request(self, stream, ident, parent): ) self._at_shutdown() - # call sys.exit after a short delay - loop = ioloop.IOLoop.current() - loop.add_timeout(time.time()+0.1, loop.stop) + + # Flush to ensure reply is sent before stopping loop + self.control_stream.flush(zmq.POLLOUT) + + self.log.debug('Stopping control ioloop') + control_io_loop = self.control_stream.io_loop + control_io_loop.add_callback(control_io_loop.stop) + + self.log.debug('Stopping shell ioloop') + shell_io_loop = self.shell_stream.io_loop + shell_io_loop.add_callback(shell_io_loop.stop) def do_shutdown(self, restart): """Override in subclasses to do things when the frontend shuts down the From 03bc0e3d592e2c0f960452e6d91ae32469997824 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Mon, 8 Feb 2021 12:16:31 +0100 Subject: [PATCH 0448/1195] Fix control dispatch --- ipykernel/kernelbase.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 38b12e82d..c21e3cc1e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -405,13 +405,7 @@ def start(self): self.msg_queue = Queue() self.io_loop.add_callback(self.dispatch_queue) - self.control_stream.on_recv( - partial( - self.schedule_dispatch, - self.dispatch_control, - ), - copy=False, - ) + self.control_stream.on_recv(self.dispatch_control, copy=False) self.shell_stream.on_recv( partial( From fefec0d03b18a336355d1169aa6712c2a79f5077 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 10 Feb 2021 11:47:27 +0100 Subject: [PATCH 0449/1195] Use WeakSet to close all event pipes leaving an event_pipe in a thread that doesn't exit results in ctx.term() hanging --- ipykernel/iostream.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 4d1e6c95d..486e80e27 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -11,6 +11,7 @@ import sys import threading import warnings +from weakref import WeakSet from io import StringIO, TextIOBase import zmq @@ -66,6 +67,7 @@ def __init__(self, socket, pipe=False): self._setup_pipe_in() self._local = threading.local() self._events = deque() + self._event_pipes = WeakSet() self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main) self.thread.daemon = True @@ -100,6 +102,9 @@ def _event_pipe(self): event_pipe.linger = 0 event_pipe.connect(self._event_interface) self._local.event_pipe = event_pipe + # WeakSet so that event pipes will be closed by garbage collection + # when their threads are terminated + self._event_pipes.add(event_pipe) return event_pipe def _handle_event(self, msg): @@ -179,8 +184,11 @@ def stop(self): return self.io_loop.add_callback(self.io_loop.stop) self.thread.join() - if hasattr(self._local, 'event_pipe'): - self._local.event_pipe.close() + # close *all* event pipes, created in any thread + # event pipes can only be used from other threads while self.thread.is_alive() + # so after thread.join, this should be safe + for event_pipe in self._event_pipes: + event_pipe.close() def close(self): if self.closed: From d9500ce7ab91b95018de2a4a28e4bd105a7d98dc Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Wed, 10 Feb 2021 17:25:40 +0100 Subject: [PATCH 0450/1195] Remove flush --- ipykernel/kernelbase.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index c21e3cc1e..1ee2dcca0 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -366,12 +366,7 @@ def dispatch_queue(self): Ensures that only one message is processing at a time, even when the handler is async """ - while True: - # ensure control stream is flushed before processing shell messages - if self.control_stream: - self.control_stream.flush() - # receive the next message and handle it try: yield self.process_one() except Exception: @@ -667,9 +662,6 @@ def shutdown_request(self, stream, ident, parent): self._at_shutdown() - # Flush to ensure reply is sent before stopping loop - self.control_stream.flush(zmq.POLLOUT) - self.log.debug('Stopping control ioloop') control_io_loop = self.control_stream.io_loop control_io_loop.add_callback(control_io_loop.stop) From 132e8cf9185e2e8b0303fa3ece12a829811cce5b Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Thu, 11 Feb 2021 15:54:29 +0100 Subject: [PATCH 0451/1195] Start control thread after creating the control stream --- ipykernel/kernelapp.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index da8f5598d..d99c7ab9f 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -289,6 +289,7 @@ def init_sockets(self): self.init_iopub(context) def init_control(self, context): + self.log.debug('IN INIT_CONTROL') self.control_socket = context.socket(zmq.ROUTER) self.control_socket.linger = 1000 self.control_port = self._bind_socket(self.control_socket, self.control_port) @@ -301,7 +302,6 @@ def init_control(self, context): self.control_socket.router_handover = 1 self.control_thread = ControlThread(daemon=True) - self.control_thread.start() def init_iopub(self, context): self.iopub_socket = context.socket(zmq.PUB) @@ -448,9 +448,10 @@ def init_signal(self): def init_kernel(self): """Create the Kernel object itself""" + self.log.debug('IN INIT_KERNEL') shell_stream = ZMQStream(self.shell_socket) control_stream = ZMQStream(self.control_socket, self.control_thread.io_loop) - + self.control_thread.start() kernel_factory = self.kernel_class.instance kernel = kernel_factory(parent=self, session=self.session, @@ -569,6 +570,7 @@ def init_pdb(self): def initialize(self, argv=None): self._init_asyncio_patch() super(IPKernelApp, self).initialize(argv) + self.log.debug('IN INITIALIZE') if self.subapp is not None: return @@ -605,6 +607,7 @@ def initialize(self, argv=None): sys.stderr.flush() def start(self): + self.log.debug('IN START') if self.subapp is not None: return self.subapp.start() if self.poller is not None: From 326ea899d7ac7e5252a052dcae433c01a022c5c4 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 12 Feb 2021 11:33:28 +0100 Subject: [PATCH 0452/1195] Added stream parameter to _publish_status method --- ipykernel/kernelbase.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 1ee2dcca0..4d26a73a7 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -187,7 +187,7 @@ def dispatch_control(self, msg): # Set the parent message for side effects. self.set_parent(idents, msg, channel='control') - self._publish_status('busy') + self._publish_status('busy', 'control') header = msg['header'] msg_type = header['msg_type'] @@ -203,7 +203,7 @@ def dispatch_control(self, msg): sys.stdout.flush() sys.stderr.flush() - self._publish_status('idle') + self._publish_status('idle', 'control') # flush to ensure reply is sent self.control_stream.flush(zmq.POLLOUT) @@ -233,14 +233,14 @@ def dispatch_shell(self, msg): # Set the parent message for side effects. self.set_parent(idents, msg, channel='shell') - self._publish_status('busy') + self._publish_status('busy', 'shell') msg_type = msg['header']['msg_type'] # Only abort execute requests if self._aborting and msg_type == 'execute_request': self._send_abort_reply(self.shell_stream, msg, idents) - self._publish_status('idle') + self._publish_status('idle', 'shell') # flush to ensure reply is sent before # handling the next request self.shell_stream.flush(zmq.POLLOUT) @@ -276,7 +276,7 @@ def dispatch_shell(self, msg): sys.stdout.flush() sys.stderr.flush() - self._publish_status('idle') + self._publish_status('idle', 'shell') # flush to ensure reply is sent before # handling the next request self.shell_stream.flush(zmq.POLLOUT) @@ -411,7 +411,7 @@ def start(self): ) # publish idle status - self._publish_status('starting') + self._publish_status('starting', 'shell') def record_ports(self, ports): @@ -434,12 +434,12 @@ def _publish_execute_input(self, code, parent, execution_count): parent=parent, ident=self._topic('execute_input') ) - def _publish_status(self, status, parent=None): + def _publish_status(self, status, channel, parent=None): """send status (busy/idle) on IOPub""" self.session.send(self.iopub_socket, 'status', {'execution_state': status}, - parent=parent or self._parent_header['shell'], + parent=parent or self._parent_header[channel], ident=self._topic('status'), ) From c03e3436b23314652ac43cf0790b2c9d6835fb35 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Tue, 23 Feb 2021 22:49:52 +0100 Subject: [PATCH 0453/1195] Remove debug logs --- ipykernel/kernelapp.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index d99c7ab9f..c7756fa2c 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -289,7 +289,6 @@ def init_sockets(self): self.init_iopub(context) def init_control(self, context): - self.log.debug('IN INIT_CONTROL') self.control_socket = context.socket(zmq.ROUTER) self.control_socket.linger = 1000 self.control_port = self._bind_socket(self.control_socket, self.control_port) @@ -448,7 +447,6 @@ def init_signal(self): def init_kernel(self): """Create the Kernel object itself""" - self.log.debug('IN INIT_KERNEL') shell_stream = ZMQStream(self.shell_socket) control_stream = ZMQStream(self.control_socket, self.control_thread.io_loop) self.control_thread.start() @@ -570,7 +568,6 @@ def init_pdb(self): def initialize(self, argv=None): self._init_asyncio_patch() super(IPKernelApp, self).initialize(argv) - self.log.debug('IN INITIALIZE') if self.subapp is not None: return @@ -607,7 +604,6 @@ def initialize(self, argv=None): sys.stderr.flush() def start(self): - self.log.debug('IN START') if self.subapp is not None: return self.subapp.start() if self.poller is not None: From 208ad24669be43610da964b8c5ab074a3b14c137 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Wed, 24 Feb 2021 10:03:37 +0100 Subject: [PATCH 0454/1195] Change version to 6.0.0dev0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7b5660bea..b4c4984ae 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (5, 6, 0, 'dev0') +version_info = (6, 0, 0, 'dev0') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 9bce1fa1a7b302d145c86d5fb0e34e3d7c16f6bf Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 9 Feb 2021 22:30:34 +0100 Subject: [PATCH 0455/1195] Debugger implementation --- ipykernel/debugger.py | 287 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 ipykernel/debugger.py diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py new file mode 100644 index 000000000..32a785390 --- /dev/null +++ b/ipykernel/debugger.py @@ -0,0 +1,287 @@ +import logging +import os + +from zmq.utils import jsonapi +from traitlets import Instance + +from asyncio import Queue + +class DebugpyMessageQueue: + + HEADER = 'Content Length: ' + HEADER_LENGTH = 16 + SEPARATOR = '\r\n\r\n' + SEPARATOR_LENGTH = 4 + + def __init__(self, event_callback): + self.tcp_buffer = '' + self._reset_tcp_pos() + self.event_callback = event_callback + self.message_queue = Queue + + def _reset_tcp_pos(self): + self.header_pos = -1 + self.separator_pos = -1 + self.message_size = 0 + self.message_pos = -1 + + def _put_message(self, raw_msg): + # TODO: forward to iopub if this is an event message + msg = jsonapi.loads(raw_msg) + if mes['type'] == 'event': + self.event_callback(msg) + else: + self.message_queue.put_nowait(msg) + + def put_tcp_frame(self, frame): + self.tcp_buffer += frame + # TODO: not sure this is required + #self.tcp_buffer += frame.decode("utf-8") + + # Finds header + if self.header_pos == -1: + self.header_pos = self.tcp_buffer.find(DebugpyMessageQueue.HEADER, hint) + if self.header_pos == -1: + return + + #Finds separator + if self.separator_pos == -1: + hint = self.header_pos + DebugpyMessageQueue.HEADER_lenth + self.separator_pos = self.tcp_buffer.find(DebugpyMessageQueue.SEPARATOR, hint) + if self.separator_pos == -1: + return + + if self.message_pos == -1: + size_pos = self.header_pos + DebugpyMessageQueue.HEADER_lenth + self.message_pos = self.separator_pos + DebugpyMessageQueue.SEPARATOR_LENGTH + self.message_size = int(self.tcp_buf[size_pos:self.separator_pos]) + + if len(self.tcp_buffer - self.message_pos) < self.message_size: + return + + self._put_message(self.tcp_buf[self.message_pos:self.message_size]) + if len(self.tcp_buffer - self_message_pos) == self.message_size: + self.reset_buffer() + else: + self.tcp_buffer = self.tcp_buffer[self.message_pos + self.message_size:] + self._reset_tcp_pos() + + async def get_message(self): + return await self.message_queue.get() + + +class DebugpyClient: + + def __init__(self, debugpy_socket, debugpy_stream): + self.debugpy_socket = debugpy_socket + self.debugpy_stream = debugpy_stream + self.message_queue = DebugpyMessageQueue(self._forward_event) + self.wait_for_attach = True + self.init_event = asyncio.Event() + + def _forward_event(self, msg): + if msg['event'] == 'initialized': + self.init_event.set() + #TODO: send event to iopub + + def _send_request(self, msg): + content = jsonapi.dumps(msg) + content_length = len(content) + buf = DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR + content_msg + self.debugpy_socket.send(buf) # TODO: pass routing_id + + async def _wait_for_reponse(self): + # Since events are never pushed to the message_queue + # we can safely assume the next message in queue + # will be an answer to the previous request + return await self.message_queue.get() + + async def _handle_init_sequence(self): + # 1] Waits for initialized event + await self.init_event.wait() + + # 2] Sends configurationDone request + configurationDone = { + 'type': 'request', + 'seq': int(self.init_event_message['seq']) + 1, + 'command': 'configurationDone' + } + self._send_request(configurationDone) + + # 3] Waits for configurationDone response + await self._wait_for_response() + + # 4] Waits for attachResponse and returns it + attach_rep = await self._wait_for_response() + return attach_rep + + async def send_dap_request(self, msg): + self._send_request(msg) + if self.wait_for_attach and msg['command'] == 'attach': + rep = await self._handle_init_sequence() + self.wait_for_attach = False + return rep + else: + rep = await self._wait_for_reponse() + return rep + +class Debugger: + + # Requests that requires that the debugger has started + started_debug_msg_types = [ + 'dumpCell', 'setBreakpoints', + 'source', 'stackTrace', + 'variables', 'attach', + 'configurationDone' + ] + + # Requests that can be handled even if the debugger is not running + static_debug_msg_types = [ + 'debugInfo', 'inspectVariables' + ] + + log = Instance(logging.Logger, allow_none=True) + + def __init__(self): + self.is_started = False + + self.header = '' + + self.started_debug_handlers = {} + for msg_type in started_debug_msg_types: + self.started_debug_handlers[msg_type] = getattr(self, msg_type) + + self.static_debug_handlers = {} + for msg_type in static_debug_msg_types: + self.static_debug_handlers[msg_type] = getattr(self, msg_type) + + self.breakpoint_list = {} + self.stopped_threads = [] + + async def _forward_message(self, msg): + return await self.debugpy_client.send_dap_request(msg) + + def start(self): + return False + + def stop(self): + pass + + def dumpCell(self, message): + return {} + + async def setBreakpoints(self, message): + source = message['arguments']['source']['path']; + self.breakpoint_list[source] = message['arguments']['breakpoints'] + return await self._forward_message(message); + + def source(self, message): + reply = { + 'type': 'response', + 'request_seq': message['seq'], + 'command': message['command'] + } + source_path = message['arguments']['source']['path']; + if os.path.isfile(source_path): + with open(source_path) as f: + reply['success'] = True + reply['body'] = { + 'content': f.read() + } + + else: + reply['success'] = False + reply['message'] = 'source unavailable' + reply['body'] = {} + + return reply + + async def stackTrace(self, message): + reply = await self._forward_message(message) + reply['body']['stackFrames'] = + [frame for frame in reply['body']['stackFrames'] if frame['source']['path'] != ''] + return reply + + async def variables(self, message): + reply = await self._forward_message(message) + # TODO : check start and count arguments work as expected in debugpy + return reply + + async def attach(self, message): + message['arguments']['connect'] = { + 'host': self.debugpy_host, + 'port': self.debugpy_port + } + message['arguments']['logToFile'] = True + return await self._forward_message(message) + + def configurationDone(self, message): + reply = { + 'seq': message['seq'], + 'type': 'response', + 'request_seq': message['seq'], + 'success': True, + 'command': message['command'] + } + return reply; + + def debugInfo(self, message): + reply = { + 'type': 'response', + 'request_seq': message['seq'], + 'success': True, + 'command': message['command'], + 'body': { + 'isStarted': self.is_started, + 'hashMethod': 'Murmur2', + 'hashSeed': 0, + 'tmpFilePrefix': 'coincoin', + 'tmpFileSuffix': '.py', + 'breakpoints': self.breakpoint_list, + 'stoppedThreads': self.stopped_threads + } + } + return reply + + def inspectVariables(self, message): + return {} + + async def process_request(self, header, message): + reply = {} + + if message['command'] == 'initialize': + if self.is_started: + self.log.info('The debugger has already started') + else: + self.is_started = self.start() + if self.is_started: + self.log.info('The debugger has started') + else: + reply = { + 'command', 'initialize', + 'request_seq', message['seq'], + 'seq', 3, + 'success', False, + 'type', 'response' + } + + handler = self.static_debug_handlers.get(message['command'], None) + if handler is not None: + reply = await handler(message) + elif self.is_started: + self.header = header + handler = self.started_debug_handlers.get(message['command'], None) + if handler is not None: + reply = await handler(message) + else + reply = await self._forward_message(message) + + if message['command'] == 'disconnect': + self.stop() + self.breakpoint_list = {} + self.stopped_threads = [] + self.is_started = False + self.log.info('The debugger has stopped') + + return reply + From a0be3997e2b7c5c856f492f117be652ef7c495cd Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 10 Feb 2021 18:08:25 +0100 Subject: [PATCH 0456/1195] Plugged debugger --- ipykernel/debugger.py | 73 ++++++++++++++++++++++++++++------------- ipykernel/kernelapp.py | 18 ++++++++++ ipykernel/kernelbase.py | 46 +++++++++++++++++++++++++- ipykernel/kernelspec.py | 1 + 4 files changed, 115 insertions(+), 23 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 32a785390..40c7a65f1 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -2,9 +2,10 @@ import os from zmq.utils import jsonapi -from traitlets import Instance +#from traitlets import Instance +#from traitlets.config.configurable import SingletonConfigurable -from asyncio import Queue +from asyncio import (Event, Queue) class DebugpyMessageQueue: @@ -26,7 +27,6 @@ def _reset_tcp_pos(self): self.message_pos = -1 def _put_message(self, raw_msg): - # TODO: forward to iopub if this is an event message msg = jsonapi.loads(raw_msg) if mes['type'] == 'event': self.event_callback(msg) @@ -72,23 +72,23 @@ async def get_message(self): class DebugpyClient: - def __init__(self, debugpy_socket, debugpy_stream): - self.debugpy_socket = debugpy_socket + def __init__(self, debugpy_stream, event_callback): self.debugpy_stream = debugpy_stream + self.event_callback = event_callback self.message_queue = DebugpyMessageQueue(self._forward_event) self.wait_for_attach = True - self.init_event = asyncio.Event() + self.init_event = Event() def _forward_event(self, msg): if msg['event'] == 'initialized': self.init_event.set() - #TODO: send event to iopub + self.event_callback(msg) def _send_request(self, msg): content = jsonapi.dumps(msg) content_length = len(content) buf = DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR + content_msg - self.debugpy_socket.send(buf) # TODO: pass routing_id + self.debugpy_stream.send(buf) # TODO: pass routing_id async def _wait_for_reponse(self): # Since events are never pushed to the message_queue @@ -115,6 +115,9 @@ async def _handle_init_sequence(self): attach_rep = await self._wait_for_response() return attach_rep + def receive_dap_frame(self, frame): + self.message_queue.put_tcp_frame(frame) + async def send_dap_request(self, msg): self._send_request(msg) if self.wait_for_attach and msg['command'] == 'attach': @@ -140,19 +143,20 @@ class Debugger: 'debugInfo', 'inspectVariables' ] - log = Instance(logging.Logger, allow_none=True) + #log = Instance(logging.Logger, allow_none=True) - def __init__(self): + def __init__(self, debugpy_stream, event_callback, shell_socket, session): + self.debugpy_client = DebugpyClient(debugpy_stream, event_callback) + self.shell_socket = shell_socket + self.session = session self.is_started = False - - self.header = '' self.started_debug_handlers = {} - for msg_type in started_debug_msg_types: + for msg_type in Debugger.started_debug_msg_types: self.started_debug_handlers[msg_type] = getattr(self, msg_type) self.static_debug_handlers = {} - for msg_type in static_debug_msg_types: + for msg_type in Debugger.static_debug_msg_types: self.static_debug_handlers[msg_type] = getattr(self, msg_type) self.breakpoint_list = {} @@ -161,10 +165,27 @@ def __init__(self): async def _forward_message(self, msg): return await self.debugpy_client.send_dap_request(msg) + @property + def tcp_client(self): + return self.debugpy_client + def start(self): + endpoint = self.debugpy_client.debugpy_stream.socket.getsockopt(zmq.LAST_ENDPOINT) + index = endpoit.rfind(':') + port = endpoint[index+1:] + code = 'import debugpy;' + code += 'debugpy.listen(("127.0.0.1",' + port + '))' + content = { + 'code': code, + 'slient': True + } + self.session.send(self.shell_socket, 'execute_request', content, + None, (self.shell_socket.getsockopt(zmq.ROUTING_ID))) + return False def stop(self): + # TODO pass def dumpCell(self, message): @@ -198,7 +219,7 @@ def source(self, message): async def stackTrace(self, message): reply = await self._forward_message(message) - reply['body']['stackFrames'] = + reply['body']['stackFrames'] = \ [frame for frame in reply['body']['stackFrames'] if frame['source']['path'] != ''] return reply @@ -215,7 +236,7 @@ async def attach(self, message): message['arguments']['logToFile'] = True return await self._forward_message(message) - def configurationDone(self, message): + async def configurationDone(self, message): reply = { 'seq': message['seq'], 'type': 'response', @@ -225,7 +246,13 @@ def configurationDone(self, message): } return reply; - def debugInfo(self, message): + async def debugInfo(self, message): + breakpoint_list = [] + for key, value in self.breakpoint_list.items(): + breakpoint_list.append({ + 'source': key, + 'breakpoints': value + }) reply = { 'type': 'response', 'request_seq': message['seq'], @@ -235,18 +262,21 @@ def debugInfo(self, message): 'isStarted': self.is_started, 'hashMethod': 'Murmur2', 'hashSeed': 0, - 'tmpFilePrefix': 'coincoin', + 'tmpFilePrefix': '/tmp/ipykernel_debugger', 'tmpFileSuffix': '.py', - 'breakpoints': self.breakpoint_list, + 'breakpoints': breakpoint_list, 'stoppedThreads': self.stopped_threads } } + #self.log.info("returning reply %s", reply) + print("DEBUGGER: ", reply) return reply def inspectVariables(self, message): + # TODO return {} - async def process_request(self, header, message): + async def process_request(self, message): reply = {} if message['command'] == 'initialize': @@ -269,11 +299,10 @@ async def process_request(self, header, message): if handler is not None: reply = await handler(message) elif self.is_started: - self.header = header handler = self.started_debug_handlers.get(message['command'], None) if handler is not None: reply = await handler(message) - else + else: reply = await self._forward_message(message) if message['command'] == 'disconnect': diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index c7756fa2c..86dfb098c 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -122,6 +122,8 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, context = Any() shell_socket = Any() control_socket = Any() + debugpy_socket = Any() + debug_shell_socket = Any() stdin_socket = Any() iopub_socket = Any() iopub_thread = Any() @@ -294,6 +296,16 @@ def init_control(self, context): self.control_port = self._bind_socket(self.control_socket, self.control_port) self.log.debug("control ROUTER Channel on port: %i" % self.control_port) + self.debugpy_socket = context.socket(zmq.STREAM) + self.debugpy_socket.linger = 1000 + self.debugpy_port = 0 + self.debugpy_port = self._bind_socket(self.debugpy_socket, self.debugpy_port) + self.log.debug("debugpy STREAM Channel on port: %i" % self.debugpy_port) + + self.debug_shell_socket = context.socket(zmq.DEALER) + self.debug_shell_socket.linger = 1000 + self.debug_shell_socket.connect(self.shell_socket.getsockopt(zmq.LAST_ENDPOINT)) + if hasattr(zmq, 'ROUTER_HANDOVER'): # set router-handover to workaround zeromq reconnect problems # in certain rare circumstances @@ -335,6 +347,9 @@ def close(self): self.log.debug("Closing iopub channel") self.iopub_thread.stop() self.iopub_thread.close() + + self.debug_shell_socket.close() + for channel in ('shell', 'control', 'stdin'): self.log.debug("Closing %s channel", channel) socket = getattr(self, channel + "_socket", None) @@ -449,11 +464,14 @@ def init_kernel(self): """Create the Kernel object itself""" shell_stream = ZMQStream(self.shell_socket) control_stream = ZMQStream(self.control_socket, self.control_thread.io_loop) + debugpy_stream = ZMQStream(self.debugpy_socket, self.control_thread.io_loop) self.control_thread.start() kernel_factory = self.kernel_class.instance kernel = kernel_factory(parent=self, session=self.session, control_stream=control_stream, + debugpy_stream=debugpy_stream, + debug_shell_socket=self.debug_shell_socket, shell_stream=shell_stream, iopub_thread=self.iopub_thread, iopub_socket=self.iopub_socket, diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 4d26a73a7..e12d893d0 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -12,6 +12,7 @@ import time import uuid import warnings +import asyncio try: # jupyter_client >= 5, use tz-aware now @@ -39,6 +40,7 @@ from ._version import kernel_protocol_version +from .debugger import Debugger class Kernel(SingletonConfigurable): @@ -69,6 +71,10 @@ def shell_streams(self): return [shell_stream] control_stream = Instance(ZMQStream, allow_none=True) + debugpy_stream = Instance(ZMQStream, allow_none=True) + + debug_shell_socket = Any() + iopub_socket = Any() iopub_thread = Any() stdin_socket = Any() @@ -160,7 +166,7 @@ def _default_ident(self): 'apply_request', ] # add deprecated ipyparallel control messages - control_msg_types = msg_types + ['clear_request', 'abort_request'] + control_msg_types = msg_types + ['clear_request', 'abort_request', 'debug_request'] def __init__(self, **kwargs): super(Kernel, self).__init__(**kwargs) @@ -173,6 +179,16 @@ def __init__(self, **kwargs): for msg_type in self.control_msg_types: self.control_handlers[msg_type] = getattr(self, msg_type) + self.debugger = Debugger(self.debugpy_stream, + self._publish_debug_event, + self.debug_shell_socket, + self.session) + + @gen.coroutine + def dispatch_debugpy(self, msg): + self.log.debug("Debugpy received: %s", msg) + selg.debugger.tcp_client.receive_dap_frame(msg) + @gen.coroutine def dispatch_control(self, msg): """dispatch control requests""" @@ -366,7 +382,12 @@ def dispatch_queue(self): Ensures that only one message is processing at a time, even when the handler is async """ + while True: + # ensure control stream is flushed before processing shell messages + if self.control_stream: + self.control_stream.flush() + # receive the next message and handle it try: yield self.process_one() except Exception: @@ -401,6 +422,7 @@ def start(self): self.io_loop.add_callback(self.dispatch_queue) self.control_stream.on_recv(self.dispatch_control, copy=False) + self.debugpy_stream.on_recv(self.dispatch_debugpy, copy=False) self.shell_stream.on_recv( partial( @@ -442,6 +464,13 @@ def _publish_status(self, status, channel, parent=None): parent=parent or self._parent_header[channel], ident=self._topic('status'), ) + def _publish_debug_event(self, event): + self.session.send(self.iopub_socket, + 'debug_event', + event, + parent=self._parent_header['control'], + ident=self._topic('debug_event') + ) def set_parent(self, ident, parent, channel='shell'): """Set the current parent_header @@ -519,6 +548,7 @@ def execute_request(self, stream, ident, parent): ) ) + self.log.debug("EXECUTE_REPLY: %s", reply_content) # Flush output before sending the reply. sys.stdout.flush() sys.stderr.flush() @@ -693,6 +723,20 @@ def do_is_complete(self, code): return {'status' : 'unknown', } + @gen.coroutine + def debug_request(self, stream, ident, parent): + content = parent['content'] + + reply_content = yield gen.maybe_future(self.do_debug_request(content)) + reply_content = json_clean(reply_content) + reply_msg = self.session.send(stream, 'debug_reply', reply_content, + parent, ident) + self.log.debug("%s", reply_msg) + + @gen.coroutine + def do_debug_request(self, msg): + return (yield self.debugger.process_request(msg)) + #--------------------------------------------------------------------------- # Engine methods (DEPRECATED) #--------------------------------------------------------------------------- diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index f37f98e19..deda6ebec 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -55,6 +55,7 @@ def get_kernel_dict(extra_arguments=None): 'argv': make_ipkernel_cmd(extra_arguments=extra_arguments), 'display_name': 'Python %i' % sys.version_info[0], 'language': 'python', + 'metadata': { 'debugger': True} } From b6731f338c229b879260cb53a00a91130632e242 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 12 Feb 2021 16:11:57 +0100 Subject: [PATCH 0457/1195] Fixed starting sequence --- ipykernel/debugger.py | 115 ++++++++++++++++++++++++++-------------- ipykernel/kernelapp.py | 3 -- ipykernel/kernelbase.py | 10 ++-- 3 files changed, 80 insertions(+), 48 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 40c7a65f1..e55071a0f 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,24 +1,25 @@ import logging import os +import zmq from zmq.utils import jsonapi -#from traitlets import Instance -#from traitlets.config.configurable import SingletonConfigurable -from asyncio import (Event, Queue) +from tornado.queues import Queue +from tornado.locks import Event class DebugpyMessageQueue: - HEADER = 'Content Length: ' + HEADER = 'Content-Length: ' HEADER_LENGTH = 16 SEPARATOR = '\r\n\r\n' SEPARATOR_LENGTH = 4 - def __init__(self, event_callback): + def __init__(self, event_callback, log): self.tcp_buffer = '' self._reset_tcp_pos() self.event_callback = event_callback - self.message_queue = Queue + self.message_queue = Queue() + self.log = log def _reset_tcp_pos(self): self.header_pos = -1 @@ -27,42 +28,56 @@ def _reset_tcp_pos(self): self.message_pos = -1 def _put_message(self, raw_msg): + self.log.debug('QUEUE - _put_message:') msg = jsonapi.loads(raw_msg) - if mes['type'] == 'event': + if msg['type'] == 'event': + self.log.debug('QUEUE - received event:') + self.log.debug(msg) self.event_callback(msg) else: + self.log.debug('QUEUE - put message:') + self.log.debug(msg) self.message_queue.put_nowait(msg) def put_tcp_frame(self, frame): self.tcp_buffer += frame - # TODO: not sure this is required - #self.tcp_buffer += frame.decode("utf-8") + self.log.debug('QUEUE - received frame') # Finds header if self.header_pos == -1: - self.header_pos = self.tcp_buffer.find(DebugpyMessageQueue.HEADER, hint) + self.header_pos = self.tcp_buffer.find(DebugpyMessageQueue.HEADER) if self.header_pos == -1: return + self.log.debug('QUEUE - found header at pos %i', self.header_pos) + #Finds separator if self.separator_pos == -1: - hint = self.header_pos + DebugpyMessageQueue.HEADER_lenth + hint = self.header_pos + DebugpyMessageQueue.HEADER_LENGTH self.separator_pos = self.tcp_buffer.find(DebugpyMessageQueue.SEPARATOR, hint) if self.separator_pos == -1: return + self.log.debug('QUEUE - found separator at pos %i', self.separator_pos) + if self.message_pos == -1: - size_pos = self.header_pos + DebugpyMessageQueue.HEADER_lenth + size_pos = self.header_pos + DebugpyMessageQueue.HEADER_LENGTH self.message_pos = self.separator_pos + DebugpyMessageQueue.SEPARATOR_LENGTH - self.message_size = int(self.tcp_buf[size_pos:self.separator_pos]) + self.message_size = int(self.tcp_buffer[size_pos:self.separator_pos]) - if len(self.tcp_buffer - self.message_pos) < self.message_size: + self.log.debug('QUEUE - found message at pos %i', self.message_pos) + self.log.debug('QUEUE - message size is %i', self.message_size) + + if len(self.tcp_buffer) - self.message_pos < self.message_size: return - self._put_message(self.tcp_buf[self.message_pos:self.message_size]) - if len(self.tcp_buffer - self_message_pos) == self.message_size: - self.reset_buffer() + self._put_message(self.tcp_buffer[self.message_pos:self.message_pos + self.message_size]) + if len(self.tcp_buffer) - self.message_pos == self.message_size: + self.log.debug('QUEUE - resetting tcp_buffer') + self.tcp_buffer = '' + self._reset_tcp_pos() else: + self.log.debug('QUEUE - slicing tcp_buffer') self.tcp_buffer = self.tcp_buffer[self.message_pos + self.message_size:] self._reset_tcp_pos() @@ -72,29 +87,40 @@ async def get_message(self): class DebugpyClient: - def __init__(self, debugpy_stream, event_callback): + def __init__(self, log, debugpy_stream, event_callback): + self.log = log self.debugpy_stream = debugpy_stream + self.routing_id = None self.event_callback = event_callback - self.message_queue = DebugpyMessageQueue(self._forward_event) + self.message_queue = DebugpyMessageQueue(self._forward_event, self.log) self.wait_for_attach = True self.init_event = Event() + self.init_event_seq = -1 def _forward_event(self, msg): if msg['event'] == 'initialized': self.init_event.set() + self.init_event_seq = msg['seq'] self.event_callback(msg) def _send_request(self, msg): + if self.routing_id is None: + self.routing_id = self.debugpy_stream.socket.getsockopt(zmq.ROUTING_ID) content = jsonapi.dumps(msg) - content_length = len(content) - buf = DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR + content_msg - self.debugpy_stream.send(buf) # TODO: pass routing_id + content_length = str(len(content)) + buf = (DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR).encode('ascii') + buf += content + self.log.debug("DEBUGPYCLIENT:") + self.log.debug(self.routing_id) + self.log.debug(buf) + self.debugpy_stream.send_multipart((self.routing_id, buf)) + #self.debugpy_stream.send(buf) # TODO: pass routing_id - async def _wait_for_reponse(self): + async def _wait_for_response(self): # Since events are never pushed to the message_queue # we can safely assume the next message in queue # will be an answer to the previous request - return await self.message_queue.get() + return await self.message_queue.get_message() async def _handle_init_sequence(self): # 1] Waits for initialized event @@ -103,7 +129,7 @@ async def _handle_init_sequence(self): # 2] Sends configurationDone request configurationDone = { 'type': 'request', - 'seq': int(self.init_event_message['seq']) + 1, + 'seq': int(self.init_event_seq) + 1, 'command': 'configurationDone' } self._send_request(configurationDone) @@ -125,7 +151,9 @@ async def send_dap_request(self, msg): self.wait_for_attach = False return rep else: - rep = await self._wait_for_reponse() + rep = await self._wait_for_response() + self.log.debug('DEBUGPYCLIENT - returning:') + self.log.debug(rep) return rep class Debugger: @@ -143,10 +171,9 @@ class Debugger: 'debugInfo', 'inspectVariables' ] - #log = Instance(logging.Logger, allow_none=True) - - def __init__(self, debugpy_stream, event_callback, shell_socket, session): - self.debugpy_client = DebugpyClient(debugpy_stream, event_callback) + def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): + self.log = log + self.debugpy_client = DebugpyClient(log, debugpy_stream, event_callback) self.shell_socket = shell_socket self.session = session self.is_started = False @@ -162,6 +189,9 @@ def __init__(self, debugpy_stream, event_callback, shell_socket, session): self.breakpoint_list = {} self.stopped_threads = [] + self.debugpy_host = '127.0.0.1' + self.debugpy_port = 0 + async def _forward_message(self, msg): return await self.debugpy_client.send_dap_request(msg) @@ -170,25 +200,30 @@ def tcp_client(self): return self.debugpy_client def start(self): - endpoint = self.debugpy_client.debugpy_stream.socket.getsockopt(zmq.LAST_ENDPOINT) - index = endpoit.rfind(':') - port = endpoint[index+1:] + socket = self.debugpy_client.debugpy_stream.socket + socket.bind_to_random_port('tcp://' + self.debugpy_host) + endpoint = socket.getsockopt(zmq.LAST_ENDPOINT).decode('utf-8') + socket.unbind(endpoint) + index = endpoint.rfind(':') + self.debugpy_port = endpoint[index+1:] code = 'import debugpy;' - code += 'debugpy.listen(("127.0.0.1",' + port + '))' + code += 'debugpy.listen(("' + self.debugpy_host + '",' + self.debugpy_port + '))' content = { 'code': code, - 'slient': True + 'silent': True } self.session.send(self.shell_socket, 'execute_request', content, None, (self.shell_socket.getsockopt(zmq.ROUTING_ID))) - return False + self.session.recv(self.shell_socket, mode=0) + socket.connect(endpoint) + return True def stop(self): # TODO pass - def dumpCell(self, message): + async def dumpCell(self, message): return {} async def setBreakpoints(self, message): @@ -196,7 +231,7 @@ async def setBreakpoints(self, message): self.breakpoint_list[source] = message['arguments']['breakpoints'] return await self._forward_message(message); - def source(self, message): + async def source(self, message): reply = { 'type': 'response', 'request_seq': message['seq'], @@ -268,11 +303,9 @@ async def debugInfo(self, message): 'stoppedThreads': self.stopped_threads } } - #self.log.info("returning reply %s", reply) - print("DEBUGGER: ", reply) return reply - def inspectVariables(self, message): + async def inspectVariables(self, message): # TODO return {} diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 86dfb098c..7bad05013 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -298,9 +298,6 @@ def init_control(self, context): self.debugpy_socket = context.socket(zmq.STREAM) self.debugpy_socket.linger = 1000 - self.debugpy_port = 0 - self.debugpy_port = self._bind_socket(self.debugpy_socket, self.debugpy_port) - self.log.debug("debugpy STREAM Channel on port: %i" % self.debugpy_port) self.debug_shell_socket = context.socket(zmq.DEALER) self.debug_shell_socket.linger = 1000 diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e12d893d0..803987b05 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -179,15 +179,18 @@ def __init__(self, **kwargs): for msg_type in self.control_msg_types: self.control_handlers[msg_type] = getattr(self, msg_type) - self.debugger = Debugger(self.debugpy_stream, + self.debugger = Debugger(self.log, + self.debugpy_stream, self._publish_debug_event, self.debug_shell_socket, self.session) @gen.coroutine def dispatch_debugpy(self, msg): - self.log.debug("Debugpy received: %s", msg) - selg.debugger.tcp_client.receive_dap_frame(msg) + # The first frame is the socket id, we can drop it + frame = msg[1].bytes.decode('utf-8') + self.log.debug("Debugpy received: %s", frame) + self.debugger.tcp_client.receive_dap_frame(frame) @gen.coroutine def dispatch_control(self, msg): @@ -548,7 +551,6 @@ def execute_request(self, stream, ident, parent): ) ) - self.log.debug("EXECUTE_REPLY: %s", reply_content) # Flush output before sending the reply. sys.stdout.flush() sys.stderr.flush() From 95b5aeea424ec0ab1ccb73631e19125860280ae5 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 19 Feb 2021 11:55:17 +0100 Subject: [PATCH 0458/1195] Implemented breakpoints --- ipykernel/compiler.py | 29 +++++++++++ ipykernel/control.py | 4 +- ipykernel/debugger.py | 106 +++++++++++++++++++++++++--------------- ipykernel/heartbeat.py | 2 + ipykernel/iostream.py | 2 + ipykernel/ipkernel.py | 3 ++ ipykernel/kernelapp.py | 1 + ipykernel/kernelbase.py | 15 ++++++ 8 files changed, 121 insertions(+), 41 deletions(-) create mode 100644 ipykernel/compiler.py diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py new file mode 100644 index 000000000..fbb6cc41d --- /dev/null +++ b/ipykernel/compiler.py @@ -0,0 +1,29 @@ +from IPython.core.compilerop import CachingCompiler +import murmurhash.mrmr + +def get_tmp_directory(): + return '/tmp/ipykernel_debugger/' + +def get_tmp_hash_seed(): + hash_seed = 0xc70f6907 + return hash_seed + +def get_file_name(code): + name = murmurhash.mrmr.hash(code, seed = get_tmp_hash_seed(), murmur_version=2) + return get_tmp_directory() + str(name) + '.py' + +class XCachingCompiler(CachingCompiler): + + def __init__(self, *args, **kwargs): + super(XCachingCompiler, self).__init__(*args, **kwargs) + self.filename_mapper = None + self.log = None + + def get_code_name(self, raw_code, code, number): + filename = get_file_name(raw_code) + + if self.filename_mapper is not None: + self.filename_mapper(filename, number) + + return filename + diff --git a/ipykernel/control.py b/ipykernel/control.py index 10853046d..57a7ac727 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -9,9 +9,11 @@ class ControlThread(Thread): - def __init__(self, **kwargs): + def __init__(self, log=None, **kwargs): Thread.__init__(self, **kwargs) self.io_loop = IOLoop(make_current=False) + self.pydev_do_not_trace = True + self.is_pydev_daemon_thread = True def run(self): self.io_loop.make_current() diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index e55071a0f..efd8a66d3 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -7,6 +7,10 @@ from tornado.queues import Queue from tornado.locks import Event +from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed) + +import debugpy + class DebugpyMessageQueue: HEADER = 'Content-Length: ' @@ -43,43 +47,45 @@ def put_tcp_frame(self, frame): self.tcp_buffer += frame self.log.debug('QUEUE - received frame') - # Finds header - if self.header_pos == -1: - self.header_pos = self.tcp_buffer.find(DebugpyMessageQueue.HEADER) - if self.header_pos == -1: - return + while True: + # Finds header + if self.header_pos == -1: + self.header_pos = self.tcp_buffer.find(DebugpyMessageQueue.HEADER) + if self.header_pos == -1: + return - self.log.debug('QUEUE - found header at pos %i', self.header_pos) - - #Finds separator - if self.separator_pos == -1: - hint = self.header_pos + DebugpyMessageQueue.HEADER_LENGTH - self.separator_pos = self.tcp_buffer.find(DebugpyMessageQueue.SEPARATOR, hint) - if self.separator_pos == -1: - return - - self.log.debug('QUEUE - found separator at pos %i', self.separator_pos) - - if self.message_pos == -1: - size_pos = self.header_pos + DebugpyMessageQueue.HEADER_LENGTH - self.message_pos = self.separator_pos + DebugpyMessageQueue.SEPARATOR_LENGTH - self.message_size = int(self.tcp_buffer[size_pos:self.separator_pos]) - - self.log.debug('QUEUE - found message at pos %i', self.message_pos) - self.log.debug('QUEUE - message size is %i', self.message_size) - - if len(self.tcp_buffer) - self.message_pos < self.message_size: - return - - self._put_message(self.tcp_buffer[self.message_pos:self.message_pos + self.message_size]) - if len(self.tcp_buffer) - self.message_pos == self.message_size: - self.log.debug('QUEUE - resetting tcp_buffer') - self.tcp_buffer = '' - self._reset_tcp_pos() - else: - self.log.debug('QUEUE - slicing tcp_buffer') - self.tcp_buffer = self.tcp_buffer[self.message_pos + self.message_size:] - self._reset_tcp_pos() + self.log.debug('QUEUE - found header at pos %i', self.header_pos) + + #Finds separator + if self.separator_pos == -1: + hint = self.header_pos + DebugpyMessageQueue.HEADER_LENGTH + self.separator_pos = self.tcp_buffer.find(DebugpyMessageQueue.SEPARATOR, hint) + if self.separator_pos == -1: + return + + self.log.debug('QUEUE - found separator at pos %i', self.separator_pos) + + if self.message_pos == -1: + size_pos = self.header_pos + DebugpyMessageQueue.HEADER_LENGTH + self.message_pos = self.separator_pos + DebugpyMessageQueue.SEPARATOR_LENGTH + self.message_size = int(self.tcp_buffer[size_pos:self.separator_pos]) + + self.log.debug('QUEUE - found message at pos %i', self.message_pos) + self.log.debug('QUEUE - message size is %i', self.message_size) + + if len(self.tcp_buffer) - self.message_pos < self.message_size: + return + + self._put_message(self.tcp_buffer[self.message_pos:self.message_pos + self.message_size]) + if len(self.tcp_buffer) - self.message_pos == self.message_size: + self.log.debug('QUEUE - resetting tcp_buffer') + self.tcp_buffer = '' + self._reset_tcp_pos() + return + else: + self.tcp_buffer = self.tcp_buffer[self.message_pos + self.message_size:] + self.log.debug('QUEUE - slicing tcp_buffer: %s', self.tcp_buffer) + self._reset_tcp_pos() async def get_message(self): return await self.message_queue.get() @@ -217,6 +223,7 @@ def start(self): self.session.recv(self.shell_socket, mode=0) socket.connect(endpoint) + debugpy.trace_this_thread(False) return True def stop(self): @@ -224,7 +231,22 @@ def stop(self): pass async def dumpCell(self, message): - return {} + code = message['arguments']['code'] + file_name = get_file_name(code) + + with open(file_name, 'w') as f: + f.write(code) + + reply = { + 'type': 'response', + 'request_seq': message['seq'], + 'success': True, + 'command': message['command'], + 'body': { + 'sourcePath': file_name + } + } + return reply async def setBreakpoints(self, message): source = message['arguments']['source']['path']; @@ -244,7 +266,6 @@ async def source(self, message): reply['body'] = { 'content': f.read() } - else: reply['success'] = False reply['message'] = 'source unavailable' @@ -258,9 +279,14 @@ async def stackTrace(self, message): [frame for frame in reply['body']['stackFrames'] if frame['source']['path'] != ''] return reply + def accept_variable(self, variable): + return variable['type'] != 'list' and variable['type'] != 'ZMQExitAutocall' and variable['type'] != 'dict' + async def variables(self, message): reply = await self._forward_message(message) # TODO : check start and count arguments work as expected in debugpy + reply['body']['variables'] = \ + [var for var in reply['body']['variables'] if self.accept_variable(var)] return reply async def attach(self, message): @@ -296,8 +322,8 @@ async def debugInfo(self, message): 'body': { 'isStarted': self.is_started, 'hashMethod': 'Murmur2', - 'hashSeed': 0, - 'tmpFilePrefix': '/tmp/ipykernel_debugger', + 'hashSeed': get_tmp_hash_seed(), + 'tmpFilePrefix': get_tmp_directory(), 'tmpFileSuffix': '.py', 'breakpoints': breakpoint_list, 'stoppedThreads': self.stopped_threads diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 01be21122..6d65a6c7e 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -40,6 +40,8 @@ def __init__(self, context, addr=None): self.pick_port() self.addr = (self.ip, self.port) self.daemon = True + self.pydev_do_not_trace = True + self.is_pydev_daemon_thread = True def pick_port(self): if self.transport == 'tcp': diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 486e80e27..cb3382604 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -71,6 +71,8 @@ def __init__(self, socket, pipe=False): self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main) self.thread.daemon = True + self.thread.pydev_do_not_trace = True + self.thread.is_pydev_daemon_thread = True def _thread_main(self): """The inner loop that's actually run in a thread""" diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 62628971a..1939a54f0 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -19,6 +19,8 @@ from .zmqshell import ZMQInteractiveShell from .eventloops import _use_appnope +from .compiler import XCachingCompiler + try: from IPython.core.interactiveshell import _asyncio_runner except ImportError: @@ -71,6 +73,7 @@ def __init__(self, **kwargs): user_module = self.user_module, user_ns = self.user_ns, kernel = self, + compiler_class = XCachingCompiler, ) self.shell.displayhook.session = self.session self.shell.displayhook.pub_socket = self.iopub_socket diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 7bad05013..090e62e05 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -470,6 +470,7 @@ def init_kernel(self): debugpy_stream=debugpy_stream, debug_shell_socket=self.debug_shell_socket, shell_stream=shell_stream, + control_thread=self.control_thread, iopub_thread=self.iopub_thread, iopub_socket=self.iopub_socket, stdin_socket=self.stdin_socket, diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 803987b05..0643fcd7b 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -75,6 +75,7 @@ def shell_streams(self): debug_shell_socket = Any() + control_thread = Any() iopub_socket = Any() iopub_thread = Any() stdin_socket = Any() @@ -185,6 +186,10 @@ def __init__(self, **kwargs): self.debug_shell_socket, self.session) + self.control_queue = Queue() + kwargs['control_thread'].io_loop.add_callback(self.poll_control_queue) + + @gen.coroutine def dispatch_debugpy(self, msg): # The first frame is the socket id, we can drop it @@ -194,6 +199,16 @@ def dispatch_debugpy(self, msg): @gen.coroutine def dispatch_control(self, msg): + self.control_queue.put_nowait(msg) + + @gen.coroutine + def poll_control_queue(self): + while True: + msg = yield self.control_queue.get() + yield self.process_control(msg) + + @gen.coroutine + def process_control(self, msg): """dispatch control requests""" idents, msg = self.session.feed_identities(msg, copy=False) try: From 5ec9e348a13ac8e9ec1736bf33649e2c616062e4 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 19 Feb 2021 23:19:23 +0100 Subject: [PATCH 0459/1195] Fixed negative hash numbers --- ipykernel/compiler.py | 2 ++ ipykernel/debugger.py | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index fbb6cc41d..85fd68603 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -10,6 +10,8 @@ def get_tmp_hash_seed(): def get_file_name(code): name = murmurhash.mrmr.hash(code, seed = get_tmp_hash_seed(), murmur_version=2) + if name < 0: + name += 2**32 return get_tmp_directory() + str(name) + '.py' class XCachingCompiler(CachingCompiler): diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index efd8a66d3..900c10bf0 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -120,7 +120,6 @@ def _send_request(self, msg): self.log.debug(self.routing_id) self.log.debug(buf) self.debugpy_stream.send_multipart((self.routing_id, buf)) - #self.debugpy_stream.send(buf) # TODO: pass routing_id async def _wait_for_response(self): # Since events are never pushed to the message_queue From 72a9755e62b65e3b8b5a3b7b884cb8bf6e8cf5ef Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 22 Feb 2021 10:50:23 +0100 Subject: [PATCH 0460/1195] Handling stop and restart --- ipykernel/debugger.py | 74 +++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 900c10bf0..8acb65cbf 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -10,6 +10,7 @@ from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed) import debugpy +import time class DebugpyMessageQueue: @@ -96,13 +97,19 @@ class DebugpyClient: def __init__(self, log, debugpy_stream, event_callback): self.log = log self.debugpy_stream = debugpy_stream - self.routing_id = None self.event_callback = event_callback self.message_queue = DebugpyMessageQueue(self._forward_event, self.log) + self.debugpy_host = '127.0.0.1' + self.debugpy_port = -1 + self.routing_id = None self.wait_for_attach = True self.init_event = Event() self.init_event_seq = -1 + def _get_endpoint(self): + host, port = self.get_host_port() + return 'tcp://' + host + ':' + str(port) + def _forward_event(self, msg): if msg['event'] == 'initialized': self.init_event.set() @@ -146,6 +153,28 @@ async def _handle_init_sequence(self): attach_rep = await self._wait_for_response() return attach_rep + def get_host_port(self): + if self.debugpy_port == -1: + socket = self.debugpy_stream.socket + socket.bind_to_random_port('tcp://' + self.debugpy_host) + self.endpoint = socket.getsockopt(zmq.LAST_ENDPOINT).decode('utf-8') + socket.unbind(self.endpoint) + index = self.endpoint.rfind(':') + self.debugpy_port = self.endpoint[index+1:] + return self.debugpy_host, self.debugpy_port + + + def connect_tcp_socket(self): + self.debugpy_stream.socket.connect(self._get_endpoint()) + self.routing_id = self.debugpy_stream.socket.getsockopt(zmq.ROUTING_ID) + + def disconnect_tcp_socket(self): + self.debugpy_stream.socket.disconnect(self._get_endpoint()) + self.routing_id = None + self.init_event = Event() + self.init_event_seq = -1 + self.wait_for_attach = True + def receive_dap_frame(self, frame): self.message_queue.put_tcp_frame(frame) @@ -194,8 +223,11 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): self.breakpoint_list = {} self.stopped_threads = [] + self.debugpy_initialized = False + self.debugpy_host = '127.0.0.1' self.debugpy_port = 0 + self.endpoint = None async def _forward_message(self, msg): return await self.debugpy_client.send_dap_request(msg) @@ -205,29 +237,24 @@ def tcp_client(self): return self.debugpy_client def start(self): - socket = self.debugpy_client.debugpy_stream.socket - socket.bind_to_random_port('tcp://' + self.debugpy_host) - endpoint = socket.getsockopt(zmq.LAST_ENDPOINT).decode('utf-8') - socket.unbind(endpoint) - index = endpoint.rfind(':') - self.debugpy_port = endpoint[index+1:] - code = 'import debugpy;' - code += 'debugpy.listen(("' + self.debugpy_host + '",' + self.debugpy_port + '))' - content = { - 'code': code, - 'silent': True - } - self.session.send(self.shell_socket, 'execute_request', content, - None, (self.shell_socket.getsockopt(zmq.ROUTING_ID))) + if not self.debugpy_initialized: + host, port = self.debugpy_client.get_host_port() + code = 'import debugpy;' + code += 'debugpy.listen(("' + host + '",' + port + '))' + content = { + 'code': code, + 'silent': True + } + self.session.send(self.shell_socket, 'execute_request', content, + None, (self.shell_socket.getsockopt(zmq.ROUTING_ID))) - self.session.recv(self.shell_socket, mode=0) - socket.connect(endpoint) - debugpy.trace_this_thread(False) - return True + ident, msg = self.session.recv(self.shell_socket, mode=0) + self.debugpy_initialized = msg['content']['status'] == 'ok' + self.debugpy_client.connect_tcp_socket() + return self.debugpy_initialized def stop(self): - # TODO - pass + self.debugpy_client.disconnect_tcp_socket() async def dumpCell(self, message): code = message['arguments']['code'] @@ -289,9 +316,10 @@ async def variables(self, message): return reply async def attach(self, message): + host, port = self.debugpy_client.get_host_port() message['arguments']['connect'] = { - 'host': self.debugpy_host, - 'port': self.debugpy_port + 'host': host, + 'port': port } message['arguments']['logToFile'] = True return await self._forward_message(message) From adc8079ce2232328a207cd858501b7d6c984ded5 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 23 Feb 2021 02:09:24 +0100 Subject: [PATCH 0461/1195] Create tmporary directory for dumpCell request --- ipykernel/compiler.py | 8 ++++++-- ipykernel/debugger.py | 7 ++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 85fd68603..44456b8b7 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -1,8 +1,12 @@ from IPython.core.compilerop import CachingCompiler import murmurhash.mrmr +import tempfile +import os def get_tmp_directory(): - return '/tmp/ipykernel_debugger/' + tmp_dir = tempfile.gettempdir() + pid = os.getpid() + return tmp_dir + '/ipykernel_' + str(pid) def get_tmp_hash_seed(): hash_seed = 0xc70f6907 @@ -12,7 +16,7 @@ def get_file_name(code): name = murmurhash.mrmr.hash(code, seed = get_tmp_hash_seed(), murmur_version=2) if name < 0: name += 2**32 - return get_tmp_directory() + str(name) + '.py' + return get_tmp_directory() + '/' + str(name) + '.py' class XCachingCompiler(CachingCompiler): diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 8acb65cbf..3ad8fd94c 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -10,7 +10,6 @@ from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed) import debugpy -import time class DebugpyMessageQueue: @@ -163,7 +162,6 @@ def get_host_port(self): self.debugpy_port = self.endpoint[index+1:] return self.debugpy_host, self.debugpy_port - def connect_tcp_socket(self): self.debugpy_stream.socket.connect(self._get_endpoint()) self.routing_id = self.debugpy_stream.socket.getsockopt(zmq.ROUTING_ID) @@ -238,6 +236,9 @@ def tcp_client(self): def start(self): if not self.debugpy_initialized: + tmp_dir = get_tmp_directory() + if not os.path.exists(tmp_dir): + os.makedirs(tmp_dir) host, port = self.debugpy_client.get_host_port() code = 'import debugpy;' code += 'debugpy.listen(("' + host + '",' + port + '))' @@ -350,7 +351,7 @@ async def debugInfo(self, message): 'isStarted': self.is_started, 'hashMethod': 'Murmur2', 'hashSeed': get_tmp_hash_seed(), - 'tmpFilePrefix': get_tmp_directory(), + 'tmpFilePrefix': get_tmp_directory() + '/', 'tmpFileSuffix': '.py', 'breakpoints': breakpoint_list, 'stoppedThreads': self.stopped_threads From 1b6f09a19ebc2f21d5014a6197bcd97f8bb62f24 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 23 Feb 2021 09:35:58 +0100 Subject: [PATCH 0462/1195] Added dependencies in setup.py --- setup.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 106eceaa6..e96636919 100644 --- a/setup.py +++ b/setup.py @@ -89,11 +89,13 @@ def run(self): keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], python_requires='>=3.5', install_requires=[ - 'ipython>=5.0.0', + 'debugpy>=1.0.0', + 'ipython>=7.21.0', 'traitlets>=4.1.0', 'jupyter_client', 'tornado>=4.2', 'appnope;platform_system=="Darwin"', + #'murmurhash>=1.0.0' requires https://github.com/explosion/murmurhash/pull/24 ], extras_require={ 'test': [ From 56f618f38d25a669169cdacd34a20d2ab4b5691f Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 1 Mar 2021 13:42:15 +0100 Subject: [PATCH 0463/1195] Local implementation of murmur2 hash --- ipykernel/compiler.py | 37 +++++++++++++++++++++++++++++++++---- setup.py | 1 - 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 44456b8b7..4c724f146 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -1,8 +1,39 @@ from IPython.core.compilerop import CachingCompiler -import murmurhash.mrmr import tempfile import os +def murmur2_x86(data, seed): + m = 0x5bd1e995 + length = len(data) + h = seed ^ length + rounded_end = (length & 0xfffffffc) + for i in range(0, rounded_end, 4): + k = (ord(data[i]) & 0xff) | ((ord(data[i + 1]) & 0xff) << 8) | \ + ((ord(data[i + 2]) & 0xff) << 16) | (ord(data[i + 3]) << 24) + k = (k * m) & 0xffffffff + k ^= k >> 24 + k = (k * m) & 0xffffffff + + h = (h * m) & 0xffffffff + h ^= k + + val = length & 0x03 + k = 0 + if val == 3: + k = (ord(data[rounded_end + 2]) & 0xff) << 16 + if val in [2, 3]: + k |= (ord(data[rounded_end + 1]) & 0xff) << 8 + if val in [1, 2, 3]: + k |= ord(data[rounded_end]) & 0xff + h ^= k + h = (h * m) & 0xffffffff + + h ^= h >> 13 + h = (h * m) & 0xffffffff + h ^= h >> 15 + + return h + def get_tmp_directory(): tmp_dir = tempfile.gettempdir() pid = os.getpid() @@ -13,9 +44,7 @@ def get_tmp_hash_seed(): return hash_seed def get_file_name(code): - name = murmurhash.mrmr.hash(code, seed = get_tmp_hash_seed(), murmur_version=2) - if name < 0: - name += 2**32 + name = murmur2_x86(code, get_tmp_hash_seed()) return get_tmp_directory() + '/' + str(name) + '.py' class XCachingCompiler(CachingCompiler): diff --git a/setup.py b/setup.py index e96636919..927152151 100644 --- a/setup.py +++ b/setup.py @@ -95,7 +95,6 @@ def run(self): 'jupyter_client', 'tornado>=4.2', 'appnope;platform_system=="Darwin"', - #'murmurhash>=1.0.0' requires https://github.com/explosion/murmurhash/pull/24 ], extras_require={ 'test': [ From 0181d70a18f4b01520c1aba04b2bd4a65c1c7c67 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 1 Mar 2021 14:04:23 +0100 Subject: [PATCH 0464/1195] Dropped support for Python 3.6 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7c4898c6f..d4e6425e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos, windows] - python-version: [ '3.6', '3.7', '3.8', '3.9', 'pypy3' ] + python-version: [ '3.7', '3.8', '3.9' ] exclude: - os: windows python-version: pypy3 From 5b4f173cd320366206c72e2d3c5acfb267a33161 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Mar 2021 10:30:52 +0100 Subject: [PATCH 0465/1195] Removed ipykernel stack frames from the callstack --- ipykernel/debugger.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 3ad8fd94c..8871bdceb 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -302,8 +302,17 @@ async def source(self, message): async def stackTrace(self, message): reply = await self._forward_message(message) - reply['body']['stackFrames'] = \ - [frame for frame in reply['body']['stackFrames'] if frame['source']['path'] != ''] + # We stackFrames array has the following content: + # { frames from the notebook} + # ... + # { 'id': xxx, 'name': '', ... } <= this is the first frame of the code from the notebook + # { frame from ipykernel } + # ... + # {'id': yyy, 'name': '', ... } <= this is the first frame of ipykernel code + # We want to remove all the frames from ipykernel + sf_list = reply['body']['stackFrames'] + module_idx = len(sf_list) - next(i for i, v in enumerate(reversed(sf_list), 1) if v['name'] == '' and i != 1) + reply['body']['stackFrames'] = reply['body']['stackFrames'][:module_idx+1] return reply def accept_variable(self, variable): From 8210c110d5e94a1adc8cb1526754cefddc54484f Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Mar 2021 15:07:40 +0100 Subject: [PATCH 0466/1195] Add and remove threads Id upon debugger events --- ipykernel/debugger.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 8871bdceb..b40dc4069 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -205,10 +205,11 @@ class Debugger: def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): self.log = log - self.debugpy_client = DebugpyClient(log, debugpy_stream, event_callback) + self.debugpy_client = DebugpyClient(log, debugpy_stream, self._handle_event) self.shell_socket = shell_socket self.session = session self.is_started = False + self.event_callback = event_callback self.started_debug_handlers = {} for msg_type in Debugger.started_debug_msg_types: @@ -227,6 +228,13 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): self.debugpy_port = 0 self.endpoint = None + def _handle_event(self, msg): + if msg['event'] == 'stopped': + self.stopped_threads.append(msg['body']['threadId']) + elif msg['event'] == 'continued': + self.stopped_threads.remove(msg['body']['threadId']) + self.event_callback(msg) + async def _forward_message(self, msg): return await self.debugpy_client.send_dap_request(msg) @@ -302,11 +310,11 @@ async def source(self, message): async def stackTrace(self, message): reply = await self._forward_message(message) - # We stackFrames array has the following content: + # The stackFrames array has the following content: # { frames from the notebook} # ... # { 'id': xxx, 'name': '', ... } <= this is the first frame of the code from the notebook - # { frame from ipykernel } + # { frames from ipykernel } # ... # {'id': yyy, 'name': '', ... } <= this is the first frame of ipykernel code # We want to remove all the frames from ipykernel From 19d189d225273d4c46fa523d2bd08e0415493d26 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 5 Mar 2021 15:46:34 +0100 Subject: [PATCH 0467/1195] Filtered out irrelevant variables --- ipykernel/debugger.py | 10 ++++++++-- ipykernel/ipkernel.py | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index b40dc4069..e2bc8fc49 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -232,7 +232,10 @@ def _handle_event(self, msg): if msg['event'] == 'stopped': self.stopped_threads.append(msg['body']['threadId']) elif msg['event'] == 'continued': - self.stopped_threads.remove(msg['body']['threadId']) + try: + self.stopped_threads.remove(msg['body']['threadId']) + except: + pass self.event_callback(msg) async def _forward_message(self, msg): @@ -324,7 +327,10 @@ async def stackTrace(self, message): return reply def accept_variable(self, variable): - return variable['type'] != 'list' and variable['type'] != 'ZMQExitAutocall' and variable['type'] != 'dict' + cond = variable['type'] != 'list' and variable['type'] != 'ZMQExitAutocall' and variable['type'] != 'dict' + cond = cond and variable['name'] not in ['debugpy', 'get_ipython', '_'] + cond = cond and variable['name'][0:2] != '_i' + return cond async def variables(self, message): reply = await self._forward_message(message) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 1939a54f0..23c06f034 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -153,7 +153,8 @@ def set_parent(self, ident, parent, channel='shell'): about the parent message. """ super(IPythonKernel, self).set_parent(ident, parent, channel) - self.shell.set_parent(parent) + if channel == 'shell': + self.shell.set_parent(parent) def init_metadata(self, parent): """Initialize metadata. From 137112e2daeb9eb00e400342853cf0f8402fb138 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Sun, 7 Mar 2021 17:35:48 +0100 Subject: [PATCH 0468/1195] Fix tests --- ipykernel/control.py | 2 +- ipykernel/kernelapp.py | 8 ++++++-- ipykernel/kernelbase.py | 4 ++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index 57a7ac727..7ac56b7f9 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -9,7 +9,7 @@ class ControlThread(Thread): - def __init__(self, log=None, **kwargs): + def __init__(self, **kwargs): Thread.__init__(self, **kwargs) self.io_loop = IOLoop(make_current=False) self.pydev_do_not_trace = True diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 090e62e05..7bf4a54ba 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -301,7 +301,8 @@ def init_control(self, context): self.debug_shell_socket = context.socket(zmq.DEALER) self.debug_shell_socket.linger = 1000 - self.debug_shell_socket.connect(self.shell_socket.getsockopt(zmq.LAST_ENDPOINT)) + if self.shell_socket.getsockopt(zmq.LAST_ENDPOINT): + self.debug_shell_socket.connect(self.shell_socket.getsockopt(zmq.LAST_ENDPOINT)) if hasattr(zmq, 'ROUTER_HANDOVER'): # set router-handover to workaround zeromq reconnect problems @@ -345,7 +346,10 @@ def close(self): self.iopub_thread.stop() self.iopub_thread.close() - self.debug_shell_socket.close() + if self.debugpy_socket and not self.debugpy_socket.closed: + self.debugpy_socket.close() + if self.debug_shell_socket and not self.debug_shell_socket.closed: + self.debug_shell_socket.close() for channel in ('shell', 'control', 'stdin'): self.log.debug("Closing %s channel", channel) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 0643fcd7b..7b2917d23 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -187,8 +187,8 @@ def __init__(self, **kwargs): self.session) self.control_queue = Queue() - kwargs['control_thread'].io_loop.add_callback(self.poll_control_queue) - + if 'control_thread' in kwargs: + kwargs['control_thread'].io_loop.add_callback(self.poll_control_queue) @gen.coroutine def dispatch_debugpy(self, msg): From 148fab477b1bb3432b9d1f2866efd6a33b4054b9 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 9 Mar 2021 05:59:32 -0600 Subject: [PATCH 0469/1195] Release 6.0.0a0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index b4c4984ae..598840ce1 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, 'dev0') +version_info = (6, 0, 0, 'a0') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 903fcf5da2e0c94054ee30325a932262c93e0c11 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 9 Mar 2021 06:02:52 -0600 Subject: [PATCH 0470/1195] back to dev version --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 598840ce1..fec135c07 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, 'a0') +version_info = (6, 0, 0, '.dev0') __version__ = '.'.join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 508a7dc9bd7fb4b79887ef62b38ded72920a1f36 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 9 Mar 2021 09:16:46 -0800 Subject: [PATCH 0471/1195] Fix version number dev leading dot is automatically added. --- ipykernel/_version.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index fec135c07..0c672a142 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,5 +1,5 @@ -version_info = (6, 0, 0, '.dev0') -__version__ = '.'.join(map(str, version_info[:3])) +version_info = (6, 0, 0, "dev0") +__version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools # confuses which one between the wheel and sdist is the most recent. From f3ae9cd8fdb426ff30f6d0953c5e3d8dff6ba1dd Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 9 Mar 2021 08:59:07 -0800 Subject: [PATCH 0472/1195] Replace import item from ipython_genutils to traitlets. This is to start removing ipython_genutils dependency, the functionality is identical except this one checks its parameter is a str. --- ipykernel/comm/manager.py | 2 +- ipykernel/kernelapp.py | 2 +- ipykernel/pickleutil.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index f1ef1f93c..bf63838e3 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -8,7 +8,7 @@ from traitlets.config import LoggingConfigurable -from ipython_genutils.importstring import import_item +from traitlets.utils.importstring import import_item from traitlets import Instance, Unicode, Dict, Any, default from .comm import Comm diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 7bf4a54ba..ff2fdfd90 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -29,7 +29,7 @@ from traitlets import ( Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type, default ) -from ipython_genutils.importstring import import_item +from traitlets.utils.importstring import import_item from jupyter_core.paths import jupyter_runtime_dir from jupyter_client import write_connection_file from jupyter_client.connect import ConnectionFileMixin diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index c0dd4a807..651df1f3c 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -14,7 +14,7 @@ import pickle from types import FunctionType -from ipython_genutils.importstring import import_item +from traitlets.utils.importstring import import_item from ipython_genutils.py3compat import buffer_to_bytes # This registers a hook when it's imported From 2052290b81e6a161233b8fa1534bdf87833eeea7 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 8 Mar 2021 15:19:35 -0800 Subject: [PATCH 0473/1195] Some removal of ipython_genutils.py3compat. As in some of the other Jupyter Repositories; this start to remove ipython_genutils because of packaging problem in linux distribution (getting rid of nose). This is a step toward that. Note that this changes is slightly stricter in some area where function would previously accept string/bytes and now only accept one; but as far as I can tell are generally only used with one of those since we are Python 3 only. --- ipykernel/connect.py | 3 +-- ipykernel/iostream.py | 11 +++++++---- ipykernel/kernelbase.py | 7 +++---- ipykernel/tests/test_jsonutil.py | 9 +-------- 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 55939aae2..37b029207 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -11,7 +11,6 @@ from IPython.core.profiledir import ProfileDir from IPython.paths import get_ipython_dir from ipython_genutils.path import filefind -from ipython_genutils.py3compat import str_to_bytes import jupyter_client from jupyter_client import write_connection_file @@ -131,7 +130,7 @@ def get_connection_info(connection_file=None, unpack=False, profile=None): if unpack: info = json.loads(info) # ensure key is bytes: - info['key'] = str_to_bytes(info.get('key', '')) + info["key"] = info.get("key", "").encode() return info diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index cb3382604..ab6a2f707 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -24,7 +24,6 @@ from jupyter_client.session import extract_header -from ipython_genutils import py3compat #----------------------------------------------------------------------------- # Globals @@ -288,8 +287,12 @@ class OutStream(TextIOBase): def __init__(self, session, pub_thread, name, pipe=None, echo=None): if pipe is not None: - warnings.warn("pipe argument to OutStream is deprecated and ignored", - DeprecationWarning) + warnings.warn( + "pipe argument to OutStream is deprecated and ignored", + " since ipykernel 4.2.3.", + DeprecationWarning, + stacklevel=2, + ) # This is necessary for compatibility with Python built-in streams self.session = session if not isinstance(pub_thread, IOPubThread): @@ -300,7 +303,7 @@ def __init__(self, session, pub_thread, name, pipe=None, echo=None): pub_thread.start() self.pub_thread = pub_thread self.name = name - self.topic = b'stream.' + py3compat.cast_bytes(name) + self.topic = b"stream." + name.encode() self.parent_header = {} self._master_pid = os.getpid() self._flush_pending = False diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 7b2917d23..c64da9ccf 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -29,7 +29,6 @@ from traitlets.config.configurable import SingletonConfigurable from IPython.core.error import StdinNotImplementedError -from ipython_genutils import py3compat from ipykernel.jsonutil import json_clean from traitlets import ( Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool, @@ -824,7 +823,7 @@ def _topic(self, topic): """prefixed topic for IOPub messages""" base = "kernel.%s" % self.ident - return py3compat.cast_bytes("%s.%s" % (base, topic)) + return ("%s.%s" % (base, topic)).encode() _aborting = Bool(False) @@ -939,8 +938,8 @@ def _input_request(self, prompt, ident, parent, password=False): self.log.warning("Invalid Message:", exc_info=True) try: - value = py3compat.unicode_to_str(reply['content']['value']) - except: + value = reply["content"]["value"] + except Exception: self.log.error("Bad input_reply: %s", parent) value = '' if value == '\x04': diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 2220688a2..4d8ccb604 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -13,7 +13,6 @@ from .. import jsonutil from ..jsonutil import json_clean, encode_images -from ipython_genutils.py3compat import unicode_to_str class MyInt(object): def __int__(self): @@ -80,14 +79,8 @@ def test_encode_images(): encoded2 = json_clean(encode_images(encoded)) assert encoded == encoded2 - # test that we don't double-encode base64 str - b64_str = {} - for key, encoded in encoded.items(): - b64_str[key] = unicode_to_str(encoded) - encoded3 = json_clean(encode_images(b64_str)) - assert encoded3 == b64_str for key, value in fmt.items(): - decoded = a2b_base64(encoded3[key]) + decoded = a2b_base64(encoded[key]) assert decoded == value def test_lambda(): From dd81bab9ba91667bf5d33c566178b7aedeb69cfb Mon Sep 17 00:00:00 2001 From: "Afshin T. Darian" Date: Tue, 16 Mar 2021 12:38:07 +0000 Subject: [PATCH 0474/1195] Move Changelog to Standard Location --- .github/workflows/ci.yml | 5 +++++ .gitignore | 3 +++ docs/changelog.md => CHANGELOG.md | 0 docs/conf.py | 8 +++++++- 4 files changed, 15 insertions(+), 1 deletion(-) rename docs/changelog.md => CHANGELOG.md (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d4e6425e8..096319ac7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,6 +60,11 @@ jobs: timeout-minutes: 30 run: | pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 + - name: Build the docs + run: | + cd docs + pip install -r requirements.txt + make html - name: Build and check the dist files run: | pip install build twine diff --git a/.gitignore b/.gitignore index e483a4411..a986023db 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ __pycache__ data_kernelspec .pytest_cache + +# copied changelog file +docs/changelog.md diff --git a/docs/changelog.md b/CHANGELOG.md similarity index 100% rename from docs/changelog.md rename to CHANGELOG.md diff --git a/docs/conf.py b/docs/conf.py index eefd27475..8b9f63994 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,6 +15,7 @@ import sys import os import shlex +import shutil # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -151,7 +152,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -304,3 +305,8 @@ 'ipython': ('https://ipython.readthedocs.io/en/latest', None), 'jupyter': ('https://jupyter.readthedocs.io/en/latest', None), } + + +def setup(app): + here = os.path.dirname(os.path.abspath(__file__)) + shutil.copy(os.path.join(here, '..', 'CHANGELOG.md'), 'changelog.md') From 54c29d3bfd8c97b190ee7270d42cc86daa58cf1d Mon Sep 17 00:00:00 2001 From: "Afshin T. Darian" Date: Thu, 18 Mar 2021 15:51:09 +0000 Subject: [PATCH 0475/1195] Fix Handling of shell.should_run_async --- ipykernel/ipkernel.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 23c06f034..7b600a2bf 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -282,9 +282,16 @@ def run_cell(*args, **kwargs): # default case: runner is asyncio and asyncio is already running # TODO: this should check every case for "are we inside the runner", # not just asyncio + preprocessing_exc_tuple = None + try: + transformed_cell = self.shell.transform_cell(code) + except Exception: + transformed_cell = code + preprocessing_exc_tuple = sys.exc_info() + if ( _asyncio_runner - and should_run_async(code) + and should_run_async(code, transformed_cell=transformed_cell, preprocessing_exc_tuple=preprocessing_exc_tuple) and shell.loop_runner is _asyncio_runner and asyncio.get_event_loop().is_running() ): @@ -376,14 +383,14 @@ def do_complete(self, code, cursor_pos): def _experimental_do_complete(self, code, cursor_pos): """ - Experimental completions from IPython, using Jedi. + Experimental completions from IPython, using Jedi. """ if cursor_pos is None: cursor_pos = len(code) with _provisionalcompleter(): raw_completions = self.shell.Completer.completions(code, cursor_pos) completions = list(_rectify_completions(code, raw_completions)) - + comps = [] for comp in completions: comps.append(dict( From 12db7f9593def374230b505000f07a1168ef7188 Mon Sep 17 00:00:00 2001 From: "Afshin T. Darian" Date: Fri, 19 Mar 2021 13:38:33 +0000 Subject: [PATCH 0476/1195] Update Python Requirement to 3.7 --- setup.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/setup.py b/setup.py index 927152151..c065cf8f2 100644 --- a/setup.py +++ b/setup.py @@ -3,26 +3,8 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -# the name of the package -name = 'ipykernel' - -#----------------------------------------------------------------------------- -# Minimal Python version sanity check -#----------------------------------------------------------------------------- - import sys import re - -v = sys.version_info -if v[:2] < (3, 5): - error = "ERROR: %s requires Python version 3.5 or above." % name - print(error, file=sys.stderr) - sys.exit(1) - -#----------------------------------------------------------------------------- -# get on with it -#----------------------------------------------------------------------------- - from glob import glob import os import shutil @@ -30,6 +12,9 @@ from setuptools import setup from setuptools.command.bdist_egg import bdist_egg +# the name of the package +name = 'ipykernel' + class bdist_egg_disabled(bdist_egg): """Disabled version of bdist_egg @@ -87,7 +72,7 @@ def run(self): long_description=LONG_DESCRIPTION, platforms="Linux, Mac OS X, Windows", keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], - python_requires='>=3.5', + python_requires='>=3.7', install_requires=[ 'debugpy>=1.0.0', 'ipython>=7.21.0', From d011b034c1009cfe585dc80fa51358dd6b6cb332 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Mon, 22 Mar 2021 10:16:56 +0100 Subject: [PATCH 0477/1195] Move Python-specific bits to ipkernel --- ipykernel/ipkernel.py | 24 +++++++++++++++++++++++- ipykernel/kernelbase.py | 19 +------------------ 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 7b600a2bf..3bbf92aff 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -13,12 +13,13 @@ from IPython.utils.tokenutil import token_at_cursor, line_at_cursor from tornado import gen from traitlets import Instance, Type, Any, List, Bool, observe, observe_compat +from zmq.eventloop.zmqstream import ZMQStream from .comm import CommManager from .kernelbase import Kernel as KernelBase from .zmqshell import ZMQInteractiveShell from .eventloops import _use_appnope - +from .debugger import Debugger from .compiler import XCachingCompiler try: @@ -44,6 +45,8 @@ class IPythonKernel(KernelBase): help="Set this flag to False to deactivate the use of experimental IPython completion APIs.", ).tag(config=True) + debugpy_stream = Instance(ZMQStream, allow_none=True) + user_module = Any() @observe('user_module') @observe_compat @@ -67,6 +70,13 @@ def _user_ns_changed(self, change): def __init__(self, **kwargs): super(IPythonKernel, self).__init__(**kwargs) + # Initialize the Debugger + self.debugger = Debugger(self.log, + self.debugpy_stream, + self._publish_debug_event, + self.debug_shell_socket, + self.session) + # Initialize the InteractiveShell subclass self.shell = self.shell_class.instance(parent=self, profile_dir = self.profile_dir, @@ -140,12 +150,20 @@ def __init__(self, **kwargs): 'file_extension': '.py' } + @gen.coroutine + def dispatch_debugpy(self, msg): + # The first frame is the socket id, we can drop it + frame = msg[1].bytes.decode('utf-8') + self.log.debug("Debugpy received: %s", frame) + self.debugger.tcp_client.receive_dap_frame(frame) + @property def banner(self): return self.shell.banner def start(self): self.shell.exit_now = False + self.debugpy_stream.on_recv(self.dispatch_debugpy, copy=False) super(IPythonKernel, self).start() def set_parent(self, ident, parent, channel='shell'): @@ -381,6 +399,10 @@ def do_complete(self, code, cursor_pos): 'metadata' : {}, 'status' : 'ok'} + @gen.coroutine + def do_debug_request(self, msg): + return (yield self.debugger.process_request(msg)) + def _experimental_do_complete(self, code, cursor_pos): """ Experimental completions from IPython, using Jedi. diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 7b2917d23..3f12dc558 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -12,7 +12,6 @@ import time import uuid import warnings -import asyncio try: # jupyter_client >= 5, use tz-aware now @@ -40,7 +39,6 @@ from ._version import kernel_protocol_version -from .debugger import Debugger class Kernel(SingletonConfigurable): @@ -71,7 +69,6 @@ def shell_streams(self): return [shell_stream] control_stream = Instance(ZMQStream, allow_none=True) - debugpy_stream = Instance(ZMQStream, allow_none=True) debug_shell_socket = Any() @@ -180,23 +177,10 @@ def __init__(self, **kwargs): for msg_type in self.control_msg_types: self.control_handlers[msg_type] = getattr(self, msg_type) - self.debugger = Debugger(self.log, - self.debugpy_stream, - self._publish_debug_event, - self.debug_shell_socket, - self.session) - self.control_queue = Queue() if 'control_thread' in kwargs: kwargs['control_thread'].io_loop.add_callback(self.poll_control_queue) - @gen.coroutine - def dispatch_debugpy(self, msg): - # The first frame is the socket id, we can drop it - frame = msg[1].bytes.decode('utf-8') - self.log.debug("Debugpy received: %s", frame) - self.debugger.tcp_client.receive_dap_frame(frame) - @gen.coroutine def dispatch_control(self, msg): self.control_queue.put_nowait(msg) @@ -440,7 +424,6 @@ def start(self): self.io_loop.add_callback(self.dispatch_queue) self.control_stream.on_recv(self.dispatch_control, copy=False) - self.debugpy_stream.on_recv(self.dispatch_debugpy, copy=False) self.shell_stream.on_recv( partial( @@ -752,7 +735,7 @@ def debug_request(self, stream, ident, parent): @gen.coroutine def do_debug_request(self, msg): - return (yield self.debugger.process_request(msg)) + raise NotImplementedError #--------------------------------------------------------------------------- # Engine methods (DEPRECATED) From ec0c746e87c7e5508e98f00f05d611c7af02b512 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Mon, 22 Mar 2021 13:49:00 +0100 Subject: [PATCH 0478/1195] Flush control stream upon shutdown --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 3f12dc558..6495cbcf0 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -937,4 +937,4 @@ def _at_shutdown(self): if self._shutdown_message is not None: self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown')) self.log.debug("%s", self._shutdown_message) - self.shell_stream.flush(zmq.POLLOUT) + self.control_stream.flush(zmq.POLLOUT) From 0262e5ba0578893e4d9802ac728955a3b2f76999 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 22 Mar 2021 16:40:21 -0500 Subject: [PATCH 0479/1195] Release 6.0.0a1 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 0c672a142..d3ecdede6 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "dev0") +version_info = (6, 0, 0, "a1") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From fdc978c5c1dd691d47764e42091c3d377c5e6526 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 22 Mar 2021 16:40:53 -0500 Subject: [PATCH 0480/1195] back to dev version --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index d3ecdede6..0c672a142 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "a1") +version_info = (6, 0, 0, "dev0") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 7d4403a6d69338bbafb28bf9c463eb7eb9db07f6 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 23 Mar 2021 09:35:14 +0100 Subject: [PATCH 0481/1195] bump Python to 3.8 in readthedocs.yml 3.5 is EOL --- readthedocs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readthedocs.yml b/readthedocs.yml index a2bc2646f..2b684d138 100644 --- a/readthedocs.yml +++ b/readthedocs.yml @@ -1,4 +1,4 @@ python: - version: 3.5 + version: 3.8 pip_install: true requirements_file: docs/requirements.txt From ed7042686c4ceb66405527bfb7952a1552a32360 Mon Sep 17 00:00:00 2001 From: Glen Takahashi Date: Tue, 23 Mar 2021 11:21:18 -0700 Subject: [PATCH 0482/1195] Add release note to 5.5.0 about `stop_on_error_timeout` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c9a7792e..89f323292 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### 5.5.0 * kernelspec: ensure path is writable before writing `kernel.json`. [#593](https://github.com/ipython/ipykernel/pull/593) * Add `configure_inline_support` and call it in the shell. [#590](https://github.com/ipython/ipykernel/pull/590) +* Fix `stop_on_error_timeout` to now properly abort `execute_request`'s that fall within the timeout after an error. [#572](https://github.com/ipython/ipykernel/pull/572) ## 5.4 From 1d4aaea518c8e2f4a3547b6a5bd9b2373d344de3 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Wed, 24 Mar 2021 15:34:49 +0100 Subject: [PATCH 0483/1195] Attempt longer timeout --- ipykernel/tests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index b16d10507..0e5129295 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -18,7 +18,7 @@ STARTUP_TIMEOUT = 60 -TIMEOUT = 15 +TIMEOUT = 60 KM = None KC = None From 257713dfc30c51e9fe65207b8b7a14207a200c3f Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Thu, 25 Mar 2021 16:23:34 +0100 Subject: [PATCH 0484/1195] Specify ipykernel in kernelspec --- ipykernel/kernelspec.py | 2 +- ipykernel/tests/test_kernelspec.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index deda6ebec..911df5f8b 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -53,7 +53,7 @@ def get_kernel_dict(extra_arguments=None): """Construct dict for kernel.json""" return { 'argv': make_ipkernel_cmd(extra_arguments=extra_arguments), - 'display_name': 'Python %i' % sys.version_info[0], + 'display_name': 'Python %i (ipykernel)' % sys.version_info[0], 'language': 'python', 'metadata': { 'debugger': True} } diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index 6e9b7f0f8..a0a9b5c47 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -41,7 +41,7 @@ def test_make_ipkernel_cmd(): def assert_kernel_dict(d): assert d['argv'] == make_ipkernel_cmd() - assert d['display_name'] == 'Python %i' % sys.version_info[0] + assert d['display_name'] == 'Python %i (ipykernel)' % sys.version_info[0] assert d['language'] == 'python' @@ -53,7 +53,7 @@ def test_get_kernel_dict(): def assert_kernel_dict_with_profile(d): nt.assert_equal(d['argv'], make_ipkernel_cmd( extra_arguments=["--profile", "test"])) - assert d['display_name'] == 'Python %i' % sys.version_info[0] + assert d['display_name'] == 'Python %i (ipykernel)' % sys.version_info[0] assert d['language'] == 'python' From b3da331a7285dfc616ae78236dc2fa6b1e35175a Mon Sep 17 00:00:00 2001 From: "Afshin T. Darian" Date: Fri, 26 Mar 2021 18:58:44 +0000 Subject: [PATCH 0485/1195] Follow up DeprecationWarning Fix --- ipykernel/ipkernel.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 3bbf92aff..8bd8433a6 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -309,11 +309,17 @@ def run_cell(*args, **kwargs): if ( _asyncio_runner - and should_run_async(code, transformed_cell=transformed_cell, preprocessing_exc_tuple=preprocessing_exc_tuple) and shell.loop_runner is _asyncio_runner and asyncio.get_event_loop().is_running() + and should_run_async(code, transformed_cell=transformed_cell, preprocessing_exc_tuple=preprocessing_exc_tuple) ): - coro = run_cell(code, store_history=store_history, silent=silent) + coro = run_cell( + code, + store_history=store_history, + silent=silent, + transformed_cell=transformed_cell, + preprocessing_exc_tuple=preprocessing_exc_tuple + ) coro_future = asyncio.ensure_future(coro) with self._cancel_on_sigint(coro_future): From 36ba9a9b9612040791633b10a3e8bf05dc9554bb Mon Sep 17 00:00:00 2001 From: Matthew Seal Date: Sun, 28 Mar 2021 18:33:47 -0700 Subject: [PATCH 0486/1195] Changed default timeout to 0.0 seconds for stop_on_error_timeout --- CHANGELOG.md | 2 ++ ipykernel/kernelbase.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 89f323292..79e48f395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changes in IPython kernel +* Set `stop_on_error_timeout` default to 0.0 matching pre 5.5.0 default behavior with correctly working flag from 5.5.0. + ## 5.5 ### 5.5.0 diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 3409ba8f8..3e99a36a5 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -120,7 +120,7 @@ def _default_ident(self): _poll_interval = Float(0.01).tag(config=True) stop_on_error_timeout = Float( - 0.1, + 0.0, config=True, help="""time (in seconds) to wait for messages to arrive when aborting queued requests after an error. From 2946dcb8e6e972bbdaed3acbd7b36c30a6590b99 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 30 Mar 2021 18:02:35 +0200 Subject: [PATCH 0487/1195] Implemented inspectVariables request --- ipykernel/debugger.py | 64 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index e2bc8fc49..67df688c8 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,5 +1,6 @@ import logging import os +import re import zmq from zmq.utils import jsonapi @@ -9,6 +10,7 @@ from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed) +from IPython.core.getipython import get_ipython import debugpy class DebugpyMessageQueue: @@ -326,17 +328,39 @@ async def stackTrace(self, message): reply['body']['stackFrames'] = reply['body']['stackFrames'][:module_idx+1] return reply - def accept_variable(self, variable): - cond = variable['type'] != 'list' and variable['type'] != 'ZMQExitAutocall' and variable['type'] != 'dict' - cond = cond and variable['name'] not in ['debugpy', 'get_ipython', '_'] - cond = cond and variable['name'][0:2] != '_i' + def accept_variable(self, variable_name): + forbid_list = [ + '__name__', + '__doc__', + '__package__', + '__loader__', + '__spec__', + '__annotations__', + '__builtins__', + '__builtin__', + '__display__', + 'get_ipython', + 'debugpy', + 'exit', + 'quit', + 'In', + 'Out', + '_oh', + '_dh', + '_', + '__', + '___' + ] + cond = variable_name not in forbid_list + cond = cond and not bool(re.search(r'^_\d', variable_name)) + cond = cond and variable_name[0:2] != '_i' return cond async def variables(self, message): reply = await self._forward_message(message) # TODO : check start and count arguments work as expected in debugpy reply['body']['variables'] = \ - [var for var in reply['body']['variables'] if self.accept_variable(var)] + [var for var in reply['body']['variables'] if self.accept_variable(var['name'])] return reply async def attach(self, message): @@ -383,8 +407,24 @@ async def debugInfo(self, message): return reply async def inspectVariables(self, message): - # TODO - return {} + var_list = [] + for k, v in get_ipython().user_ns.items(): + if self.accept_variable(k): + var_list.append({ + 'name': k, + 'value': v, + 'variablesReference': 0 + }) + reply = { + 'type': 'response', + 'request_seq': message['seq'], + 'success': True, + 'command': message['command'], + 'body': { + 'variables': var_list + } + } + return reply async def process_request(self, message): reply = {} @@ -398,11 +438,11 @@ async def process_request(self, message): self.log.info('The debugger has started') else: reply = { - 'command', 'initialize', - 'request_seq', message['seq'], - 'seq', 3, - 'success', False, - 'type', 'response' + 'command': 'initialize', + 'request_seq': message['seq'], + 'seq': 3, + 'success': False, + 'type': 'response' } handler = self.static_debug_handlers.get(message['command'], None) From a17b300b0cbb92333f66fb2353d9e6bbd67b36b6 Mon Sep 17 00:00:00 2001 From: "Afshin T. Darian" Date: Tue, 30 Mar 2021 17:42:04 +0100 Subject: [PATCH 0488/1195] Run GitHub Actions on all branches --- .github/workflows/ci.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 096319ac7..875fdc2ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,6 @@ name: ipykernel tests -on: - push: - branches: [master] - pull_request: - branches: [master] +on: [push, pull_request] jobs: build: From 7ea45723bedb374791de0b7136a3da57d99ad3df Mon Sep 17 00:00:00 2001 From: "Mark E. Haase" Date: Tue, 30 Mar 2021 21:06:39 -0400 Subject: [PATCH 0489/1195] Update Trio mode for compatibility with Trio >= 0.18.0 The Trio package renamed their trio.hazmat module to trio.lowlevel. --- ipykernel/trio_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index 4ba1da196..d78e516aa 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -39,7 +39,7 @@ def log_nursery_exc(exc): exc) async def trio_main(): - self._trio_token = trio.hazmat.current_trio_token() + self._trio_token = trio.lowlevel.current_trio_token() async with trio.open_nursery() as nursery: # TODO This hack prevents the nursery from cancelling all child # tasks when an uncaught exception occurs, but it's ugly. From c0788c737aeabdf2d617bae8f2c5fc7f338684ec Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 31 Mar 2021 18:12:30 +0200 Subject: [PATCH 0490/1195] Added 'type' field to variables returned by inspectVariables request --- ipykernel/debugger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 67df688c8..5d5116995 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -413,6 +413,7 @@ async def inspectVariables(self, message): var_list.append({ 'name': k, 'value': v, + 'type': str(type(v))[8:-2], 'variablesReference': 0 }) reply = { From 1647f838a2f520aedfab0d85a785c3c5c22abb16 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 1 Apr 2021 06:26:13 -0500 Subject: [PATCH 0491/1195] Release 6.0.0a2 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 0c672a142..1ec1d6fca 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "dev0") +version_info = (6, 0, 0, "a2") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 7e857569131aa2c64957ceda550c8730f4b9f3c0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 1 Apr 2021 06:26:43 -0500 Subject: [PATCH 0492/1195] back to dev version --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 1ec1d6fca..0c672a142 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "a2") +version_info = (6, 0, 0, "dev0") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From f5c3094d6b5dd4db7a1c59ebb4e92a2103f7a302 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 9 Apr 2021 16:14:55 +0200 Subject: [PATCH 0493/1195] Make less use of ipython_genutils --- ipykernel/ipkernel.py | 3 +-- ipykernel/zmqshell.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 8bd8433a6..f9125b992 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -9,7 +9,6 @@ import sys from IPython.core import release -from ipython_genutils.py3compat import safe_unicode from IPython.utils.tokenutil import token_at_cursor, line_at_cursor from tornado import gen from traitlets import Instance, Type, Any, List, Bool, observe, observe_compat @@ -351,7 +350,7 @@ def run_cell(*args, **kwargs): reply_content.update({ 'traceback': shell._last_traceback or [], 'ename': str(type(err).__name__), - 'evalue': safe_unicode(err), + 'evalue': str(err), }) # FIXME: deprecated piece for ipyparallel (remove in 5.0): diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 8c9cfecce..2dcb8b349 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -40,7 +40,6 @@ from IPython.utils import openpy from ipykernel.jsonutil import json_clean, encode_images from IPython.utils.process import arg_split, system -from ipython_genutils import py3compat from traitlets import ( Instance, Type, Dict, CBool, CBytes, Any, default, observe ) @@ -546,7 +545,7 @@ def _showtraceback(self, etype, evalue, stb): exc_content = { 'traceback' : stb, 'ename' : str(etype.__name__), - 'evalue' : py3compat.safe_unicode(evalue), + 'evalue' : str(evalue), } dh = self.displayhook From 5e9668f9c3f4515f6fe520710164424309eb9ee1 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 12 Apr 2021 10:25:06 +0200 Subject: [PATCH 0494/1195] Catch StopIteration exception in stackFrames --- ipykernel/debugger.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 5d5116995..db5fbc841 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -315,17 +315,21 @@ async def source(self, message): async def stackTrace(self, message): reply = await self._forward_message(message) - # The stackFrames array has the following content: + # The stackFrames array can have the following content: # { frames from the notebook} # ... # { 'id': xxx, 'name': '', ... } <= this is the first frame of the code from the notebook # { frames from ipykernel } # ... # {'id': yyy, 'name': '', ... } <= this is the first frame of ipykernel code - # We want to remove all the frames from ipykernel - sf_list = reply['body']['stackFrames'] - module_idx = len(sf_list) - next(i for i, v in enumerate(reversed(sf_list), 1) if v['name'] == '' and i != 1) - reply['body']['stackFrames'] = reply['body']['stackFrames'][:module_idx+1] + # or only the frames from the notebook. + # We want to remove all the frames from ipykernel when they are present. + try: + sf_list = reply['body']['stackFrames'] + module_idx = len(sf_list) - next(i for i, v in enumerate(reversed(sf_list), 1) if v['name'] == '' and i != 1) + reply['body']['stackFrames'] = reply['body']['stackFrames'][:module_idx+1] + except StopIteration: + pass return reply def accept_variable(self, variable_name): From 820c502bb5c02bd720cbe3b7710c94fd922ced03 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Mon, 12 Apr 2021 19:24:33 +0200 Subject: [PATCH 0495/1195] Release 6.0.0a3 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 0c672a142..e76229ad9 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "dev0") +version_info = (6, 0, 0, "a3") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 9f78f556321b54aee3bb42bd273439e6ab76b720 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Mon, 12 Apr 2021 19:41:35 +0200 Subject: [PATCH 0496/1195] Use channel get_msg helper method --- ipykernel/inprocess/client.py | 12 ++++++++++++ ipykernel/inprocess/tests/test_kernel.py | 4 ++-- ipykernel/tests/test_kernel.py | 20 ++++++++++---------- ipykernel/tests/test_message_spec.py | 14 +++++++------- ipykernel/tests/utils.py | 10 +++++----- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index e6b3b358a..37edf8064 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -178,6 +178,18 @@ def _dispatch_to_kernel(self, msg): idents, reply_msg = self.session.recv(stream, copy=False) self.shell_channel.call_handlers_later(reply_msg) + def get_shell_msg(self, block=True, timeout=None): + return self.shell_channel.get_msg(block, timeout) + + def get_iopub_msg(self, block=True, timeout=None): + return self.iopub_channel.get_msg(block, timeout) + + def get_stdin_msg(self, block=True, timeout=None): + return self.stdin_channel.get_msg(block, timeout) + + def get_control_msg(self, block=True, timeout=None): + return self.control_channel.get_msg(block, timeout) + #----------------------------------------------------------------------------- # ABC Registration diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 92c8ea538..4d42e797b 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -65,7 +65,7 @@ def test_pylab(self): """Does %pylab work in the in-process kernel?""" kc = self.kc kc.execute('%pylab') - out, err = assemble_output(kc.iopub_channel) + out, err = assemble_output(kc.get_iopub_msg) self.assertIn('matplotlib', out) def test_raw_input(self): @@ -96,7 +96,7 @@ def test_stdout(self): kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) kernel.frontends.append(kc) kc.execute('print("bar")') - out, err = assemble_output(kc.iopub_channel) + out, err = assemble_output(kc.get_iopub_msg) assert out == 'bar\n' def test_getpass_stream(self): diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index bdd74e24e..334748d1a 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -29,7 +29,7 @@ def _check_master(kc, expected=True, stream="stdout"): execute(kc=kc, code="import sys") flush_channels(kc) msg_id, content = execute(kc=kc, code="print (sys.%s._is_master_process())" % stream) - stdout, stderr = assemble_output(kc.iopub_channel) + stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout.strip() == repr(expected) @@ -46,7 +46,7 @@ def test_simple_print(): with kernel() as kc: iopub = kc.iopub_channel msg_id, content = execute(kc=kc, code="print ('hi')") - stdout, stderr = assemble_output(iopub) + stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == 'hi\n' assert stderr == '' _check_master(kc, expected=True) @@ -56,7 +56,7 @@ def test_sys_path(): """test that sys.path doesn't get messed up by default""" with kernel() as kc: msg_id, content = execute(kc=kc, code="import sys; print(repr(sys.path))") - stdout, stderr = assemble_output(kc.iopub_channel) + stdout, stderr = assemble_output(kc.get_iopub_msg) # for error-output on failure sys.stderr.write(stderr) @@ -69,7 +69,7 @@ def test_sys_path_profile_dir(): with new_kernel(['--profile-dir', locate_profile('default')]) as kc: msg_id, content = execute(kc=kc, code="import sys; print(repr(sys.path))") - stdout, stderr = assemble_output(kc.iopub_channel) + stdout, stderr = assemble_output(kc.get_iopub_msg) # for error-output on failure sys.stderr.write(stderr) @@ -100,7 +100,7 @@ def test_subprocess_print(): ]) msg_id, content = execute(kc=kc, code=code) - stdout, stderr = assemble_output(iopub) + stdout, stderr = assemble_output(kc.get_iopub_msg) nt.assert_equal(stdout.count("hello"), np, stdout) for n in range(np): nt.assert_equal(stdout.count(str(n)), 1, stdout) @@ -124,7 +124,7 @@ def test_subprocess_noprint(): ]) msg_id, content = execute(kc=kc, code=code) - stdout, stderr = assemble_output(iopub) + stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == '' assert stderr == '' @@ -150,7 +150,7 @@ def test_subprocess_error(): ]) msg_id, content = execute(kc=kc, code=code) - stdout, stderr = assemble_output(iopub) + stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == '' assert "ValueError" in stderr @@ -176,7 +176,7 @@ def test_raw_input(): kc.input(text) reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) assert reply['content']['status'] == 'ok' - stdout, stderr = assemble_output(iopub) + stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == text + "\n" @@ -318,14 +318,14 @@ def test_unc_paths(): kc.execute("cd {0:s}".format(unc_file_path)) reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) assert reply['content']['status'] == 'ok' - out, err = assemble_output(iopub) + out, err = assemble_output(kc.get_iopub_msg) assert unc_file_path in out flush_channels(kc) kc.execute(code="ls") reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) assert reply['content']['status'] == 'ok' - out, err = assemble_output(iopub) + out, err = assemble_output(kc.get_iopub_msg) assert 'unc.txt' in out kc.execute(code="cd") diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index a142165f1..e700553ff 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -288,21 +288,21 @@ def test_execute_silent(): msg_id, reply = execute(code='x=1', silent=True) # flush status=idle - status = KC.iopub_channel.get_msg(timeout=TIMEOUT) + status = KC.get_iopub_msg(timeout=TIMEOUT) validate_message(status, 'status', msg_id) assert status['content']['execution_state'] == 'idle' - nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1) + nt.assert_raises(Empty, KC.get_iopub_msg, timeout=0.1) count = reply['execution_count'] msg_id, reply = execute(code='x=2', silent=True) # flush status=idle - status = KC.iopub_channel.get_msg(timeout=TIMEOUT) + status = KC.get_iopub_msg(timeout=TIMEOUT) validate_message(status, 'status', msg_id) assert status['content']['execution_state'] == 'idle' - nt.assert_raises(Empty, KC.iopub_channel.get_msg, timeout=0.1) + nt.assert_raises(Empty, KC.get_iopub_msg, timeout=0.1) count_2 = reply['execution_count'] assert count_2 == count @@ -314,7 +314,7 @@ def test_execute_error(): assert reply['status'] == 'error' assert reply['ename'] == 'ZeroDivisionError' - error = KC.iopub_channel.get_msg(timeout=TIMEOUT) + error = KC.get_iopub_msg(timeout=TIMEOUT) validate_message(error, 'error', msg_id) @@ -560,7 +560,7 @@ def test_stream(): msg_id, reply = execute("print('hi')") - stdout = KC.iopub_channel.get_msg(timeout=TIMEOUT) + stdout = KC.get_iopub_msg(timeout=TIMEOUT) validate_message(stdout, 'stream', msg_id) content = stdout['content'] assert content['text'] == 'hi\n' @@ -571,7 +571,7 @@ def test_display_data(): msg_id, reply = execute("from IPython.display import display; display(1)") - display = KC.iopub_channel.get_msg(timeout=TIMEOUT) + display = KC.get_iopub_msg(timeout=TIMEOUT) validate_message(display, 'display_data', parent=msg_id) data = display['content']['data'] assert data['text/plain'] == '1' diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 0e5129295..920261e84 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -43,10 +43,10 @@ def flush_channels(kc=None): if kc is None: kc = KC - for channel in (kc.shell_channel, kc.iopub_channel): + for get_msg in (kc.get_shell_msg, kc.get_iopub_msg): while True: try: - msg = channel.get_msg(block=True, timeout=0.1) + msg = get_msg(block=True, timeout=0.1) except Empty: break else: @@ -149,12 +149,12 @@ def new_kernel(argv=None): kwargs['extra_arguments'] = argv return manager.run_kernel(**kwargs) -def assemble_output(iopub): +def assemble_output(get_msg): """assemble stdout/err from an execution""" stdout = '' stderr = '' while True: - msg = iopub.get_msg(block=True, timeout=1) + msg = get_msg(block=True, timeout=1) msg_type = msg['msg_type'] content = msg['content'] if msg_type == 'status' and content['execution_state'] == 'idle': @@ -174,7 +174,7 @@ def assemble_output(iopub): def wait_for_idle(kc): while True: - msg = kc.iopub_channel.get_msg(block=True, timeout=1) + msg = kc.get_iopub_msg(block=True, timeout=1) msg_type = msg['msg_type'] content = msg['content'] if msg_type == 'status' and content['execution_state'] == 'idle': From ae2f441a2d0903c2394d7aa6e606c58b47f5ab05 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 5 Apr 2021 09:17:25 -0700 Subject: [PATCH 0497/1195] Try to capture stdout and err going directly to 1/2 filedescriptor. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This try to fix a long standing issue that stdout and stderr going directly to the filedescriptor are not shown in notebooks. This is annoying when using wrappers around c-libraries, or calling system commands as those will not be seen from within notebook. Here we redirect and split the filedescriptor and watch those in threads and redirect both to the original FD (terminal), and ZMQ (notebook). Thus output sent to fd 1 & 2 will be shown BOTH in terminal that launched the notebook server and in notebook themselves. One of the concern is that now logs and errors internal to ipykernel may appear in the notebook themselves, so may confuse user; though these should be limited to error and debug; and we can workaround this by setting the log handler to not be stdout/err. This still seem like a big hack to me, and I don't like thread. I did not manage to make reading the FD non-blocking; so this cannot be put in the io-thread – at least I'm not sure how. So adds 2 extra threads to the kernel. This might need to be turn off by default for now until further testing. Locally this seem to work with things like: - os.system("echo HELLO WORLD") - c-extensions writing directly to fd 1 and 2 (when properly flushed). I have no clue how filedescriptor work on windows, so this only change behavior on linux and mac. --- ipykernel/iostream.py | 73 +++++++++++++++++++++++++++++++++++++++-- ipykernel/kernelbase.py | 6 ++-- 2 files changed, 73 insertions(+), 6 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index ab6a2f707..716126b78 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -12,6 +12,7 @@ import threading import warnings from weakref import WeakSet +import traceback from io import StringIO, TextIOBase import zmq @@ -285,7 +286,44 @@ class OutStream(TextIOBase): topic = None encoding = 'UTF-8' - def __init__(self, session, pub_thread, name, pipe=None, echo=None): + def _watch_pipe_fd(self): + """ + We've redirected standards steams 0 and 1 into a pipe. + + We need to watch in a thread and redirect them to the right places. + + 1) the ZMQ channels to show in notebook interfaces, + 2) the original stdout/err, to capture errors in terminals. + + We cannot schedule this on the ioloop thread, as this might be blocking. + + """ + + try: + bts = os.read(self._fid, 1000) + while bts and self._should_watch: + self.write(bts.decode()) + os.write(self._original_stdstream_copy, bts) + bts = os.read(self._fid, 1000) + except Exception: + self._exc = sys.exc_info() + + def __init__( + self, session, pub_thread, name, pipe=None, echo=None, *, watchfd=True + ): + """ + Parameters + ---------- + name : str {'stderr', 'stdout'} + the name of the standard stream to replace + watchfd : bool (default, True) + Watch the file descripttor corresponding to the replaced stream. + This is useful if you know some underlying code will write directly + the file descriptor by its number. It will spawn a watching thread, + that will swap the give file descriptor for a pipe, read from the + pipe, and insert this into the current Stream. + + """ if pipe is not None: warnings.warn( "pipe argument to OutStream is deprecated and ignored", @@ -297,8 +335,12 @@ def __init__(self, session, pub_thread, name, pipe=None, echo=None): self.session = session if not isinstance(pub_thread, IOPubThread): # Backward-compat: given socket, not thread. Wrap in a thread. - warnings.warn("OutStream should be created with IOPubThread, not %r" % pub_thread, - DeprecationWarning, stacklevel=2) + warnings.warn( + "Since IPykernel 4.3, OutStream should be created with " + "IOPubThread, not %r" % pub_thread, + DeprecationWarning, + stacklevel=2, + ) pub_thread = IOPubThread(pub_thread) pub_thread.start() self.pub_thread = pub_thread @@ -312,12 +354,31 @@ def __init__(self, session, pub_thread, name, pipe=None, echo=None): self._new_buffer() self.echo = None + if watchfd and ( + sys.platform.startswith("linux") or sys.platform.startswith("darwin") + ): + self._should_watch = True + self._setup_stream_redirects(name) + if echo: if hasattr(echo, 'read') and hasattr(echo, 'write'): self.echo = echo else: raise ValueError("echo argument must be a file like object") + def _setup_stream_redirects(self, name): + pr, pw = os.pipe() + fno = getattr(sys, name).fileno() + self._original_stdstream_copy = os.dup(fno) + os.dup2(pw, fno) + + self._fid = pr + + self._exc = None + self.watch_fd_thread = threading.Thread(target=self._watch_pipe_fd) + self.watch_fd_thread.daemon = True + self.watch_fd_thread.start() + def _is_master_process(self): return os.getpid() == self._master_pid @@ -325,6 +386,12 @@ def set_parent(self, parent): self.parent_header = extract_header(parent) def close(self): + if sys.platform.startswith("linux") or sys.platform.startswith("darwin"): + self._should_watch = False + self.watch_fd_thread.join() + if self._exc: + etype, value, tb = self._exc + traceback.print_exception(etype, value, tb) self.pub_thread = None @property diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 3e99a36a5..e071c6936 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -825,9 +825,9 @@ def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" self.log.info("Aborting:") self.log.info("%s", msg) - reply_type = msg['header']['msg_type'].rsplit('_', 1)[0] + '_reply' - status = {'status': 'aborted'} - md = {'engine': self.ident} + reply_type = msg["header"]["msg_type"].rsplit("_", 1)[0] + "_reply" + status = {"status": "aborted"} + md = {"engine": self.ident} md.update(status) self.session.send( stream, reply_type, metadata=md, From f1b8a56747f27c7e2bda9f4a175b12b0cc80a2ca Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 5 Apr 2021 14:05:50 -0700 Subject: [PATCH 0498/1195] Modify all log handlers to route messages directly screen. Bypass the original fd 2 for stderr, and use the new piped one. --- ipykernel/kernelapp.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index ff2fdfd90..a967ccbde 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -10,6 +10,8 @@ import signal import traceback import logging +from io import TextIOWrapper, FileIO +from logging import StreamHandler import tornado from tornado import ioloop @@ -414,9 +416,24 @@ def init_io(self): echo=e_stdout) if sys.stderr is not None: sys.stderr.flush() - sys.stderr = outstream_factory(self.session, self.iopub_thread, - 'stderr', - echo=e_stderr) + sys.stderr = outstream_factory( + self.session, self.iopub_thread, "stderr", echo=e_stderr + ) + self.log.error("this %s", hasattr(sys.stderr, "_original_stdstream_copy")) + if hasattr(sys.stderr, "_original_stdstream_copy"): + + for handler in self.log.handlers: + if isinstance(handler, StreamHandler) and ( + handler.stream.buffer.fileno() == 2 + ): + self.log.debug( + "Seeing logger to stderr, rerouting to raw filedescriptor." + ) + + handler.stream = TextIOWrapper( + FileIO(sys.stderr._original_stdstream_copy, "w") + ) + self.log.error("Redirected to raw FD.") if self.displayhook_class: displayhook_factory = import_item(str(self.displayhook_class)) self.displayhook = displayhook_factory(self.session, self.iopub_socket) From 799722ec83c8d4e1f372ad97ccbc224b9bbbce93 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 12 Apr 2021 08:19:52 -0700 Subject: [PATCH 0499/1195] add tests --- ipykernel/inprocess/tests/test_kernel.py | 21 +++++++++++++++++++++ ipykernel/tests/test_kernel.py | 14 +++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 4d42e797b..1b84aa12a 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -99,6 +99,27 @@ def test_stdout(self): out, err = assemble_output(kc.get_iopub_msg) assert out == 'bar\n' + @pytest.mark.skipif( + sys.platform == 'win32', + reason="not ment to work on windows" + ) + def test_capfd(self): + """ Does correctly capture fd + """ + kernel = InProcessKernel() + + with capture_output() as io: + kernel.shell.run_cell('print("foo")') + assert io.stdout == 'foo\n' + + kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) + kernel.frontends.append(kc) + kc.execute('import os') + kc.execute('os.system("echo capfd")') + out, err = assemble_output(kc.iopub_channel) + assert out == 'capfd\n' + + def test_getpass_stream(self): "Tests that kernel getpass accept the stream parameter" kernel = InProcessKernel() diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 334748d1a..3d55f683b 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -28,7 +28,7 @@ def _check_master(kc, expected=True, stream="stdout"): execute(kc=kc, code="import sys") flush_channels(kc) - msg_id, content = execute(kc=kc, code="print (sys.%s._is_master_process())" % stream) + msg_id, content = execute(kc=kc, code="print(sys.%s._is_master_process())" % stream) stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout.strip() == repr(expected) @@ -52,6 +52,18 @@ def test_simple_print(): _check_master(kc, expected=True) +@pytest.mark.skipif(sys.platform == "win32", reason="Not meant to work on windows") +def test_capture_fd(): + """simple print statement in kernel""" + with kernel() as kc: + iopub = kc.iopub_channel + msg_id, content = execute(kc=kc, code="import os; os.system('echo capsys')") + stdout, stderr = assemble_output(iopub) + assert stdout == "capsys\n" + assert stderr == "" + _check_master(kc, expected=True) + + def test_sys_path(): """test that sys.path doesn't get messed up by default""" with kernel() as kc: From 12c8ecefdabda937de183373e6ba32ae39bea0d6 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 12 Apr 2021 08:40:25 -0700 Subject: [PATCH 0500/1195] Make sure replaced streams have fileno. This is important for example for subprocess that will peak at the filedescriptor. --- ipykernel/iostream.py | 10 ++++++++++ ipykernel/kernelapp.py | 2 -- ipykernel/tests/test_kernel.py | 17 ++++++++++++++++- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 716126b78..1948fc994 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -286,6 +286,16 @@ class OutStream(TextIOBase): topic = None encoding = 'UTF-8' + + def fileno(self): + """ + Things like subprocess will peak and write to the fileno() of stderr/stdout. + """ + if getattr(self, '_original_stdstream_copy', None) is not None: + return self._original_stdstream_copy + else: + raise UnsupportedOperation('fileno') + def _watch_pipe_fd(self): """ We've redirected standards steams 0 and 1 into a pipe. diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index a967ccbde..78fa0e70c 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -419,7 +419,6 @@ def init_io(self): sys.stderr = outstream_factory( self.session, self.iopub_thread, "stderr", echo=e_stderr ) - self.log.error("this %s", hasattr(sys.stderr, "_original_stdstream_copy")) if hasattr(sys.stderr, "_original_stdstream_copy"): for handler in self.log.handlers: @@ -433,7 +432,6 @@ def init_io(self): handler.stream = TextIOWrapper( FileIO(sys.stderr._original_stdstream_copy, "w") ) - self.log.error("Redirected to raw FD.") if self.displayhook_class: displayhook_factory = import_item(str(self.displayhook_class)) self.displayhook = displayhook_factory(self.session, self.iopub_socket) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 3d55f683b..4e4b286d2 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -45,7 +45,7 @@ def test_simple_print(): """simple print statement in kernel""" with kernel() as kc: iopub = kc.iopub_channel - msg_id, content = execute(kc=kc, code="print ('hi')") + msg_id, content = execute(kc=kc, code="print('hi')") stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == 'hi\n' assert stderr == '' @@ -64,6 +64,21 @@ def test_capture_fd(): _check_master(kc, expected=True) +@pytest.mark.skipif(sys.platform == "win32", reason="Not meant to work on windows") +def test_subprocess_peek_at_stream_fileno(): + """""" + with kernel() as kc: + iopub = kc.iopub_channel + msg_id, content = execute( + kc=kc, + code="import subprocess, sys; subprocess.run(['python', '-c', 'import os; os.system(\"echo CAP1\"); print(\"CAP2\")'], stderr=sys.stderr)", + ) + stdout, stderr = assemble_output(iopub) + assert stdout == "CAP1\nCAP2\n" + assert stderr == "" + _check_master(kc, expected=True) + + def test_sys_path(): """test that sys.path doesn't get messed up by default""" with kernel() as kc: From 310f0cd8214c4b305b72098b0e920a9be83c0bfb Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 12 Apr 2021 09:06:43 -0700 Subject: [PATCH 0501/1195] dont capture with pytest --- ipykernel/inprocess/tests/test_kernel.py | 5 ++--- ipykernel/iostream.py | 9 +++++++-- ipykernel/tests/test_kernel.py | 4 +++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 1b84aa12a..6728cbdb4 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -99,9 +99,8 @@ def test_stdout(self): out, err = assemble_output(kc.get_iopub_msg) assert out == 'bar\n' - @pytest.mark.skipif( - sys.platform == 'win32', - reason="not ment to work on windows" + @pytest.mark.skip( + reason="Currently don't capture during test as pytest does its own capturing" ) def test_capfd(self): """ Does correctly capture fd diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 1948fc994..f0766a433 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -14,6 +14,7 @@ from weakref import WeakSet import traceback from io import StringIO, TextIOBase +import os import zmq if zmq.pyzmq_version_info() >= (17, 0): @@ -364,9 +365,13 @@ def __init__( self._new_buffer() self.echo = None - if watchfd and ( - sys.platform.startswith("linux") or sys.platform.startswith("darwin") + if ( + watchfd + and (sys.platform.startswith("linux") or sys.platform.startswith("darwin")) + and ("PYTEST_CURRENT_TEST" not in os.environ) ): + # Pytest set its own capture. Dont redirect from within pytest. + self._should_watch = True self._setup_stream_redirects(name) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 4e4b286d2..4b9788619 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -52,7 +52,9 @@ def test_simple_print(): _check_master(kc, expected=True) -@pytest.mark.skipif(sys.platform == "win32", reason="Not meant to work on windows") +@pytest.mark.skip( + reason="Currently don't capture during test as pytest does its own capturing" +) def test_capture_fd(): """simple print statement in kernel""" with kernel() as kc: From 192fc6caddb123ef2c10b1d6f88f574f7d484dfb Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 12 Apr 2021 09:14:31 -0700 Subject: [PATCH 0502/1195] fix test --- ipykernel/iostream.py | 3 ++- ipykernel/tests/test_kernel.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index f0766a433..063db479a 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -14,6 +14,7 @@ from weakref import WeakSet import traceback from io import StringIO, TextIOBase +import io import os import zmq @@ -295,7 +296,7 @@ def fileno(self): if getattr(self, '_original_stdstream_copy', None) is not None: return self._original_stdstream_copy else: - raise UnsupportedOperation('fileno') + raise io.UnsupportedOperation("fileno") def _watch_pipe_fd(self): """ diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 4b9788619..6c918aa88 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -66,7 +66,9 @@ def test_capture_fd(): _check_master(kc, expected=True) -@pytest.mark.skipif(sys.platform == "win32", reason="Not meant to work on windows") +@pytest.mark.skip( + reason="Currently don't capture during test as pytest does its own capturing" +) def test_subprocess_peek_at_stream_fileno(): """""" with kernel() as kc: From 79496f0db1ca3a698d596074e341f45d014079c1 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 13 Apr 2021 16:30:09 -0700 Subject: [PATCH 0503/1195] reformat --- ipykernel/inprocess/tests/test_kernel.py | 10 ++++------ ipykernel/iostream.py | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 6728cbdb4..c2904a006 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -103,21 +103,19 @@ def test_stdout(self): reason="Currently don't capture during test as pytest does its own capturing" ) def test_capfd(self): - """ Does correctly capture fd - """ + """Does correctly capture fd""" kernel = InProcessKernel() with capture_output() as io: kernel.shell.run_cell('print("foo")') - assert io.stdout == 'foo\n' + assert io.stdout == "foo\n" kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) kernel.frontends.append(kc) - kc.execute('import os') + kc.execute("import os") kc.execute('os.system("echo capfd")') out, err = assemble_output(kc.iopub_channel) - assert out == 'capfd\n' - + assert out == "capfd\n" def test_getpass_stream(self): "Tests that kernel getpass accept the stream parameter" diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 063db479a..0697c6ef8 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -15,7 +15,6 @@ import traceback from io import StringIO, TextIOBase import io -import os import zmq if zmq.pyzmq_version_info() >= (17, 0): @@ -39,6 +38,7 @@ # IO classes #----------------------------------------------------------------------------- + class IOPubThread(object): """An object for sending IOPub messages in a background thread @@ -293,7 +293,7 @@ def fileno(self): """ Things like subprocess will peak and write to the fileno() of stderr/stdout. """ - if getattr(self, '_original_stdstream_copy', None) is not None: + if getattr(self, "_original_stdstream_copy", None) is not None: return self._original_stdstream_copy else: raise io.UnsupportedOperation("fileno") From 8a160a53e298d4cfb3d38a7ac670c5ddd0de5be0 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 13 Apr 2021 16:35:27 -0700 Subject: [PATCH 0504/1195] flake 8 cleanup --- ipykernel/kernelapp.py | 1 - ipykernel/kernelbase.py | 4 +--- ipykernel/tests/test_kernel.py | 6 ------ 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 78fa0e70c..a340befe8 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -17,7 +17,6 @@ from tornado import ioloop import zmq -from zmq.eventloop import ioloop as zmq_ioloop from zmq.eventloop.zmqstream import ZMQStream from IPython.core.application import ( diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e071c6936..e28baed39 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -231,7 +231,6 @@ def should_handle(self, stream, msg, idents): """ msg_id = msg['header']['msg_id'] if msg_id in self.aborted: - msg_type = msg['header']['msg_type'] # is it safe to assume a msg_id will not be resubmitted? self.aborted.remove(msg_id) self._send_abort_reply(stream, msg, idents) @@ -584,8 +583,7 @@ def complete_request(self, stream, ident, parent): matches = yield gen.maybe_future(self.do_complete(code, cursor_pos)) matches = json_clean(matches) - completion_msg = self.session.send(stream, 'complete_reply', - matches, parent, ident) + self.session.send(stream, "complete_reply", matches, parent, ident) def do_complete(self, code, cursor_pos): """Override in subclasses to find completions. diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 6c918aa88..9fe09f43b 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -44,7 +44,6 @@ def _check_status(content): def test_simple_print(): """simple print statement in kernel""" with kernel() as kc: - iopub = kc.iopub_channel msg_id, content = execute(kc=kc, code="print('hi')") stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == 'hi\n' @@ -116,7 +115,6 @@ def test_sys_path_profile_dir(): def test_subprocess_print(): """printing from forked mp.Process""" with new_kernel() as kc: - iopub = kc.iopub_channel _check_master(kc, expected=True) flush_channels(kc) @@ -144,7 +142,6 @@ def test_subprocess_print(): def test_subprocess_noprint(): """mp.Process without print doesn't trigger iostream mp_mode""" with kernel() as kc: - iopub = kc.iopub_channel np = 5 code = '\n'.join([ @@ -171,7 +168,6 @@ def test_subprocess_noprint(): def test_subprocess_error(): """error in mp.Process doesn't crash""" with new_kernel() as kc: - iopub = kc.iopub_channel code = '\n'.join([ "import multiprocessing as mp", @@ -344,8 +340,6 @@ def test_unc_paths(): file_path = os.path.splitdrive(os.path.dirname(drive_file_path))[1] unc_file_path = os.path.join(unc_root, file_path[1:]) - iopub = kc.iopub_channel - kc.execute("cd {0:s}".format(unc_file_path)) reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) assert reply['content']['status'] == 'ok' From c1f1e5af1408067d9ec0cce3f502ddf41af8f5a9 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 13 Apr 2021 16:36:17 -0700 Subject: [PATCH 0505/1195] missing self --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 3e99a36a5..bbd7a2a20 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -65,7 +65,7 @@ def shell_streams(self): 'Property shell_streams is deprecated in favor of shell_stream', DeprecationWarning ) - return [shell_stream] + return [self.shell_stream] control_stream = Instance(ZMQStream, allow_none=True) From 0702e32875e0952d479ba9fbdc7d2c44b5c8f950 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 14 Apr 2021 11:26:42 +0200 Subject: [PATCH 0506/1195] make deprecated shell_streams writable shell_streams is deprecated, but keep it working with deprecation warnings instead of breaking it and handle debugpy_stream being undefined, since allow_none is True --- ipykernel/ipkernel.py | 5 ++++- ipykernel/kernelbase.py | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index f9125b992..38cef9f92 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -162,7 +162,10 @@ def banner(self): def start(self): self.shell.exit_now = False - self.debugpy_stream.on_recv(self.dispatch_debugpy, copy=False) + if self.debugpy_stream is None: + self.log.warning("debugpy_stream undefined, debugging will not be enabled") + else: + self.debugpy_stream.on_recv(self.dispatch_debugpy, copy=False) super(IPythonKernel, self).start() def set_parent(self, ident, parent, channel='shell'): diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index bbd7a2a20..21552e758 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -59,13 +59,41 @@ def _update_eventloop(self, change): profile_dir = Instance('IPython.core.profiledir.ProfileDir', allow_none=True) shell_stream = Instance(ZMQStream, allow_none=True) - @property - def shell_streams(self): + shell_streams = List( + help="""Deprecated shell_streams alias. Use shell_stream + + .. versionchanged:: 6.0 + shell_streams is deprecated. Use shell_stream. + """ + ) + + @default("shell_streams") + def _shell_streams_default(self): warnings.warn( - 'Property shell_streams is deprecated in favor of shell_stream', - DeprecationWarning + "Kernel.shell_streams is deprecated in ipykernel 6.0. Use Kernel.shell_stream", + DeprecationWarning, + stacklevel=2, ) - return [self.shell_stream] + if self.shell_stream is not None: + return [self.shell_stream] + else: + return [] + + @observe("shell_streams") + def _shell_streams_changed(self, change): + warnings.warn( + "Kernel.shell_streams is deprecated in ipykernel 6.0. Use Kernel.shell_stream", + DeprecationWarning, + stacklevel=2, + ) + if len(change.new) > 1: + warnings.warn( + "Kernel only supports one shell stream. Additional streams will be ignored.", + RuntimeWarning, + stacklevel=2, + ) + if change.new: + self.shell_stream = change.new[0] control_stream = Instance(ZMQStream, allow_none=True) From a19816f8373e9e977cfd1d9d4833f874c930ad7c Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 14 Apr 2021 11:38:24 +0200 Subject: [PATCH 0507/1195] ensure control messages are handled without optional control_thread and start handling them in `.start()` instead of `__init__` --- ipykernel/kernelbase.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 21552e758..91a26df18 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -205,8 +205,6 @@ def __init__(self, **kwargs): self.control_handlers[msg_type] = getattr(self, msg_type) self.control_queue = Queue() - if 'control_thread' in kwargs: - kwargs['control_thread'].io_loop.add_callback(self.poll_control_queue) @gen.coroutine def dispatch_control(self, msg): @@ -452,6 +450,13 @@ def start(self): self.control_stream.on_recv(self.dispatch_control, copy=False) + if self.control_thread: + control_loop = self.control_thread.io_loop + else: + control_loop = self.io_loop + + control_loop.add_callback(self.poll_control_queue) + self.shell_stream.on_recv( partial( self.schedule_dispatch, From 256ffec7dd1c7ca0633815208a348d23c3e1fa50 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Wed, 14 Apr 2021 15:18:57 +0200 Subject: [PATCH 0508/1195] Fix parent header retrieval --- ipykernel/comm/comm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 178ce4215..63074b455 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -66,7 +66,7 @@ def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys) self.kernel.session.send(self.kernel.iopub_socket, msg_type, content, metadata=json_clean(metadata), - parent=self.kernel._parent_header, + parent=self.kernel._parent_header.get('shell', {}), ident=self.topic, buffers=buffers, ) From a6b6be89168c63cf8072e95507c546ef8f7bb4ef Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Wed, 14 Apr 2021 22:37:32 +0200 Subject: [PATCH 0509/1195] Release 6.0.0a4 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index e76229ad9..78fa1d8e7 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "a3") +version_info = (6, 0, 0, "a4") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 17ba245eebb820d0862e00f6ec506425ffd8d150 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Sat, 17 Apr 2021 01:36:35 +0200 Subject: [PATCH 0510/1195] Do no flush control stream in shell thread. --- ipykernel/kernelbase.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 91a26df18..2bcf22a87 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -411,10 +411,6 @@ def dispatch_queue(self): """ while True: - # ensure control stream is flushed before processing shell messages - if self.control_stream: - self.control_stream.flush() - # receive the next message and handle it try: yield self.process_one() except Exception: From 91550b37f78cb2db6590f591b3eeacd653fba9b4 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 17 Apr 2021 10:20:30 -0700 Subject: [PATCH 0511/1195] DOC: Autoreformat all docstrings. This reformat all docstrings in ipykernel to conform to numpydoc. --- ipykernel/connect.py | 4 ++-- ipykernel/datapub.py | 2 -- ipykernel/embed.py | 3 +-- ipykernel/iostream.py | 5 ++--- ipykernel/jsonutil.py | 9 +++------ ipykernel/kernelapp.py | 8 ++++---- ipykernel/kernelspec.py | 25 ++++++++++--------------- ipykernel/parentpoller.py | 1 - ipykernel/pickleutil.py | 20 +++++++++----------- ipykernel/pylab/backend_inline.py | 13 ++++++------- ipykernel/serialize.py | 6 +----- ipykernel/zmqshell.py | 23 +++++++++++------------ 12 files changed, 49 insertions(+), 70 deletions(-) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 37b029207..08c374141 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -36,7 +36,7 @@ def get_connection_file(app=None): def find_connection_file(filename='kernel-*.json', profile=None): """DEPRECATED: find a connection file, and return its absolute path. - + THIS FUNCTION IS DEPRECATED. Use jupyter_client.find_connection_file instead. Parameters @@ -80,7 +80,7 @@ def find_connection_file(filename='kernel-*.json', profile=None): def _find_connection_file(connection_file, profile=None): """Return the absolute path for a connection file - + - If nothing specified, return current Kernel's connection file - If profile specified, show deprecation warning about finding connection files in profiles - Otherwise, call jupyter_client.find_connection_file diff --git a/ipykernel/datapub.py b/ipykernel/datapub.py index 19db14a6c..c124195cb 100644 --- a/ipykernel/datapub.py +++ b/ipykernel/datapub.py @@ -38,7 +38,6 @@ def publish_data(self, data): Parameters ---------- - data : dict The data to be published. Think of it as a namespace. """ @@ -60,7 +59,6 @@ def publish_data(data): Parameters ---------- - data : dict The data to be published. Think of it as a namespace. """ diff --git a/ipykernel/embed.py b/ipykernel/embed.py index 6641cd6e5..4334eb182 100644 --- a/ipykernel/embed.py +++ b/ipykernel/embed.py @@ -23,8 +23,7 @@ def embed_kernel(module=None, local_ns=None, **kwargs): The module to load into IPython globals (default: caller) local_ns : dict, optional The namespace to load into IPython user namespace (default: caller) - - kwargs : various, optional + **kwargs : various, optional Further keyword args are relayed to the IPKernelApp constructor, allowing configuration of the Kernel. Will only have an effect on the first embed_kernel call for a given process. diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index ab6a2f707..8fab38da4 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -50,10 +50,9 @@ def __init__(self, socket, pipe=False): Parameters ---------- - - socket: zmq.PUB Socket + socket : zmq.PUB Socket the socket on which messages will be sent. - pipe: bool + pipe : bool Whether this process should listen for IOPub messages piped from subprocesses. """ diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 3029ad462..1218c8eae 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -50,13 +50,11 @@ def encode_images(format_dict): Parameters ---------- - format_dict : dict A dictionary of display data keyed by mime-type Returns ------- - format_dict : dict A copy of the same dictionary, but binary image data ('image/png', 'image/jpeg' or 'application/pdf') @@ -87,10 +85,9 @@ def json_clean(obj): Returns ------- out : object - - A version of the input which will not cause an encoding error when - encoded as JSON. Note that this function does not *encode* its inputs, - it simply sanitizes it so that there will be no encoding errors later. + A version of the input which will not cause an encoding error when + encoded as JSON. Note that this function does not *encode* its inputs, + it simply sanitizes it so that there will be no encoding errors later. """ # types that are 'atomic' and ok in json as-is. diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index ff2fdfd90..17fe183a7 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -527,10 +527,10 @@ def init_shell(self): def configure_tornado_logger(self): """ Configure the tornado logging.Logger. - Must set up the tornado logger or else tornado will call - basicConfig for the root logger which makes the root logger - go to the real sys.stderr instead of the capture streams. - This function mimics the setup of logging.basicConfig. + Must set up the tornado logger or else tornado will call + basicConfig for the root logger which makes the root logger + go to the real sys.stderr instead of the capture streams. + This function mimics the setup of logging.basicConfig. """ logger = logging.getLogger('tornado') handler = logging.StreamHandler() diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 911df5f8b..9d43a8f71 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -28,16 +28,13 @@ def make_ipkernel_cmd(mod='ipykernel_launcher', executable=None, extra_arguments ---------- mod : str, optional (default 'ipykernel') A string of an IPython module whose __main__ starts an IPython kernel - executable : str, optional (default sys.executable) The Python executable to use for the kernel process. - extra_arguments : list, optional A list of extra arguments to pass when executing the launch code. Returns ------- - A Popen command list """ if executable is None: @@ -61,10 +58,10 @@ def get_kernel_dict(extra_arguments=None): def write_kernel_spec(path=None, overrides=None, extra_arguments=None): """Write a kernel spec directory to `path` - + If `path` is not specified, a temporary directory is created. If `overrides` is given, the kernelspec JSON is updated before writing. - + The path to the kernelspec is always returned. """ if path is None: @@ -92,33 +89,31 @@ def write_kernel_spec(path=None, overrides=None, extra_arguments=None): def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, display_name=None, prefix=None, profile=None, env=None): """Install the IPython kernelspec for Jupyter - + Parameters ---------- - - kernel_spec_manager: KernelSpecManager [optional] + kernel_spec_manager : KernelSpecManager [optional] A KernelSpecManager to use for installation. If none provided, a default instance will be created. - user: bool [default: False] + user : bool [default: False] Whether to do a user-only install, or system-wide. - kernel_name: str, optional + kernel_name : str, optional Specify a name for the kernelspec. This is needed for having multiple IPython kernels for different environments. - display_name: str, optional + display_name : str, optional Specify the display name for the kernelspec - profile: str, optional + profile : str, optional Specify a custom profile to be loaded by the kernel. - prefix: str, optional + prefix : str, optional Specify an install prefix for the kernelspec. This is needed to install into a non-default location, such as a conda/virtual-env. - env: dict, optional + env : dict, optional A dictionary of extra environment variables for the kernel. These will be added to the current environment variables before the kernel is started Returns ------- - The path where the kernelspec was installed. """ if kernel_spec_manager is None: diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 38bf72e72..238a397c2 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -55,7 +55,6 @@ def __init__(self, interrupt_handle=None, parent_handle=None): interrupt_handle : HANDLE (int), optional If provided, the program will generate a Ctrl+C event when this handle is signaled. - parent_handle : HANDLE (int), optional If provided, the program will terminate immediately when this handle is signaled. diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 651df1f3c..ea5ba715d 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -67,7 +67,7 @@ def interactive(f): def use_dill(): """use dill to expand serialization support - + adds support for object methods and closures to serialization. """ # import dill causes most of the magic @@ -91,7 +91,7 @@ def use_dill(): def use_cloudpickle(): """use cloudpickle to expand serialization support - + adds support for object methods and closures to serialization. """ import cloudpickle @@ -118,18 +118,16 @@ def use_cloudpickle(): class CannedObject(object): def __init__(self, obj, keys=[], hook=None): """can an object for safe pickling - + Parameters - ========== - - obj: + ---------- + obj The object to be canned - keys: list (optional) + keys : list (optional) list of attribute names that will be explicitly canned / uncanned - hook: callable (optional) + hook : callable (optional) An optional extra callable, which can do additional processing of the uncanned object. - large data may be offloaded into the buffers list, used for zero-copy transfers. """ @@ -304,7 +302,7 @@ class CannedMemoryView(CannedBytes): def _import_mapping(mapping, original=None): """import any string-keys in a type mapping - + """ log = get_logger() log.debug("Importing canning map") @@ -322,7 +320,7 @@ def _import_mapping(mapping, original=None): def istype(obj, check): """like isinstance(obj, check), but strict - + This won't catch subclasses. """ if isinstance(check, tuple): diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index f5333d517..36af62936 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -25,13 +25,13 @@ def show(close=None, block=None): Parameters ---------- close : bool, optional - If true, a ``plt.close('all')`` call is automatically issued after - sending all the figures. If this is set, the figures will entirely - removed from the internal list of figures. + If true, a ``plt.close('all')`` call is automatically issued after + sending all the figures. If this is set, the figures will entirely + removed from the internal list of figures. block : Not used. - The `block` parameter is a Matplotlib experimental parameter. - We accept it in the function signature for compatibility with other - backends. + The `block` parameter is a Matplotlib experimental parameter. + We accept it in the function signature for compatibility with other + backends. """ if close is None: close = InlineBackend.instance().close_figures @@ -158,7 +158,6 @@ def configure_inline_support(shell, backend): Parameters ---------- shell : InteractiveShell instance - backend : matplotlib backend """ # If using our svg payload backend, register the post-execution diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index a8558e80a..184a60ded 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -61,7 +61,6 @@ def serialize_object(obj, buffer_threshold=MAX_BYTES, item_threshold=MAX_ITEMS): Parameters ---------- - obj : object The object to be serialized buffer_threshold : int @@ -99,14 +98,11 @@ def deserialize_object(buffers, g=None): Parameters ---------- - - bufs : list of buffers/bytes - + buffers : list of buffers/bytes g : globals to be used when uncanning Returns ------- - (newobj, bufs) : unpacked object, and the list of remaining unused buffers. """ bufs = list(buffers) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 2dcb8b349..0f9eb8723 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -100,15 +100,15 @@ def publish(self, data, metadata=None, source=None, transient=None, Parameters ---------- - data: dict + data : dict A mime-bundle dict, keyed by mime-type. - metadata: dict, optional + metadata : dict, optional Metadata associated with the data. - transient: dict, optional, keyword-only + transient : dict, optional, keyword-only Transient data that may only be relevant during a live display, such as display_id. Transient data should not be persisted to documents. - update: bool, optional, keyword-only + update : bool, optional, keyword-only If True, send an update_display_data message instead of display_data. """ self._flush_streams() @@ -149,7 +149,7 @@ def clear_output(self, wait=False): Parameters ---------- - wait: bool (default: False) + wait : bool (default: False) If True, the output will not be cleared immediately, instead waiting for the next display before clearing. This reduces bounce during repeated clear & display loops. @@ -173,7 +173,6 @@ def register_hook(self, hook): Returns ------- Either a publishable message, or `None`. - The DisplayHook objects must return a message from the __call__ method if they still require the `session.send` method to be called after transformation. @@ -188,13 +187,13 @@ def unregister_hook(self, hook): Parameters ---------- - hook: Any callable object which has previously been - registered as a hook. + hook : Any callable object which has previously been + registered as a hook. Returns ------- bool - `True` if the hook was removed, `False` if it wasn't - found. + found. """ try: self._hooks.remove(hook) @@ -618,9 +617,9 @@ def system_piped(self, cmd): Parameters ---------- cmd : str - Command to execute (can not end in '&', as background processes are - not supported. Should not be a command that expects input - other than simple text. + Command to execute (can not end in '&', as background processes are + not supported. Should not be a command that expects input + other than simple text. """ if cmd.rstrip().endswith('&'): # this is *far* from a rigorous test From 9171532a93831f3ab1ecb399cd7ea16671e236e5 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 17 Apr 2021 11:01:43 -0700 Subject: [PATCH 0512/1195] Update of ZMQInteractiveshell. Mark one long deprecated parameter as deprecated + warnings, and remove unused variables/imports --- ipykernel/zmqshell.py | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 2dcb8b349..79c6f3a97 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -16,12 +16,9 @@ import os import sys -import time import warnings from threading import local -from tornado import ioloop - from IPython.core.interactiveshell import ( InteractiveShell, InteractiveShellABC ) @@ -59,6 +56,9 @@ # Functions and classes #----------------------------------------------------------------------------- +_sentinel = object() + + class ZMQDisplayPublisher(DisplayPublisher): """A display publisher that publishes data using a ZeroMQ PUB socket.""" @@ -93,7 +93,12 @@ def _hooks(self): self._thread_local.hooks = [] return self._thread_local.hooks - def publish(self, data, metadata=None, source=None, transient=None, + def publish( + self, + data, + metadata=None, + source=_sentinel, + transient=None, update=False, ): """Publish a display-data message @@ -110,7 +115,23 @@ def publish(self, data, metadata=None, source=None, transient=None, Transient data should not be persisted to documents. update: bool, optional, keyword-only If True, send an update_display_data message instead of display_data. + source : unused + Value will have no effect on function behavior. Parameter is still + present for backward compatibility but will be removed in the + future. + + .. deprecated:: 4.0.1 + + `source` has been deprecated and no-op since ipykernel 4.0.1 + (2015) """ + if source is not _sentinel: + warnings.warn( + "`source` has been deprecated since ipykernel 4.0.1 " + "and will have no effect", + DeprecationWarning, + stacklevel=2, + ) self._flush_streams() if metadata is None: metadata = {} @@ -555,8 +576,13 @@ def _showtraceback(self, etype, evalue, stb): if dh.topic: topic = dh.topic.replace(b'execute_result', b'error') - exc_msg = dh.session.send(dh.pub_socket, 'error', json_clean(exc_content), - dh.parent_header, ident=topic) + dh.session.send( + dh.pub_socket, + "error", + json_clean(exc_content), + dh.parent_header, + ident=topic, + ) # FIXME - Once we rely on Python 3, the traceback is stored on the # exception object, so we shouldn't need to store it here. From c5aa9047ada9525d6a62377c778d6f5f005b9de0 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 17 Apr 2021 17:20:02 -0700 Subject: [PATCH 0513/1195] DOC last cleanup --- ipykernel/kernelspec.py | 2 +- ipykernel/pickleutil.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 9d43a8f71..5f93c9fff 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -21,7 +21,7 @@ RESOURCES = pjoin(os.path.dirname(__file__), 'resources') -def make_ipkernel_cmd(mod='ipykernel_launcher', executable=None, extra_arguments=None, **kw): +def make_ipkernel_cmd(mod="ipykernel_launcher", executable=None, extra_arguments=None): """Build Popen command list for launching an IPython kernel. Parameters diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index ea5ba715d..281b8f9ea 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -128,6 +128,9 @@ def __init__(self, obj, keys=[], hook=None): hook : callable (optional) An optional extra callable, which can do additional processing of the uncanned object. + + Notes + ----- large data may be offloaded into the buffers list, used for zero-copy transfers. """ From 6f85354d7a60d939dd4269d0ed73eb1dae91927b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sat, 17 Apr 2021 17:29:58 -0700 Subject: [PATCH 0514/1195] Try to run velin in CI --- .github/workflows/ci.yml | 43 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 875fdc2ab..82bd2fb50 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,10 +53,11 @@ jobs: pip freeze pip check - name: Run the tests - timeout-minutes: 30 + timeout-minutes: 10 run: | pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 - name: Build the docs + if: ${{ matrix.os == 'ubuntu' && matrix.python-version == '3.9'}} run: | cd docs pip install -r requirements.txt @@ -69,3 +70,43 @@ jobs: - name: Coverage run: | codecov + check_docstrings: + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu] + python-version: [ '3.9' ] + exclude: + - os: windows + python-version: pypy3 + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Install Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + architecture: 'x64' + - name: Upgrade packaging dependencies + run: | + pip install --upgrade pip setuptools wheel --user + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: Cache pip + uses: actions/cache@v1 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.python-version }}- + ${{ runner.os }}-pip- + - name: Install the Python dependencies + run: | + pip install --pre --upgrade --upgrade-strategy=eager . + pip install velin + - name: Check Docstrings + run: | + velin . --check --compact From 7ad3e42b6725f6a83ed2df6ab41811ea6d1f239c Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Sat, 24 Apr 2021 00:49:34 +0200 Subject: [PATCH 0515/1195] Fixup master build --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index c065cf8f2..efb25c035 100644 --- a/setup.py +++ b/setup.py @@ -74,6 +74,7 @@ def run(self): keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], python_requires='>=3.7', install_requires=[ + 'importlib-metadata<4;python_version<"3.8.0"', 'debugpy>=1.0.0', 'ipython>=7.21.0', 'traitlets>=4.1.0', From aefef5d732a412146ee335d1b14a7d8ab58a6d72 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 9 Apr 2021 21:57:19 +0200 Subject: [PATCH 0516/1195] Use native coroutines --- ipykernel/inprocess/client.py | 6 +- ipykernel/inprocess/ipkernel.py | 6 +- ipykernel/ipkernel.py | 35 ++++----- ipykernel/kernelbase.py | 133 +++++++++++++------------------- 4 files changed, 76 insertions(+), 104 deletions(-) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 37edf8064..1784a6eaa 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -11,6 +11,8 @@ # Imports #----------------------------------------------------------------------------- +import asyncio + # IPython imports from traitlets import Type, Instance, default from jupyter_client.clientabc import KernelClientABC @@ -173,8 +175,8 @@ def _dispatch_to_kernel(self, msg): stream = kernel.shell_stream self.session.send(stream, msg) msg_parts = stream.recv_multipart() - kernel.dispatch_shell(msg_parts) - + loop = asyncio.get_event_loop() + loop.run_until_complete(kernel.dispatch_shell(msg_parts)) idents, reply_msg = self.session.recv(stream, copy=False) self.shell_channel.call_handlers_later(reply_msg) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index dd1bc6581..942907226 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -74,16 +74,16 @@ def __init__(self, **traits): self._underlying_iopub_socket.observe(self._io_dispatch, names=['message_sent']) self.shell.kernel = self - def execute_request(self, stream, ident, parent): + async def execute_request(self, stream, ident, parent): """ Override for temporary IO redirection. """ with self._redirected_io(): - super(InProcessKernel, self).execute_request(stream, ident, parent) + await super(InProcessKernel, self).execute_request(stream, ident, parent) def start(self): """ Override registration of dispatchers for streams. """ self.shell.exit_now = False - def _abort_queues(self): + async def _abort_queues(self): """ The in-process kernel doesn't abort requests. """ pass diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 38cef9f92..924db500d 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -10,7 +10,6 @@ from IPython.core import release from IPython.utils.tokenutil import token_at_cursor, line_at_cursor -from tornado import gen from traitlets import Instance, Type, Any, List, Bool, observe, observe_compat from zmq.eventloop.zmqstream import ZMQStream @@ -149,8 +148,7 @@ def __init__(self, **kwargs): 'file_extension': '.py' } - @gen.coroutine - def dispatch_debugpy(self, msg): + async def dispatch_debugpy(self, msg): # The first frame is the socket id, we can drop it frame = msg[1].bytes.decode('utf-8') self.log.debug("Debugpy received: %s", frame) @@ -279,9 +277,8 @@ def set_sigint_result(): # restore the previous sigint handler signal.signal(signal.SIGINT, save_sigint) - @gen.coroutine - def do_execute(self, code, silent, store_history=True, - user_expressions=None, allow_stdin=False): + async def do_execute(self, code, silent, store_history=True, + user_expressions=None, allow_stdin=False): shell = self.shell # we'll need this a lot here self._forward_input(allow_stdin) @@ -294,8 +291,7 @@ def do_execute(self, code, silent, store_history=True, should_run_async = lambda cell: False # older IPython, # use blocking run_cell and wrap it in coroutine - @gen.coroutine - def run_cell(*args, **kwargs): + async def run_cell(*args, **kwargs): return shell.run_cell(*args, **kwargs) try: @@ -327,7 +323,7 @@ def run_cell(*args, **kwargs): with self._cancel_on_sigint(coro_future): res = None try: - res = yield coro_future + res = await coro_future finally: shell.events.trigger('post_execute') if not silent: @@ -388,7 +384,7 @@ def run_cell(*args, **kwargs): return reply_content - def do_complete(self, code, cursor_pos): + async def do_complete(self, code, cursor_pos): if _use_experimental_60_completion and self.use_experimental_completions: return self._experimental_do_complete(code, cursor_pos) @@ -407,9 +403,8 @@ def do_complete(self, code, cursor_pos): 'metadata' : {}, 'status' : 'ok'} - @gen.coroutine - def do_debug_request(self, msg): - return (yield self.debugger.process_request(msg)) + async def do_debug_request(self, msg): + return self.debugger.process_request(msg) def _experimental_do_complete(self, code, cursor_pos): """ @@ -445,9 +440,7 @@ def _experimental_do_complete(self, code, cursor_pos): 'metadata': {_EXPERIMENTAL_KEY_NAME: comps}, 'status': 'ok'} - - - def do_inspect(self, code, cursor_pos, detail_level=0): + async def do_inspect(self, code, cursor_pos, detail_level=0): name = token_at_cursor(code, cursor_pos) reply_content = {'status' : 'ok'} @@ -468,7 +461,7 @@ def do_inspect(self, code, cursor_pos, detail_level=0): return reply_content - def do_history(self, hist_access_type, output, raw, session=0, start=0, + async def do_history(self, hist_access_type, output, raw, session=0, start=0, stop=None, n=None, pattern=None, unique=False): if hist_access_type == 'tail': hist = self.shell.history_manager.get_tail(n, raw=raw, output=output, @@ -489,11 +482,11 @@ def do_history(self, hist_access_type, output, raw, session=0, start=0, 'history' : list(hist), } - def do_shutdown(self, restart): + async def do_shutdown(self, restart): self.shell.exit_now = True return dict(status='ok', restart=restart) - def do_is_complete(self, code): + async def do_is_complete(self, code): transformer_manager = getattr(self.shell, 'input_transformer_manager', None) if transformer_manager is None: # input_splitter attribute is deprecated @@ -504,7 +497,7 @@ def do_is_complete(self, code): r['indent'] = ' ' * indent_spaces return r - def do_apply(self, content, bufs, msg_id, reply_metadata): + async def do_apply(self, content, bufs, msg_id, reply_metadata): from .serialize import serialize_object, unpack_apply_message shell = self.shell try: @@ -559,7 +552,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): return reply_content, result_buf - def do_clear(self): + async def do_clear(self): self.shell.reset(False) return dict(status='ok') diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 90bc5684e..0ab4d8815 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -3,6 +3,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import asyncio from datetime import datetime from functools import partial import itertools @@ -21,7 +22,6 @@ now = datetime.now from tornado import ioloop -from tornado import gen from tornado.queues import Queue, QueueEmpty import zmq from zmq.eventloop.zmqstream import ZMQStream @@ -206,18 +206,15 @@ def __init__(self, **kwargs): self.control_queue = Queue() - @gen.coroutine def dispatch_control(self, msg): self.control_queue.put_nowait(msg) - @gen.coroutine - def poll_control_queue(self): + async def poll_control_queue(self): while True: - msg = yield self.control_queue.get() - yield self.process_control(msg) + msg = await self.control_queue.get() + await self.process_control(msg) - @gen.coroutine - def process_control(self, msg): + async def process_control(self, msg): """dispatch control requests""" idents, msg = self.session.feed_identities(msg, copy=False) try: @@ -240,7 +237,7 @@ def process_control(self, msg): self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type) else: try: - yield gen.maybe_future(handler(self.control_stream, idents, msg)) + await handler(self.control_stream, idents, msg) except Exception: self.log.error("Exception in control handler:", exc_info=True) @@ -263,8 +260,7 @@ def should_handle(self, stream, msg, idents): return False return True - @gen.coroutine - def dispatch_shell(self, msg): + async def dispatch_shell(self, msg): """dispatch shell requests""" idents, msg = self.session.feed_identities(msg, copy=False) try: @@ -307,7 +303,7 @@ def dispatch_shell(self, msg): except Exception: self.log.debug("Unable to signal in pre_handler_hook:", exc_info=True) try: - yield gen.maybe_future(handler(self.shell_stream, idents, msg)) + await handler(self.shell_stream, idents, msg) except Exception: self.log.error("Exception in message handler:", exc_info=True) finally: @@ -372,8 +368,7 @@ def schedule_next(): # begin polling the eventloop schedule_next() - @gen.coroutine - def do_one_iteration(self): + async def do_one_iteration(self): """Process a single shell message Any pending control messages will be flushed as well @@ -384,25 +379,23 @@ def do_one_iteration(self): # flush messages off of shell stream into the message queue self.shell_stream.flush() # process at most one shell message per iteration - yield self.process_one(wait=False) + await self.process_one(wait=False) - @gen.coroutine - def process_one(self, wait=True): + async def process_one(self, wait=True): """Process one request Returns None if no message was handled. """ if wait: - t, dispatch, args = yield self.msg_queue.get() + t, dispatch, args = await self.msg_queue.get() else: try: t, dispatch, args = self.msg_queue.get_nowait() - except QueueEmpty: + except asyncio.QueueEmpty: return None - yield gen.maybe_future(dispatch(*args)) + await dispatch(*args) - @gen.coroutine - def dispatch_queue(self): + async def dispatch_queue(self): """Coroutine to preserve order of message handling Ensures that only one message is processing at a time, @@ -411,7 +404,7 @@ def dispatch_queue(self): while True: try: - yield self.process_one() + await self.process_one() except Exception: self.log.exception("Error in message handler") @@ -450,7 +443,7 @@ def start(self): else: control_loop = self.io_loop - control_loop.add_callback(self.poll_control_queue) + asyncio.run_coroutine_threadsafe(self.poll_control_queue(), control_loop.asyncio_loop) self.shell_stream.on_recv( partial( @@ -543,8 +536,7 @@ def finish_metadata(self, parent, metadata, reply_content): """ return metadata - @gen.coroutine - def execute_request(self, stream, ident, parent): + async def execute_request(self, stream, ident, parent): """handle an execute_request""" try: @@ -569,11 +561,9 @@ def execute_request(self, stream, ident, parent): self.execution_count += 1 self._publish_execute_input(code, parent, self.execution_count) - reply_content = yield gen.maybe_future( - self.do_execute( - code, silent, store_history, - user_expressions, allow_stdin, - ) + reply_content = await self.do_execute( + code, silent, store_history, + user_expressions, allow_stdin, ) # Flush output before sending the reply. @@ -596,25 +586,24 @@ def execute_request(self, stream, ident, parent): self.log.debug("%s", reply_msg) if not silent and reply_msg['content']['status'] == 'error' and stop_on_error: - yield self._abort_queues() + await self._abort_queues() - def do_execute(self, code, silent, store_history=True, + async def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): """Execute user code. Must be overridden by subclasses. """ raise NotImplementedError - @gen.coroutine - def complete_request(self, stream, ident, parent): + async def complete_request(self, stream, ident, parent): content = parent['content'] code = content['code'] cursor_pos = content['cursor_pos'] - matches = yield gen.maybe_future(self.do_complete(code, cursor_pos)) + matches = await self.do_complete(code, cursor_pos) matches = json_clean(matches) self.session.send(stream, "complete_reply", matches, parent, ident) - def do_complete(self, code, cursor_pos): + async def do_complete(self, code, cursor_pos): """Override in subclasses to find completions. """ return {'matches' : [], @@ -623,15 +612,12 @@ def do_complete(self, code, cursor_pos): 'metadata' : {}, 'status' : 'ok'} - @gen.coroutine - def inspect_request(self, stream, ident, parent): + async def inspect_request(self, stream, ident, parent): content = parent['content'] - reply_content = yield gen.maybe_future( - self.do_inspect( - content['code'], content['cursor_pos'], - content.get('detail_level', 0), - ) + reply_content = await self.do_inspect( + content['code'], content['cursor_pos'], + content.get('detail_level', 0), ) # Before we send this object over, we scrub it for JSON usage reply_content = json_clean(reply_content) @@ -639,29 +625,28 @@ def inspect_request(self, stream, ident, parent): reply_content, parent, ident) self.log.debug("%s", msg) - def do_inspect(self, code, cursor_pos, detail_level=0): + async def do_inspect(self, code, cursor_pos, detail_level=0): """Override in subclasses to allow introspection. """ return {'status': 'ok', 'data': {}, 'metadata': {}, 'found': False} - @gen.coroutine - def history_request(self, stream, ident, parent): + async def history_request(self, stream, ident, parent): content = parent['content'] - reply_content = yield gen.maybe_future(self.do_history(**content)) + reply_content = await self.do_history(**content) reply_content = json_clean(reply_content) msg = self.session.send(stream, 'history_reply', reply_content, parent, ident) self.log.debug("%s", msg) - def do_history(self, hist_access_type, output, raw, session=None, start=None, + async def do_history(self, hist_access_type, output, raw, session=None, start=None, stop=None, n=None, pattern=None, unique=False): """Override in subclasses to access history. """ return {'status': 'ok', 'history': []} - def connect_request(self, stream, ident, parent): + async def connect_request(self, stream, ident, parent): if self._recorded_ports is not None: content = self._recorded_ports.copy() else: @@ -682,14 +667,14 @@ def kernel_info(self): 'help_links': self.help_links, } - def kernel_info_request(self, stream, ident, parent): + async def kernel_info_request(self, stream, ident, parent): content = {'status': 'ok'} content.update(self.kernel_info) msg = self.session.send(stream, 'kernel_info_reply', content, parent, ident) self.log.debug("%s", msg) - def comm_info_request(self, stream, ident, parent): + async def comm_info_request(self, stream, ident, parent): content = parent['content'] target_name = content.get('target_name', None) @@ -707,9 +692,8 @@ def comm_info_request(self, stream, ident, parent): reply_content, parent, ident) self.log.debug("%s", msg) - @gen.coroutine - def shutdown_request(self, stream, ident, parent): - content = yield gen.maybe_future(self.do_shutdown(parent['content']['restart'])) + async def shutdown_request(self, stream, ident, parent): + content = await self.do_shutdown(parent['content']['restart']) self.session.send(stream, 'shutdown_reply', content, parent, ident=ident) # same content, but different msg_id for broadcasting on IOPub self._shutdown_message = self.session.msg('shutdown_reply', @@ -726,48 +710,44 @@ def shutdown_request(self, stream, ident, parent): shell_io_loop = self.shell_stream.io_loop shell_io_loop.add_callback(shell_io_loop.stop) - def do_shutdown(self, restart): + async def do_shutdown(self, restart): """Override in subclasses to do things when the frontend shuts down the kernel. """ return {'status': 'ok', 'restart': restart} - @gen.coroutine - def is_complete_request(self, stream, ident, parent): + async def is_complete_request(self, stream, ident, parent): content = parent['content'] code = content['code'] - reply_content = yield gen.maybe_future(self.do_is_complete(code)) + reply_content = await self.do_is_complete(code) reply_content = json_clean(reply_content) reply_msg = self.session.send(stream, 'is_complete_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) - def do_is_complete(self, code): + async def do_is_complete(self, code): """Override in subclasses to find completions. """ - return {'status' : 'unknown', - } + return { 'status' : 'unknown'} - @gen.coroutine - def debug_request(self, stream, ident, parent): + async def debug_request(self, stream, ident, parent): content = parent['content'] - reply_content = yield gen.maybe_future(self.do_debug_request(content)) + reply_content = await self.do_debug_request(content) reply_content = json_clean(reply_content) reply_msg = self.session.send(stream, 'debug_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) - @gen.coroutine - def do_debug_request(self, msg): + async def do_debug_request(self, msg): raise NotImplementedError #--------------------------------------------------------------------------- # Engine methods (DEPRECATED) #--------------------------------------------------------------------------- - def apply_request(self, stream, ident, parent): + async def apply_request(self, stream, ident, parent): self.log.warning("apply_request is deprecated in kernel_base, moving to ipyparallel.") try: content = parent['content'] @@ -790,7 +770,7 @@ def apply_request(self, stream, ident, parent): self.session.send(stream, 'apply_reply', reply_content, parent=parent, ident=ident,buffers=result_buf, metadata=md) - def do_apply(self, content, bufs, msg_id, reply_metadata): + async def do_apply(self, content, bufs, msg_id, reply_metadata): """DEPRECATED""" raise NotImplementedError @@ -798,7 +778,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): # Control messages (DEPRECATED) #--------------------------------------------------------------------------- - def abort_request(self, stream, ident, parent): + async def abort_request(self, stream, ident, parent): """abort a specific msg by id""" self.log.warning("abort_request is deprecated in kernel_base. It is only part of IPython parallel") msg_ids = parent['content'].get('msg_ids', None) @@ -814,14 +794,14 @@ def abort_request(self, stream, ident, parent): parent=parent, ident=ident) self.log.debug("%s", reply_msg) - def clear_request(self, stream, idents, parent): + async def clear_request(self, stream, idents, parent): """Clear our namespace.""" self.log.warning("clear_request is deprecated in kernel_base. It is only part of IPython parallel") content = self.do_clear() self.session.send(stream, 'clear_reply', ident=idents, parent=parent, content = content) - def do_clear(self): + async def do_clear(self): """DEPRECATED since 4.0.3""" raise NotImplementedError @@ -837,16 +817,13 @@ def _topic(self, topic): _aborting = Bool(False) - @gen.coroutine - def _abort_queues(self): + async def _abort_queues(self): self.shell_stream.flush() self._aborting = True - - def stop_aborting(f): + def stop_aborting(): self.log.info("Finishing abort") self._aborting = False - - self.io_loop.add_future(gen.sleep(self.stop_on_error_timeout), stop_aborting) + asyncio.get_event_loop().call_later(self.stop_on_error_timeout, stop_aborting) def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" From 01c200be9b5f372fb0652431390395630982eeb2 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 16 Apr 2021 11:37:48 +0200 Subject: [PATCH 0517/1195] Handle synchronous message handlers --- ipykernel/kernelbase.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 0ab4d8815..aeabcb097 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -8,6 +8,7 @@ from functools import partial import itertools import logging +import inspect from signal import signal, default_int_handler, SIGINT import sys import time @@ -237,7 +238,9 @@ async def process_control(self, msg): self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type) else: try: - await handler(self.control_stream, idents, msg) + result = handler(self.control_stream, idents, msg) + if inspect.isawaitable(result): + await result except Exception: self.log.error("Exception in control handler:", exc_info=True) @@ -303,7 +306,9 @@ async def dispatch_shell(self, msg): except Exception: self.log.debug("Unable to signal in pre_handler_hook:", exc_info=True) try: - await handler(self.shell_stream, idents, msg) + result = handler(self.shell_stream, idents, msg) + if inspect.isawaitable(result): + await result except Exception: self.log.error("Exception in message handler:", exc_info=True) finally: From 4965355b9830ab1c69dc5f301157cc35c3b5a5c5 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 23 Apr 2021 23:56:46 +0200 Subject: [PATCH 0518/1195] do_[*] functions do not have to be async --- ipykernel/ipkernel.py | 16 +++++++------- ipykernel/kernelbase.py | 46 +++++++++++++++++++++++++++-------------- 2 files changed, 39 insertions(+), 23 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 924db500d..9f4259cd7 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -384,7 +384,7 @@ async def run_cell(*args, **kwargs): return reply_content - async def do_complete(self, code, cursor_pos): + def do_complete(self, code, cursor_pos): if _use_experimental_60_completion and self.use_experimental_completions: return self._experimental_do_complete(code, cursor_pos) @@ -404,7 +404,7 @@ async def do_complete(self, code, cursor_pos): 'status' : 'ok'} async def do_debug_request(self, msg): - return self.debugger.process_request(msg) + return await self.debugger.process_request(msg) def _experimental_do_complete(self, code, cursor_pos): """ @@ -440,7 +440,7 @@ def _experimental_do_complete(self, code, cursor_pos): 'metadata': {_EXPERIMENTAL_KEY_NAME: comps}, 'status': 'ok'} - async def do_inspect(self, code, cursor_pos, detail_level=0): + def do_inspect(self, code, cursor_pos, detail_level=0): name = token_at_cursor(code, cursor_pos) reply_content = {'status' : 'ok'} @@ -461,7 +461,7 @@ async def do_inspect(self, code, cursor_pos, detail_level=0): return reply_content - async def do_history(self, hist_access_type, output, raw, session=0, start=0, + def do_history(self, hist_access_type, output, raw, session=0, start=0, stop=None, n=None, pattern=None, unique=False): if hist_access_type == 'tail': hist = self.shell.history_manager.get_tail(n, raw=raw, output=output, @@ -482,11 +482,11 @@ async def do_history(self, hist_access_type, output, raw, session=0, start=0, 'history' : list(hist), } - async def do_shutdown(self, restart): + def do_shutdown(self, restart): self.shell.exit_now = True return dict(status='ok', restart=restart) - async def do_is_complete(self, code): + def do_is_complete(self, code): transformer_manager = getattr(self.shell, 'input_transformer_manager', None) if transformer_manager is None: # input_splitter attribute is deprecated @@ -497,7 +497,7 @@ async def do_is_complete(self, code): r['indent'] = ' ' * indent_spaces return r - async def do_apply(self, content, bufs, msg_id, reply_metadata): + def do_apply(self, content, bufs, msg_id, reply_metadata): from .serialize import serialize_object, unpack_apply_message shell = self.shell try: @@ -552,7 +552,7 @@ async def do_apply(self, content, bufs, msg_id, reply_metadata): return reply_content, result_buf - async def do_clear(self): + def do_clear(self): self.shell.reset(False) return dict(status='ok') diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index aeabcb097..6be2a9d6f 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -566,10 +566,12 @@ async def execute_request(self, stream, ident, parent): self.execution_count += 1 self._publish_execute_input(code, parent, self.execution_count) - reply_content = await self.do_execute( + reply_content = self.do_execute( code, silent, store_history, user_expressions, allow_stdin, ) + if inspect.isawaitable(reply_content): + reply_content = await reply_content # Flush output before sending the reply. sys.stdout.flush() @@ -593,7 +595,7 @@ async def execute_request(self, stream, ident, parent): if not silent and reply_msg['content']['status'] == 'error' and stop_on_error: await self._abort_queues() - async def do_execute(self, code, silent, store_history=True, + def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): """Execute user code. Must be overridden by subclasses. """ @@ -604,11 +606,14 @@ async def complete_request(self, stream, ident, parent): code = content['code'] cursor_pos = content['cursor_pos'] - matches = await self.do_complete(code, cursor_pos) + matches = self.do_complete(code, cursor_pos) + if inspect.isawaitable(matches): + matches = await matches + matches = json_clean(matches) self.session.send(stream, "complete_reply", matches, parent, ident) - async def do_complete(self, code, cursor_pos): + def do_complete(self, code, cursor_pos): """Override in subclasses to find completions. """ return {'matches' : [], @@ -620,17 +625,20 @@ async def do_complete(self, code, cursor_pos): async def inspect_request(self, stream, ident, parent): content = parent['content'] - reply_content = await self.do_inspect( + reply_content = self.do_inspect( content['code'], content['cursor_pos'], content.get('detail_level', 0), ) + if inspect.isawaitable(reply_content): + reply_content = await reply_content + # Before we send this object over, we scrub it for JSON usage reply_content = json_clean(reply_content) msg = self.session.send(stream, 'inspect_reply', reply_content, parent, ident) self.log.debug("%s", msg) - async def do_inspect(self, code, cursor_pos, detail_level=0): + def do_inspect(self, code, cursor_pos, detail_level=0): """Override in subclasses to allow introspection. """ return {'status': 'ok', 'data': {}, 'metadata': {}, 'found': False} @@ -638,14 +646,16 @@ async def do_inspect(self, code, cursor_pos, detail_level=0): async def history_request(self, stream, ident, parent): content = parent['content'] - reply_content = await self.do_history(**content) + reply_content = self.do_history(**content) + if inspect.isawaitable(reply_content): + reply_content = await reply_content reply_content = json_clean(reply_content) msg = self.session.send(stream, 'history_reply', reply_content, parent, ident) self.log.debug("%s", msg) - async def do_history(self, hist_access_type, output, raw, session=None, start=None, + def do_history(self, hist_access_type, output, raw, session=None, start=None, stop=None, n=None, pattern=None, unique=False): """Override in subclasses to access history. """ @@ -698,7 +708,9 @@ async def comm_info_request(self, stream, ident, parent): self.log.debug("%s", msg) async def shutdown_request(self, stream, ident, parent): - content = await self.do_shutdown(parent['content']['restart']) + content = self.do_shutdown(parent['content']['restart']) + if inspect.isawaitable(content): + content = await content self.session.send(stream, 'shutdown_reply', content, parent, ident=ident) # same content, but different msg_id for broadcasting on IOPub self._shutdown_message = self.session.msg('shutdown_reply', @@ -715,7 +727,7 @@ async def shutdown_request(self, stream, ident, parent): shell_io_loop = self.shell_stream.io_loop shell_io_loop.add_callback(shell_io_loop.stop) - async def do_shutdown(self, restart): + def do_shutdown(self, restart): """Override in subclasses to do things when the frontend shuts down the kernel. """ @@ -725,13 +737,15 @@ async def is_complete_request(self, stream, ident, parent): content = parent['content'] code = content['code'] - reply_content = await self.do_is_complete(code) + reply_content = self.do_is_complete(code) + if inspect.isawaitable(reply_content): + reply_content = await reply_content reply_content = json_clean(reply_content) reply_msg = self.session.send(stream, 'is_complete_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) - async def do_is_complete(self, code): + def do_is_complete(self, code): """Override in subclasses to find completions. """ return { 'status' : 'unknown'} @@ -739,7 +753,9 @@ async def do_is_complete(self, code): async def debug_request(self, stream, ident, parent): content = parent['content'] - reply_content = await self.do_debug_request(content) + reply_content = self.do_debug_request(content) + if inspect.isawaitable(reply_content): + reply_content = await reply_content reply_content = json_clean(reply_content) reply_msg = self.session.send(stream, 'debug_reply', reply_content, parent, ident) @@ -775,7 +791,7 @@ async def apply_request(self, stream, ident, parent): self.session.send(stream, 'apply_reply', reply_content, parent=parent, ident=ident,buffers=result_buf, metadata=md) - async def do_apply(self, content, bufs, msg_id, reply_metadata): + def do_apply(self, content, bufs, msg_id, reply_metadata): """DEPRECATED""" raise NotImplementedError @@ -806,7 +822,7 @@ async def clear_request(self, stream, idents, parent): self.session.send(stream, 'clear_reply', ident=idents, parent=parent, content = content) - async def do_clear(self): + def do_clear(self): """DEPRECATED since 4.0.3""" raise NotImplementedError From f4e5059d7e76f57fb7d6fcaf26765c19024c1973 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Mon, 26 Apr 2021 22:32:14 +0200 Subject: [PATCH 0519/1195] Fixes debugging with native coroutines --- ipykernel/ipkernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 9f4259cd7..77e38a707 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -148,7 +148,7 @@ def __init__(self, **kwargs): 'file_extension': '.py' } - async def dispatch_debugpy(self, msg): + def dispatch_debugpy(self, msg): # The first frame is the socket id, we can drop it frame = msg[1].bytes.decode('utf-8') self.log.debug("Debugpy received: %s", frame) From 3d488c7dc3b3c2288cde52432892e75625b70f4c Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Mon, 26 Apr 2021 22:51:19 +0200 Subject: [PATCH 0520/1195] Release 6.0.0a5 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 78fa1d8e7..350a2591d 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "a4") +version_info = (6, 0, 0, "a5") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 779c22087499d33db28bde1c175553030b9fbdc3 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Tue, 27 Apr 2021 11:49:49 +0200 Subject: [PATCH 0521/1195] Allow setting cell name --- ipykernel/compiler.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 4c724f146..7af9f6b44 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -44,8 +44,11 @@ def get_tmp_hash_seed(): return hash_seed def get_file_name(code): - name = murmur2_x86(code, get_tmp_hash_seed()) - return get_tmp_directory() + '/' + str(name) + '.py' + cell_name = os.environ.get("IPYKERNEL_CELL_NAME") + if cell_name is None: + name = murmur2_x86(code, get_tmp_hash_seed()) + cell_name = get_tmp_directory() + '/' + str(name) + '.py' + return cell_name class XCachingCompiler(CachingCompiler): From f0d35275100c017fbe24b84a45ed0574b0ce71ef Mon Sep 17 00:00:00 2001 From: martinRenou Date: Fri, 12 Feb 2021 16:17:17 +0100 Subject: [PATCH 0522/1195] Use matplotlib-inline --- ipykernel/kernelapp.py | 2 +- ipykernel/pylab/backend_inline.py | 243 +----------------------------- ipykernel/pylab/config.py | 106 +------------ ipykernel/zmqshell.py | 9 -- setup.py | 1 + 5 files changed, 4 insertions(+), 357 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 2e9edc0a6..9771c34dc 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -512,7 +512,7 @@ def init_gui_pylab(self): # but lower priority than anything else (mpl.use() for instance). # This only affects matplotlib >= 1.5 if not os.environ.get('MPLBACKEND'): - os.environ['MPLBACKEND'] = 'module://ipykernel.pylab.backend_inline' + os.environ['MPLBACKEND'] = 'module://matplotlib_inline.backend_inline' # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab` # to ensure that any exception is printed straight to stderr. diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index 36af62936..d593ffdc7 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -3,245 +3,4 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import matplotlib -from matplotlib.backends.backend_agg import ( - new_figure_manager, - FigureCanvasAgg, - new_figure_manager_given_figure, -) # analysis: ignore -from matplotlib import colors -from matplotlib._pylab_helpers import Gcf - -from IPython.core.getipython import get_ipython -from IPython.core.pylabtools import select_figure_formats -from IPython.display import display - -from .config import InlineBackend - - -def show(close=None, block=None): - """Show all figures as SVG/PNG payloads sent to the IPython clients. - - Parameters - ---------- - close : bool, optional - If true, a ``plt.close('all')`` call is automatically issued after - sending all the figures. If this is set, the figures will entirely - removed from the internal list of figures. - block : Not used. - The `block` parameter is a Matplotlib experimental parameter. - We accept it in the function signature for compatibility with other - backends. - """ - if close is None: - close = InlineBackend.instance().close_figures - try: - for figure_manager in Gcf.get_all_fig_managers(): - display( - figure_manager.canvas.figure, - metadata=_fetch_figure_metadata(figure_manager.canvas.figure) - ) - finally: - show._to_draw = [] - # only call close('all') if any to close - # close triggers gc.collect, which can be slow - if close and Gcf.get_all_fig_managers(): - matplotlib.pyplot.close('all') - - -# This flag will be reset by draw_if_interactive when called -show._draw_called = False -# list of figures to draw when flush_figures is called -show._to_draw = [] - - -def draw_if_interactive(): - """ - Is called after every pylab drawing command - """ - # signal that the current active figure should be sent at the end of - # execution. Also sets the _draw_called flag, signaling that there will be - # something to send. At the end of the code execution, a separate call to - # flush_figures() will act upon these values - manager = Gcf.get_active() - if manager is None: - return - fig = manager.canvas.figure - - # Hack: matplotlib FigureManager objects in interacive backends (at least - # in some of them) monkeypatch the figure object and add a .show() method - # to it. This applies the same monkeypatch in order to support user code - # that might expect `.show()` to be part of the official API of figure - # objects. - # For further reference: - # https://github.com/ipython/ipython/issues/1612 - # https://github.com/matplotlib/matplotlib/issues/835 - - if not hasattr(fig, 'show'): - # Queue up `fig` for display - fig.show = lambda *a: display(fig, metadata=_fetch_figure_metadata(fig)) - - # If matplotlib was manually set to non-interactive mode, this function - # should be a no-op (otherwise we'll generate duplicate plots, since a user - # who set ioff() manually expects to make separate draw/show calls). - if not matplotlib.is_interactive(): - return - - # ensure current figure will be drawn, and each subsequent call - # of draw_if_interactive() moves the active figure to ensure it is - # drawn last - try: - show._to_draw.remove(fig) - except ValueError: - # ensure it only appears in the draw list once - pass - # Queue up the figure for drawing in next show() call - show._to_draw.append(fig) - show._draw_called = True - - -def flush_figures(): - """Send all figures that changed - - This is meant to be called automatically and will call show() if, during - prior code execution, there had been any calls to draw_if_interactive. - - This function is meant to be used as a post_execute callback in IPython, - so user-caused errors are handled with showtraceback() instead of being - allowed to raise. If this function is not called from within IPython, - then these exceptions will raise. - """ - if not show._draw_called: - return - - if InlineBackend.instance().close_figures: - # ignore the tracking, just draw and close all figures - try: - return show(True) - except Exception as e: - # safely show traceback if in IPython, else raise - ip = get_ipython() - if ip is None: - raise e - else: - ip.showtraceback() - return - try: - # exclude any figures that were closed: - active = set([fm.canvas.figure for fm in Gcf.get_all_fig_managers()]) - for fig in [ fig for fig in show._to_draw if fig in active ]: - try: - display(fig, metadata=_fetch_figure_metadata(fig)) - except Exception as e: - # safely show traceback if in IPython, else raise - ip = get_ipython() - if ip is None: - raise e - else: - ip.showtraceback() - return - finally: - # clear flags for next round - show._to_draw = [] - show._draw_called = False - - -# Changes to matplotlib in version 1.2 requires a mpl backend to supply a default -# figurecanvas. This is set here to a Agg canvas -# See https://github.com/matplotlib/matplotlib/pull/1125 -FigureCanvas = FigureCanvasAgg - - -def configure_inline_support(shell, backend): - """Configure an IPython shell object for matplotlib use. - - Parameters - ---------- - shell : InteractiveShell instance - backend : matplotlib backend - """ - # If using our svg payload backend, register the post-execution - # function that will pick up the results for display. This can only be - # done with access to the real shell object. - - cfg = InlineBackend.instance(parent=shell) - cfg.shell = shell - if cfg not in shell.configurables: - shell.configurables.append(cfg) - - if backend == 'module://ipykernel.pylab.backend_inline': - shell.events.register('post_execute', flush_figures) - - # Save rcParams that will be overwrittern - shell._saved_rcParams = {} - for k in cfg.rc: - shell._saved_rcParams[k] = matplotlib.rcParams[k] - # load inline_rc - matplotlib.rcParams.update(cfg.rc) - new_backend_name = "inline" - else: - try: - shell.events.unregister('post_execute', flush_figures) - except ValueError: - pass - if hasattr(shell, '_saved_rcParams'): - matplotlib.rcParams.update(shell._saved_rcParams) - del shell._saved_rcParams - new_backend_name = "other" - - # only enable the formats once -> don't change the enabled formats (which the user may - # has changed) when getting another "%matplotlib inline" call. - # See https://github.com/ipython/ipykernel/issues/29 - cur_backend = getattr(configure_inline_support, "current_backend", "unset") - if new_backend_name != cur_backend: - # Setup the default figure format - select_figure_formats(shell, cfg.figure_formats, **cfg.print_figure_kwargs) - configure_inline_support.current_backend = new_backend_name - - -def _enable_matplotlib_integration(): - """Enable extra IPython matplotlib integration when we are loaded as the matplotlib backend.""" - from matplotlib import get_backend - ip = get_ipython() - backend = get_backend() - if ip and backend == 'module://%s' % __name__: - from IPython.core.pylabtools import activate_matplotlib - try: - activate_matplotlib(backend) - configure_inline_support(ip, backend) - except (ImportError, AttributeError): - # bugs may cause a circular import on Python 2 - def configure_once(*args): - activate_matplotlib(backend) - configure_inline_support(ip, backend) - ip.events.unregister('post_run_cell', configure_once) - ip.events.register('post_run_cell', configure_once) - -_enable_matplotlib_integration() - -def _fetch_figure_metadata(fig): - """Get some metadata to help with displaying a figure.""" - # determine if a background is needed for legibility - if _is_transparent(fig.get_facecolor()): - # the background is transparent - ticksLight = _is_light([label.get_color() - for axes in fig.axes - for axis in (axes.xaxis, axes.yaxis) - for label in axis.get_ticklabels()]) - if ticksLight.size and (ticksLight == ticksLight[0]).all(): - # there are one or more tick labels, all with the same lightness - return {'needs_background': 'dark' if ticksLight[0] else 'light'} - - return None - -def _is_light(color): - """Determines if a color (or each of a sequence of colors) is light (as - opposed to dark). Based on ITU BT.601 luminance formula (see - https://stackoverflow.com/a/596241).""" - rgbaArr = colors.to_rgba_array(color) - return rgbaArr[:,:3].dot((.299, .587, .114)) > .5 - -def _is_transparent(color): - """Determine transparency from alpha.""" - rgba = colors.to_rgba(color) - return rgba[3] < .5 +from matplotlib_inline.backend_inline import * # analysis: ignore diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index 249389fab..273f5ce8f 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -2,109 +2,5 @@ This module does not import anything from matplotlib. """ -#----------------------------------------------------------------------------- -# Copyright (C) 2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- - -from traitlets.config.configurable import SingletonConfigurable -from traitlets import ( - Dict, Instance, Set, Bool, TraitError, Unicode -) - -#----------------------------------------------------------------------------- -# Configurable for inline backend options -#----------------------------------------------------------------------------- - -def pil_available(): - """Test if PIL/Pillow is available""" - out = False - try: - from PIL import Image - out = True - except: - pass - return out - -# inherit from InlineBackendConfig for deprecation purposes -class InlineBackendConfig(SingletonConfigurable): - pass - -class InlineBackend(InlineBackendConfig): - """An object to store configuration of the inline backend.""" - - # 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. - rc = Dict({'figure.figsize': (6.0,4.0), - # play nicely with white background in the Qt and notebook frontend - 'figure.facecolor': (1,1,1,0), - 'figure.edgecolor': (1,1,1,0), - # 12pt labels get cutoff on 6x4 logplots, so use 10pt. - 'font.size': 10, - # 72 dpi matches SVG/qtconsole - # this only affects PNG export, as SVG has no dpi setting - 'figure.dpi': 72, - # 10pt still needs a little more room on the xlabel: - 'figure.subplot.bottom' : .125 - }, - help="""Subset of matplotlib rcParams that should be different for the - inline backend.""" - ).tag(config=True) - - figure_formats = Set({'png'}, - help="""A set of figure formats to enable: 'png', - 'retina', 'jpeg', 'svg', 'pdf'.""").tag(config=True) - - def _update_figure_formatters(self): - if self.shell is not None: - from IPython.core.pylabtools import select_figure_formats - select_figure_formats(self.shell, self.figure_formats, **self.print_figure_kwargs) - - def _figure_formats_changed(self, name, old, new): - if 'jpg' in new or 'jpeg' in new: - if not pil_available(): - raise TraitError("Requires PIL/Pillow for JPG figures") - self._update_figure_formatters() - - figure_format = Unicode(help="""The figure format to enable (deprecated - use `figure_formats` instead)""").tag(config=True) - - def _figure_format_changed(self, name, old, new): - if new: - self.figure_formats = {new} - - print_figure_kwargs = Dict({'bbox_inches' : 'tight'}, - help="""Extra kwargs to be passed to fig.canvas.print_figure. - - Logical examples include: bbox_inches, quality (for jpeg figures), etc. - """ - ).tag(config=True) - _print_figure_kwargs_changed = _update_figure_formatters - - close_figures = Bool(True, - help="""Close all figures at the end of each cell. - - When True, ensures that each cell starts with no active figures, but it - also means that one must keep track of references in order to edit or - redraw figures in subsequent cells. This mode is ideal for the notebook, - where residual plots from other cells might be surprising. - - When False, one must call figure() to create new figures. This means - that gcf() and getfigs() can reference figures created in other cells, - and the active figure can continue to be edited with pylab/pyplot - methods that reference the current active figure. This mode facilitates - iterative editing of figures, and behaves most consistently with - other matplotlib backends, but figure barriers between cells must - be explicit. - """).tag(config=True) - - shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', - allow_none=True) +from matplotlib_inline.config import * # analysis: ignore diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 14b6feaed..f41bcfb70 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -621,15 +621,6 @@ def init_magics(self): self.register_magics(KernelMagics) self.magics_manager.register_alias('ed', 'edit') - def enable_matplotlib(self, gui=None): - gui, backend = super(ZMQInteractiveShell, self).enable_matplotlib(gui) - - from ipykernel.pylab.backend_inline import configure_inline_support - - configure_inline_support(self, backend) - - return gui, backend - def init_virtualenv(self): # Overridden not to do virtualenv detection, because it's probably # not appropriate in a kernel. To use a kernel in a virtualenv, install diff --git a/setup.py b/setup.py index efb25c035..f86195b36 100644 --- a/setup.py +++ b/setup.py @@ -80,6 +80,7 @@ def run(self): 'traitlets>=4.1.0', 'jupyter_client', 'tornado>=4.2', + 'matplotlib-inline>=0.1.0,<0.2.0' 'appnope;platform_system=="Darwin"', ], extras_require={ From d4b27d49821594d50db3d9af18fa0657da952374 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Sat, 1 May 2021 01:19:31 +0200 Subject: [PATCH 0523/1195] Update IPython to 7.23 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f86195b36..b84d58bf1 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ def run(self): install_requires=[ 'importlib-metadata<4;python_version<"3.8.0"', 'debugpy>=1.0.0', - 'ipython>=7.21.0', + 'ipython>=7.23.0', 'traitlets>=4.1.0', 'jupyter_client', 'tornado>=4.2', From 6a56612ad3ddb42a27bce1b307466f3df35f0124 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Mon, 3 May 2021 10:35:30 +0200 Subject: [PATCH 0524/1195] Add deprecation warnings --- ipykernel/pylab/backend_inline.py | 9 +++++++++ ipykernel/pylab/config.py | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index d593ffdc7..3b53c7629 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -3,4 +3,13 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import warnings + from matplotlib_inline.backend_inline import * # analysis: ignore + + +warnings.warn( + "`ipykernel.pylab.backend_inline` is deprecated, directly " + "use `matplotlib_inline.backend_inline`", + DeprecationWarning +) diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index 273f5ce8f..7846cb89e 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -3,4 +3,13 @@ This module does not import anything from matplotlib. """ +import warnings + from matplotlib_inline.config import * # analysis: ignore + + +warnings.warn( + "`ipykernel.pylab.config` is deprecated, directly " + "use `matplotlib_inline.config`", + DeprecationWarning +) From 317159fd643f76c916afbdc3eb41a4c91940c62b Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Thu, 6 May 2021 10:35:54 +0200 Subject: [PATCH 0525/1195] Release 6.0.0a6 --- ipykernel/_version.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 350a2591d..926d9c470 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "a5") +version_info = (6, 0, 0, "a6") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools diff --git a/setup.py b/setup.py index b84d58bf1..f906aedbf 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,7 @@ def run(self): install_requires=[ 'importlib-metadata<4;python_version<"3.8.0"', 'debugpy>=1.0.0', - 'ipython>=7.23.0', + 'ipython>=7.23.1', 'traitlets>=4.1.0', 'jupyter_client', 'tornado>=4.2', From 6f241d27be8eae23f4d18af506cbca39a6c4b377 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 May 2021 11:01:19 +0200 Subject: [PATCH 0526/1195] flush control queue prior to handling shell messages preserves control channel priority over shell channel - when control thread is running, resolves message-processing races - when control thread is not running, ensures control messages are processed before shell requests --- ipykernel/kernelbase.py | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 6be2a9d6f..3ae03b458 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -4,6 +4,7 @@ # Distributed under the terms of the Modified BSD License. import asyncio +import concurrent.futures from datetime import datetime from functools import partial import itertools @@ -213,8 +214,34 @@ def dispatch_control(self, msg): async def poll_control_queue(self): while True: msg = await self.control_queue.get() + # handle tracers from _flush_control_queue + if isinstance(msg, (concurrent.futures.Future, asyncio.Future)): + msg.set_result(None) + continue await self.process_control(msg) + async def _flush_control_queue(self): + """Flush the control queue, wait for processing of any pending messages""" + if self.control_thread: + control_loop = self.control_thread.io_loop + # concurrent.futures.Futures are threadsafe + # and can be used to await across threads + tracer_future = concurrent.futures.Future() + awaitable_future = asyncio.wrap_future(tracer_future) + else: + control_loop = self.io_loop + tracer_future = awaitable_future = asyncio.Future() + + def _flush(): + # control_stream.flush puts messages on the queue + self.control_stream.flush() + # put Future on the queue after all of those, + # so we can wait for all queued messages to be processed + self.control_queue.put(tracer_future) + + control_loop.add_callback(_flush) + return awaitable_future + async def process_control(self, msg): """dispatch control requests""" idents, msg = self.session.feed_identities(msg, copy=False) @@ -265,6 +292,10 @@ def should_handle(self, stream, msg, idents): async def dispatch_shell(self, msg): """dispatch shell requests""" + + # flush control queue before handling shell requests + await self._flush_control_queue() + idents, msg = self.session.feed_identities(msg, copy=False) try: msg = self.session.deserialize(msg, content=True, copy=False) @@ -630,7 +661,7 @@ async def inspect_request(self, stream, ident, parent): content.get('detail_level', 0), ) if inspect.isawaitable(reply_content): - reply_content = await reply_content + reply_content = await reply_content # Before we send this object over, we scrub it for JSON usage reply_content = json_clean(reply_content) @@ -944,7 +975,7 @@ def _input_request(self, prompt, ident, parent, password=False): raise KeyboardInterrupt("Interrupted by user") from None except Exception as e: self.log.warning("Invalid Message:", exc_info=True) - + try: value = reply["content"]["value"] except Exception: From 749ed915d585fcbff6e878f1fb083588a1154611 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 May 2021 12:06:40 +0200 Subject: [PATCH 0527/1195] test control channel priority --- ipykernel/tests/test_kernel.py | 45 ++++++++++++++++++++++++++++++++++ ipykernel/tests/utils.py | 8 +++--- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 9fe09f43b..13afdcb9f 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -417,3 +417,48 @@ def test_interrupt_during_pdb_set_trace(): # If we failed to interrupt interrupt, this will timeout: reply = get_reply(kc, msg_id2, TIMEOUT) validate_message(reply, 'execute_reply', msg_id2) + + +def test_control_thread_priority(): + + N = 5 + with new_kernel() as kc: + msg_id = kc.execute("pass") + get_reply(kc, msg_id) + + sleep_msg_id = kc.execute("import asyncio; await asyncio.sleep(2)") + + # submit N shell messages + shell_msg_ids = [] + for i in range(N): + shell_msg_ids.append(kc.execute(f"i = {i}")) + + # ensure all shell messages have arrived at the kernel before any control messages + time.sleep(0.5) + # at this point, shell messages should be waiting in msg_queue, + # rather than zmq while the kernel is still in the middle of processing + # the first execution + + # now send N control messages + control_msg_ids = [] + for i in range(N): + msg = kc.session.msg("kernel_info_request", {}) + kc.control_channel.send(msg) + control_msg_ids.append(msg["header"]["msg_id"]) + + # finally, collect the replies on both channels for comparison + sleep_reply = get_reply(kc, sleep_msg_id) + shell_replies = [] + for msg_id in shell_msg_ids: + shell_replies.append(get_reply(kc, msg_id)) + + control_replies = [] + for msg_id in control_msg_ids: + control_replies.append(get_reply(kc, msg_id, channel="control")) + + # verify that all control messages were handled before all shell messages + shell_dates = [msg["header"]["date"] for msg in shell_replies] + control_dates = [msg["header"]["date"] for msg in control_replies] + # comparing first to last ought to be enough, since queues preserve order + # use <= in case of very-fast handling and/or low resolution timers + assert control_dates[-1] <= shell_dates[0] diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 920261e84..386a7a40e 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -53,13 +53,15 @@ def flush_channels(kc=None): validate_message(msg) -def get_reply(kc, msg_id, timeout): - timeout = TIMEOUT +def get_reply(kc, msg_id, timeout=TIMEOUT, channel='shell'): t0 = time() while True: - reply = kc.get_shell_msg(timeout=timeout) + get_msg = getattr(kc, f'get_{channel}_msg') + reply = get_msg(timeout=timeout) if reply['parent_header']['msg_id'] == msg_id: break + # Allow debugging ignored replies + print(f"Ignoring reply not to {msg_id}: {reply}") t1 = time() timeout -= t1 - t0 t0 = t1 From f1748ae70a85f5bd752e44244f2b13e4bd263fa6 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 May 2021 12:59:04 +0200 Subject: [PATCH 0528/1195] inprocess kernel has nothing to flush --- ipykernel/inprocess/ipkernel.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 942907226..bfa715acd 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -87,6 +87,10 @@ async def _abort_queues(self): """ The in-process kernel doesn't abort requests. """ pass + async def _flush_control_queue(self): + """No need to flush control queues for in-process""" + pass + def _input_request(self, prompt, ident, parent, password=False): # Flush output before making the request. self.raw_input_str = None From 1d895051fc2a603c2124b8ca247d4c1a1284c31d Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 May 2021 10:04:00 +0200 Subject: [PATCH 0529/1195] add Kernel.get_parent_header - deprecate Kernel._parent_header - move new multi-parent header dict to new Kernel._parent_headers instead of changing what Kernel._parent_header means --- ipykernel/comm/comm.py | 2 +- ipykernel/kernelbase.py | 95 ++++++++++++++++++++++++++++++----------- 2 files changed, 71 insertions(+), 26 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 63074b455..0e07ea3cd 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -66,7 +66,7 @@ def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys) self.kernel.session.send(self.kernel.iopub_socket, msg_type, content, metadata=json_clean(metadata), - parent=self.kernel._parent_header.get('shell', {}), + parent=self.kernel.get_parent_header("shell"), ident=self.topic, buffers=buffers, ) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 6be2a9d6f..09f4430d7 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -132,8 +132,18 @@ def _default_ident(self): # track associations with current request _allow_stdin = Bool(False) - _parent_header = Dict({'shell': {}, 'control': {}}) + _parent_headers = Dict({"shell": {}, "control": {}}) _parent_ident = Dict({'shell': b'', 'control': b''}) + + @property + def _parent_header(self): + warnings.warn( + "Kernel._parent_header is deprecated in ipykernel 6. Use .get_parent_header()", + DeprecationWarning, + stacklevel=2, + ) + return self.get_parent_header(channel="shell") + # Time to sleep after flushing the stdout/err buffers in each execute # cycle. While this introduces a hard limit on the minimal latency of the # execute cycle, it helps prevent output synchronization problems for @@ -207,6 +217,23 @@ def __init__(self, **kwargs): self.control_queue = Queue() + def get_parent_header(self, channel="shell"): + """Get the parent header associated with a channel. + + .. versionadded:: 6 + + Parameters + ---------- + channel : str + the name of the channel ('shell' or 'control') + + Returns + ------- + header : dict + the parent header for the most recent request on the channel. + """ + return self._parent_headers.get(channel, {}) + def dispatch_control(self, msg): self.control_queue.put_nowait(msg) @@ -484,18 +511,21 @@ def _publish_execute_input(self, code, parent, execution_count): def _publish_status(self, status, channel, parent=None): """send status (busy/idle) on IOPub""" - self.session.send(self.iopub_socket, - 'status', - {'execution_state': status}, - parent=parent or self._parent_header[channel], - ident=self._topic('status'), - ) + self.session.send( + self.iopub_socket, + "status", + {"execution_state": status}, + parent=parent or self.get_parent_header(channel), + ident=self._topic("status"), + ) + def _publish_debug_event(self, event): - self.session.send(self.iopub_socket, - 'debug_event', - event, - parent=self._parent_header['control'], - ident=self._topic('debug_event') + self.session.send( + self.iopub_socket, + "debug_event", + event, + parent=self.get_parent_header("control"), + ident=self._topic("debug_event"), ) def set_parent(self, ident, parent, channel='shell'): @@ -508,7 +538,7 @@ def set_parent(self, ident, parent, channel='shell'): on the stdin channel. """ self._parent_ident[channel] = ident - self._parent_header[channel] = parent + self._parent_headers[channel] = parent def send_response(self, stream, msg_or_type, content=None, ident=None, buffers=None, track=False, header=None, metadata=None, channel='shell'): @@ -520,8 +550,17 @@ def send_response(self, stream, msg_or_type, content=None, ident=None, This relies on :meth:`set_parent` having been called for the current message. """ - return self.session.send(stream, msg_or_type, content, self._parent_header[channel], - ident, buffers, track, header, metadata) + return self.session.send( + stream, + msg_or_type, + content, + self.get_parent_header(channel), + ident, + buffers, + track, + header, + metadata, + ) def init_metadata(self, parent): """Initialize metadata. @@ -630,7 +669,7 @@ async def inspect_request(self, stream, ident, parent): content.get('detail_level', 0), ) if inspect.isawaitable(reply_content): - reply_content = await reply_content + reply_content = await reply_content # Before we send this object over, we scrub it for JSON usage reply_content = json_clean(reply_content) @@ -878,11 +917,16 @@ def getpass(self, prompt='', stream=None): ) if stream is not None: import warnings - warnings.warn("The `stream` parameter of `getpass.getpass` will have no effect when using ipykernel", - UserWarning, stacklevel=2) - return self._input_request(prompt, - self._parent_ident['shell'], - self._parent_header['shell'], + + warnings.warn( + "The `stream` parameter of `getpass.getpass` will have no effect when using ipykernel", + UserWarning, + stacklevel=2, + ) + return self._input_request( + prompt, + self._parent_ident["shell"], + self.get_parent_header("shell"), password=True, ) @@ -897,9 +941,10 @@ def raw_input(self, prompt=''): raise StdinNotImplementedError( "raw_input was called, but this frontend does not support input requests." ) - return self._input_request(str(prompt), - self._parent_ident['shell'], - self._parent_header['shell'], + return self._input_request( + str(prompt), + self._parent_ident["shell"], + self.get_parent_header("shell"), password=False, ) @@ -944,7 +989,7 @@ def _input_request(self, prompt, ident, parent, password=False): raise KeyboardInterrupt("Interrupted by user") from None except Exception as e: self.log.warning("Invalid Message:", exc_info=True) - + try: value = reply["content"]["value"] except Exception: From 31ec59790381c60af3074efd3d1c7201e0e22b06 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 May 2021 14:58:11 +0200 Subject: [PATCH 0530/1195] stop control thread before closing sockets on it (#659) closing sockets in-use by another thread is a recipe for SIGABRT skip `close(all_fds=True)` in favor of explicit cleanup of sockets --- ipykernel/control.py | 15 ++++++++++++--- ipykernel/kernelapp.py | 6 +++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index 7ac56b7f9..4ba21dff5 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -15,7 +15,16 @@ def __init__(self, **kwargs): self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True - def run(self): + def run(self): self.io_loop.make_current() - self.io_loop.start() - self.io_loop.close(all_fds=True) + try: + self.io_loop.start() + finally: + self.io_loop.close() + + def stop(self): + """Stop the thread. + + This method is threadsafe. + """ + self.io_loop.add_callback(self.io_loop.stop) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 9771c34dc..f43baf2c0 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -346,6 +346,10 @@ def close(self): self.log.debug("Closing iopub channel") self.iopub_thread.stop() self.iopub_thread.close() + if self.control_thread and self.control_thread.is_alive(): + self.log.debug("Closing control thread") + self.control_thread.stop() + self.control_thread.join() if self.debugpy_socket and not self.debugpy_socket.closed: self.debugpy_socket.close() @@ -487,7 +491,7 @@ def init_kernel(self): control_stream=control_stream, debugpy_stream=debugpy_stream, debug_shell_socket=self.debug_shell_socket, - shell_stream=shell_stream, + shell_stream=shell_stream, control_thread=self.control_thread, iopub_thread=self.iopub_thread, iopub_socket=self.iopub_socket, From 82872f21688a6333c8dda6d0cb1d17ef45419e42 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 7 May 2021 16:28:36 +0200 Subject: [PATCH 0531/1195] Add Kernel.get_parent to match set_parent instead of get_parent_header, since it's actually returning the parent *message* not the parent header --- ipykernel/kernelbase.py | 54 ++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 6f79ffb3d..31124cedf 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -133,17 +133,17 @@ def _default_ident(self): # track associations with current request _allow_stdin = Bool(False) - _parent_headers = Dict({"shell": {}, "control": {}}) + _parents = Dict({"shell": {}, "control": {}}) _parent_ident = Dict({'shell': b'', 'control': b''}) @property def _parent_header(self): warnings.warn( - "Kernel._parent_header is deprecated in ipykernel 6. Use .get_parent_header()", + "Kernel._parent_header is deprecated in ipykernel 6. Use .get_parent()", DeprecationWarning, stacklevel=2, ) - return self.get_parent_header(channel="shell") + return self.get_parent(channel="shell") # Time to sleep after flushing the stdout/err buffers in each execute # cycle. While this introduces a hard limit on the minimal latency of the @@ -218,23 +218,6 @@ def __init__(self, **kwargs): self.control_queue = Queue() - def get_parent_header(self, channel="shell"): - """Get the parent header associated with a channel. - - .. versionadded:: 6 - - Parameters - ---------- - channel : str - the name of the channel ('shell' or 'control') - - Returns - ------- - header : dict - the parent header for the most recent request on the channel. - """ - return self._parent_headers.get(channel, {}) - def dispatch_control(self, msg): self.control_queue.put_nowait(msg) @@ -546,7 +529,7 @@ def _publish_status(self, status, channel, parent=None): self.iopub_socket, "status", {"execution_state": status}, - parent=parent or self.get_parent_header(channel), + parent=parent or self.get_parent(channel), ident=self._topic("status"), ) @@ -555,12 +538,12 @@ def _publish_debug_event(self, event): self.iopub_socket, "debug_event", event, - parent=self.get_parent_header("control"), + parent=self.get_parent("control"), ident=self._topic("debug_event"), ) def set_parent(self, ident, parent, channel='shell'): - """Set the current parent_header + """Set the current parent request Side effects (IOPub messages) and replies are associated with the request that caused them via the parent_header. @@ -569,7 +552,24 @@ def set_parent(self, ident, parent, channel='shell'): on the stdin channel. """ self._parent_ident[channel] = ident - self._parent_headers[channel] = parent + self._parents[channel] = parent + + def get_parent(self, channel="shell"): + """Get the parent request associated with a channel. + + .. versionadded:: 6 + + Parameters + ---------- + channel : str + the name of the channel ('shell' or 'control') + + Returns + ------- + message : dict + the parent message for the most recent request on the channel. + """ + return self._parents.get(channel, {}) def send_response(self, stream, msg_or_type, content=None, ident=None, buffers=None, track=False, header=None, metadata=None, channel='shell'): @@ -585,7 +585,7 @@ def send_response(self, stream, msg_or_type, content=None, ident=None, stream, msg_or_type, content, - self.get_parent_header(channel), + self.get_parent(channel), ident, buffers, track, @@ -957,7 +957,7 @@ def getpass(self, prompt='', stream=None): return self._input_request( prompt, self._parent_ident["shell"], - self.get_parent_header("shell"), + self.get_parent("shell"), password=True, ) @@ -975,7 +975,7 @@ def raw_input(self, prompt=''): return self._input_request( str(prompt), self._parent_ident["shell"], - self.get_parent_header("shell"), + self.get_parent("shell"), password=False, ) From caa55d0e27c62a524de4f7bbd7fdc1a2daaae983 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 7 May 2021 21:45:28 +0200 Subject: [PATCH 0532/1195] Fixup get_parent_header call --- ipykernel/comm/comm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 0e07ea3cd..8466b685b 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -66,7 +66,7 @@ def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys) self.kernel.session.send(self.kernel.iopub_socket, msg_type, content, metadata=json_clean(metadata), - parent=self.kernel.get_parent_header("shell"), + parent=self.kernel.get_header("shell"), ident=self.topic, buffers=buffers, ) From f2e82a17602ca1454178dc8e0893f87bddef966f Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 7 May 2021 21:54:53 +0200 Subject: [PATCH 0533/1195] Fix typo --- ipykernel/comm/comm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 8466b685b..cb3fbe9fb 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -66,7 +66,7 @@ def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys) self.kernel.session.send(self.kernel.iopub_socket, msg_type, content, metadata=json_clean(metadata), - parent=self.kernel.get_header("shell"), + parent=self.kernel.get_parent("shell"), ident=self.topic, buffers=buffers, ) From ca72a52540aeb8c68c09e1b443605cdb9441a03d Mon Sep 17 00:00:00 2001 From: Marc Udoff Date: Mon, 10 May 2021 11:07:05 -0400 Subject: [PATCH 0534/1195] fix: Backwards compat with older versions of zmq zmq.ROUTING_ID was added in libzmq 4.2.3 as an alias for IDENTITY. --- ipykernel/debugger.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index db5fbc841..f3ea2a917 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -13,6 +13,9 @@ from IPython.core.getipython import get_ipython import debugpy +# Required for backwards compatiblity +ROUTING_ID = getattr(zmq, 'ROUTING_ID', None) or zmq.IDENTITY + class DebugpyMessageQueue: HEADER = 'Content-Length: ' @@ -119,7 +122,7 @@ def _forward_event(self, msg): def _send_request(self, msg): if self.routing_id is None: - self.routing_id = self.debugpy_stream.socket.getsockopt(zmq.ROUTING_ID) + self.routing_id = self.debugpy_stream.socket.getsockopt(ROUTING_ID) content = jsonapi.dumps(msg) content_length = str(len(content)) buf = (DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR).encode('ascii') @@ -166,7 +169,7 @@ def get_host_port(self): def connect_tcp_socket(self): self.debugpy_stream.socket.connect(self._get_endpoint()) - self.routing_id = self.debugpy_stream.socket.getsockopt(zmq.ROUTING_ID) + self.routing_id = self.debugpy_stream.socket.getsockopt(ROUTING_ID) def disconnect_tcp_socket(self): self.debugpy_stream.socket.disconnect(self._get_endpoint()) @@ -260,7 +263,7 @@ def start(self): 'silent': True } self.session.send(self.shell_socket, 'execute_request', content, - None, (self.shell_socket.getsockopt(zmq.ROUTING_ID))) + None, (self.shell_socket.getsockopt(ROUTING_ID))) ident, msg = self.session.recv(self.shell_socket, mode=0) self.debugpy_initialized = msg['content']['status'] == 'ok' From 85e39cee71baaefb05d4e2b545801bf670f62167 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Wed, 12 May 2021 11:07:59 +0200 Subject: [PATCH 0535/1195] Release 6.0.0b0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 926d9c470..936d62ad0 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "a6") +version_info = (6, 0, 0, "b0") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 079f072a8e90422dc74270992589c56ad9f7f9f2 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 13 May 2021 12:32:29 +0200 Subject: [PATCH 0536/1195] prefer SelectorEventLoop on Windows Selector is preferable when ~all out events are on zmq sockets, which uses the unsupported add_reader methods. Tornado 6.1 puts these events in a background thread when Proactor is used, which we can avoid by using Selector. --- ipykernel/kernelapp.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index f43baf2c0..480d98af3 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -565,14 +565,21 @@ def _init_asyncio_patch(self): Pick the older SelectorEventLoopPolicy on Windows if the known-incompatible default policy is in use. + Support for Proactor via a background thread is available in tornado 6.1, + but it is still preferable to run the Selector in the main thread + instead of the background. + do this as early as possible to make it a low priority and overrideable ref: https://github.com/tornadoweb/tornado/issues/2608 - FIXME: if/when tornado supports the defaults in asyncio, - remove and bump tornado requirement for py38 + FIXME: if/when tornado supports the defaults in asyncio without threads, + remove and bump tornado requirement for py38. + Most likely, this will mean a new Python version + where asyncio.ProactorEventLoop supports add_reader and friends. + """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8) and tornado.version_info < (6, 1): + if sys.platform.startswith("win") and sys.version_info >= (3, 8): import asyncio try: from asyncio import ( From 344501416a7afeb3a49c80c85e579955458fef3a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 13 May 2021 06:37:54 -0500 Subject: [PATCH 0537/1195] Add 5.5.x Changelog entries --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79e48f395..f22b5c94f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ ## 5.5 +### 5.5.5 +- Keep preferring SelectorEventLoop on Windows. [#669](https://github.com/ipython/ipykernel/pull/669) + +### 5.5.4 +- Import ``configure_inline_support`` from ``matplotlib_inline`` if available [#654](https://github.com/ipython/ipykernel/pull/654) + +### 5.5.3 +- Revert Backport of #605: Fix Handling of ``shell.should_run_async`` [#622](https://github.com/ipython/ipykernel/pull/622) + +### 5.5.2 +**Note:** This release was deleted from PyPI since it had breaking changes. + +- Changed default timeout to 0.0 seconds for stop_on_error_timeout. [#618](https://github.com/ipython/ipykernel/pull/618) + +### 5.5.1 +**Note:** This release was deleted from PyPI since it had breaking changes. + +- Fix Handling of ``shell.should_run_async``. [#605](https://github.com/ipython/ipykernel/pull/605) + ### 5.5.0 * kernelspec: ensure path is writable before writing `kernel.json`. [#593](https://github.com/ipython/ipykernel/pull/593) * Add `configure_inline_support` and call it in the shell. [#590](https://github.com/ipython/ipykernel/pull/590) From 1ff77e32366106b575b25ff45026c5fa409ec4bc Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 13 May 2021 06:38:37 -0500 Subject: [PATCH 0538/1195] Formatting --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f22b5c94f..63586900b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,23 +5,23 @@ ## 5.5 ### 5.5.5 -- Keep preferring SelectorEventLoop on Windows. [#669](https://github.com/ipython/ipykernel/pull/669) +* Keep preferring SelectorEventLoop on Windows. [#669](https://github.com/ipython/ipykernel/pull/669) ### 5.5.4 -- Import ``configure_inline_support`` from ``matplotlib_inline`` if available [#654](https://github.com/ipython/ipykernel/pull/654) +* Import ``configure_inline_support`` from ``matplotlib_inline`` if available [#654](https://github.com/ipython/ipykernel/pull/654) ### 5.5.3 -- Revert Backport of #605: Fix Handling of ``shell.should_run_async`` [#622](https://github.com/ipython/ipykernel/pull/622) +* Revert Backport of #605: Fix Handling of ``shell.should_run_async`` [#622](https://github.com/ipython/ipykernel/pull/622) ### 5.5.2 **Note:** This release was deleted from PyPI since it had breaking changes. -- Changed default timeout to 0.0 seconds for stop_on_error_timeout. [#618](https://github.com/ipython/ipykernel/pull/618) +* Changed default timeout to 0.0 seconds for stop_on_error_timeout. [#618](https://github.com/ipython/ipykernel/pull/618) ### 5.5.1 **Note:** This release was deleted from PyPI since it had breaking changes. -- Fix Handling of ``shell.should_run_async``. [#605](https://github.com/ipython/ipykernel/pull/605) +* Fix Handling of ``shell.should_run_async``. [#605](https://github.com/ipython/ipykernel/pull/605) ### 5.5.0 * kernelspec: ensure path is writable before writing `kernel.json`. [#593](https://github.com/ipython/ipykernel/pull/593) From 4c3ef6f46330ac51615ba3fae78d2a86aada5627 Mon Sep 17 00:00:00 2001 From: Marco Monteiro Date: Thu, 13 May 2021 12:46:17 -0700 Subject: [PATCH 0539/1195] fix keyboard interrupt issue in dispatch_shell --- ipykernel/kernelbase.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 31124cedf..d69750e31 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -352,6 +352,9 @@ async def dispatch_shell(self, msg): await result except Exception: self.log.error("Exception in message handler:", exc_info=True) + except KeyboardInterrupt: + # Ctrl-c shouldn't crash the kernel here. + self.log.error("KeyboardInterrupt caught in kernel.") finally: try: self.post_handler_hook() From 891b5f6a3d23d9ea4b02be3618de8ad6c2bc02f6 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 4 Jun 2021 21:41:43 +0200 Subject: [PATCH 0540/1195] overload isatty to return True --- ipykernel/iostream.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index ce69334d8..9036d5dc8 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -381,6 +381,13 @@ def __init__( else: raise ValueError("echo argument must be a file like object") + def isatty(self): + """Return a bool indicating whether this is an 'interactive' stream. + + The standard ipykernel streams are assumed to be interactive. + """ + return True + def _setup_stream_redirects(self, name): pr, pw = os.pipe() fno = getattr(sys, name).fileno() From 54af64e90ac0df49628a1ee7cc240c47e539146c Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 4 Jun 2021 21:42:17 +0200 Subject: [PATCH 0541/1195] fix whitespace --- ipykernel/iostream.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 9036d5dc8..cd74fca6f 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -383,11 +383,11 @@ def __init__( def isatty(self): """Return a bool indicating whether this is an 'interactive' stream. - + The standard ipykernel streams are assumed to be interactive. """ return True - + def _setup_stream_redirects(self, name): pr, pw = os.pipe() fno = getattr(sys, name).fileno() From 185767d2bec1d29b7ba12a65f0f9983b81342af9 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Fri, 4 Jun 2021 21:58:44 +0200 Subject: [PATCH 0542/1195] fix tests --- ipykernel/tests/test_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index c0e7021af..7d425e2bd 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -25,7 +25,7 @@ def test_io_api(): ctx.term() assert stream.errors is None - assert not stream.isatty() + assert stream.isatty() with nt.assert_raises(io.UnsupportedOperation): stream.detach() with nt.assert_raises(io.UnsupportedOperation): From 0a2267017e12a2cb0beb4a9f1827cc34e47f4692 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 7 Jun 2021 10:21:49 +0200 Subject: [PATCH 0543/1195] call metadata methods on abort replies ensures aborted messages have consistent metadata with real replies --- ipykernel/kernelbase.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index d69750e31..83e9a1ac9 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -354,7 +354,7 @@ async def dispatch_shell(self, msg): self.log.error("Exception in message handler:", exc_info=True) except KeyboardInterrupt: # Ctrl-c shouldn't crash the kernel here. - self.log.error("KeyboardInterrupt caught in kernel.") + self.log.error("KeyboardInterrupt caught in kernel.") finally: try: self.post_handler_hook() @@ -921,12 +921,15 @@ def stop_aborting(): def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" - self.log.info("Aborting:") - self.log.info("%s", msg) + self.log.info( + f"Aborting {msg['header']['msg_id']}: {msg['header']['msg_type']}" + ) reply_type = msg["header"]["msg_type"].rsplit("_", 1)[0] + "_reply" status = {"status": "aborted"} - md = {"engine": self.ident} + md = self.init_metadata(msg) + md = self.finish_metadata(msg, md, status) md.update(status) + self.session.send( stream, reply_type, metadata=md, content=status, parent=msg, ident=idents, From d7567b04230060c5b5cecd84fcffdb03baed57f3 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 8 Jun 2021 13:09:28 +0200 Subject: [PATCH 0544/1195] make isatty an option --- ipykernel/iostream.py | 10 +++++++--- ipykernel/tests/test_io.py | 12 +++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index cd74fca6f..892ea21f0 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -320,7 +320,7 @@ def _watch_pipe_fd(self): self._exc = sys.exc_info() def __init__( - self, session, pub_thread, name, pipe=None, echo=None, *, watchfd=True + self, session, pub_thread, name, pipe=None, echo=None, *, watchfd=True, isatty=True, ): """ Parameters @@ -333,6 +333,8 @@ def __init__( the file descriptor by its number. It will spawn a watching thread, that will swap the give file descriptor for a pipe, read from the pipe, and insert this into the current Stream. + isatty: bool (default, True) + Indication of whether this stream has termimal capabilities (e.g. can handle colors) """ if pipe is not None: @@ -364,6 +366,7 @@ def __init__( self._io_loop = pub_thread.io_loop self._new_buffer() self.echo = None + self._isatty = bool(isatty) if ( watchfd @@ -384,9 +387,10 @@ def __init__( def isatty(self): """Return a bool indicating whether this is an 'interactive' stream. - The standard ipykernel streams are assumed to be interactive. + Returns: + Boolean """ - return True + return self._isatty def _setup_stream_redirects(self, name): pr, pw = os.pipe() diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index 7d425e2bd..014aaf9ce 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -17,7 +17,7 @@ def test_io_api(): thread = IOPubThread(pub) thread.start() - stream = OutStream(session, thread, 'stdout') + stream = OutStream(session, thread, 'stdout', isatty=True) # cleanup unused zmq objects before we start testing thread.stop() @@ -38,3 +38,13 @@ def test_io_api(): stream.seek(0) with nt.assert_raises(io.UnsupportedOperation): stream.tell() + +def test_io_isatty(): + session = Session() + ctx = zmq.Context() + pub = ctx.socket(zmq.PUB) + thread = IOPubThread(pub) + thread.start() + + stream = OutStream(session, thread, 'stdout', isatty=False) + assert not stream.isatty() From 05ecce6669667716020b7623e4593551947cf744 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 8 Jun 2021 20:19:44 +0200 Subject: [PATCH 0545/1195] change default value of isatty to False --- ipykernel/iostream.py | 4 ++-- ipykernel/tests/test_io.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 892ea21f0..eef51aade 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -320,7 +320,7 @@ def _watch_pipe_fd(self): self._exc = sys.exc_info() def __init__( - self, session, pub_thread, name, pipe=None, echo=None, *, watchfd=True, isatty=True, + self, session, pub_thread, name, pipe=None, echo=None, *, watchfd=True, isatty=False, ): """ Parameters @@ -333,7 +333,7 @@ def __init__( the file descriptor by its number. It will spawn a watching thread, that will swap the give file descriptor for a pipe, read from the pipe, and insert this into the current Stream. - isatty: bool (default, True) + isatty: bool (default, False) Indication of whether this stream has termimal capabilities (e.g. can handle colors) """ diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index 014aaf9ce..7e06e343c 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -25,7 +25,7 @@ def test_io_api(): ctx.term() assert stream.errors is None - assert stream.isatty() + assert not stream.isatty() with nt.assert_raises(io.UnsupportedOperation): stream.detach() with nt.assert_raises(io.UnsupportedOperation): @@ -46,5 +46,5 @@ def test_io_isatty(): thread = IOPubThread(pub) thread.start() - stream = OutStream(session, thread, 'stdout', isatty=False) - assert not stream.isatty() + stream = OutStream(session, thread, 'stdout', isatty=True) + assert stream.isatty() From 5a5c7d429245f547b196e14342946ae6a6337396 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 8 Jun 2021 21:39:08 +0200 Subject: [PATCH 0546/1195] fix tests --- ipykernel/tests/test_io.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index 7e06e343c..cf68b88ea 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -17,7 +17,7 @@ def test_io_api(): thread = IOPubThread(pub) thread.start() - stream = OutStream(session, thread, 'stdout', isatty=True) + stream = OutStream(session, thread, 'stdout') # cleanup unused zmq objects before we start testing thread.stop() From abbcc0ef43a402cd675c1d7801108a27e3dc63b1 Mon Sep 17 00:00:00 2001 From: Pieter Eendebak Date: Tue, 8 Jun 2021 23:12:45 +0200 Subject: [PATCH 0547/1195] fix linting --- ipykernel/iostream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index eef51aade..e37840be2 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -333,7 +333,7 @@ def __init__( the file descriptor by its number. It will spawn a watching thread, that will swap the give file descriptor for a pipe, read from the pipe, and insert this into the current Stream. - isatty: bool (default, False) + isatty : bool (default, False) Indication of whether this stream has termimal capabilities (e.g. can handle colors) """ From bd43976b936a82695286fa8f8050f8bf5da4b18e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 9 Jun 2021 11:26:16 -0500 Subject: [PATCH 0548/1195] Release 6.0.0rc0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 936d62ad0..c41a0d0cc 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "b0") +version_info = (6, 0, 0, "rc0") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From ef01782e3f9d7718f81d337dabfafd2267cc4f80 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 14 Jun 2021 16:12:23 +0200 Subject: [PATCH 0549/1195] Remove deprecated profile options of connect.py --- CHANGELOG.md | 2 ++ ipykernel/connect.py | 76 ++++++-------------------------------------- 2 files changed, 12 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63586900b..7e70dfaaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changes in IPython kernel +* remove `find_connection_file` and profile arg of `connect_qtconsole` and `get_connection_info`, deprecated since IPykernel 4.2.2. + * Set `stop_on_error_timeout` default to 0.0 matching pre 5.5.0 default behavior with correctly working flag from 5.5.0. ## 5.5 diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 08c374141..5f3560b39 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -34,80 +34,27 @@ def get_connection_file(app=None): return filefind(app.connection_file, ['.', app.connection_dir]) -def find_connection_file(filename='kernel-*.json', profile=None): - """DEPRECATED: find a connection file, and return its absolute path. - - THIS FUNCTION IS DEPRECATED. Use jupyter_client.find_connection_file instead. - - Parameters - ---------- - filename : str - The connection file or fileglob to search for. - profile : str [optional] - The name of the profile to use when searching for the connection file, - if different from the current IPython session or 'default'. - - Returns - ------- - str : The absolute path of the connection file. - """ - - import warnings - warnings.warn("""ipykernel.find_connection_file is deprecated, use jupyter_client.find_connection_file""", - DeprecationWarning, stacklevel=2) - from IPython.core.application import BaseIPythonApplication as IPApp - try: - # quick check for absolute path, before going through logic - return filefind(filename) - except IOError: - pass - - if profile is None: - # profile unspecified, check if running from an IPython app - if IPApp.initialized(): - app = IPApp.instance() - profile_dir = app.profile_dir - else: - # not running in IPython, use default profile - profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), 'default') - else: - # find profiledir by profile name: - profile_dir = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), profile) - security_dir = profile_dir.security_dir - - return jupyter_client.find_connection_file(filename, path=['.', security_dir]) - - -def _find_connection_file(connection_file, profile=None): +def _find_connection_file(connection_file): """Return the absolute path for a connection file - If nothing specified, return current Kernel's connection file - - If profile specified, show deprecation warning about finding connection files in profiles - Otherwise, call jupyter_client.find_connection_file """ if connection_file is None: # get connection file from current kernel return get_connection_file() else: - # connection file specified, allow shortnames: - if profile is not None: - warnings.warn( - "Finding connection file by profile is deprecated.", - DeprecationWarning, stacklevel=3, - ) - return find_connection_file(connection_file, profile=profile) - else: - return jupyter_client.find_connection_file(connection_file) - - -def get_connection_info(connection_file=None, unpack=False, profile=None): + return jupyter_client.find_connection_file(connection_file) + + +def get_connection_info(connection_file=None, unpack=False): """Return the connection information for the current Kernel. Parameters ---------- connection_file : str [optional] The connection file to be used. Can be given by absolute path, or - IPython will search in the security directory of a given profile. + IPython will search in the security directory. If run from IPython, If unspecified, the connection file for the currently running @@ -115,14 +62,13 @@ def get_connection_info(connection_file=None, unpack=False, profile=None): unpack : bool [default: False] if True, return the unpacked dict, otherwise just the string contents of the file. - profile : DEPRECATED Returns ------- The connection dictionary of the current kernel, as string or dict, depending on `unpack`. """ - cf = _find_connection_file(connection_file, profile) + cf = _find_connection_file(connection_file) with open(cf) as f: info = f.read() @@ -134,7 +80,7 @@ def get_connection_info(connection_file=None, unpack=False, profile=None): return info -def connect_qtconsole(connection_file=None, argv=None, profile=None): +def connect_qtconsole(connection_file=None, argv=None): """Connect a qtconsole to the current kernel. This is useful for connecting a second qtconsole to a kernel, or to a @@ -144,14 +90,13 @@ def connect_qtconsole(connection_file=None, argv=None, profile=None): ---------- connection_file : str [optional] The connection file to be used. Can be given by absolute path, or - IPython will search in the security directory of a given profile. + IPython will search in the security directory. If run from IPython, If unspecified, the connection file for the currently running IPython Kernel will be used, which is only allowed from inside a kernel. argv : list [optional] Any extra args to be passed to the console. - profile : DEPRECATED Returns ------- @@ -159,7 +104,7 @@ def connect_qtconsole(connection_file=None, argv=None, profile=None): """ argv = [] if argv is None else argv - cf = _find_connection_file(connection_file, profile) + cf = _find_connection_file(connection_file) cmd = ';'.join([ "from IPython.qt.console import qtconsoleapp", @@ -180,7 +125,6 @@ def connect_qtconsole(connection_file=None, argv=None, profile=None): __all__ = [ 'write_connection_file', 'get_connection_file', - 'find_connection_file', 'get_connection_info', 'connect_qtconsole', ] From 99328692f5b3dd5f3c15c27b035eb7e110ee581e Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 14 Jun 2021 16:06:07 +0200 Subject: [PATCH 0550/1195] Remove `ipykernel.codeutil` deprecated since IPykernel 4.3.1 (Feb 2016) --- ipykernel/codeutil.py | 38 ------------------------------- ipykernel/pickleutil.py | 7 +----- ipykernel/tests/test_serialize.py | 4 ++-- setup.py | 13 ++++++----- 4 files changed, 10 insertions(+), 52 deletions(-) delete mode 100644 ipykernel/codeutil.py diff --git a/ipykernel/codeutil.py b/ipykernel/codeutil.py deleted file mode 100644 index 08212a068..000000000 --- a/ipykernel/codeutil.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Utilities to enable code objects to be pickled. - -Any process that import this module will be able to pickle code objects. This -includes the func_code attribute of any function. Once unpickled, new -functions can be built using new.function(code, globals()). Eventually -we need to automate all of this so that functions themselves can be pickled. - -Reference: A. Tremols, P Cogolo, "Python Cookbook," p 302-305 -""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import warnings -warnings.warn("ipykernel.codeutil is deprecated since IPykernel 4.3.1. It has moved to ipyparallel.serialize", - DeprecationWarning, - stacklevel=2 -) - -import copyreg -import sys -import types - -def code_ctor(*args): - return types.CodeType(*args) - -def reduce_code(co): - args = [co.co_argcount, co.co_nlocals, co.co_stacksize, - co.co_flags, co.co_code, co.co_consts, co.co_names, - co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno, - co.co_lnotab, co.co_freevars, co.co_cellvars] - if sys.version_info[0] >= 3: - args.insert(1, co.co_kwonlyargcount) - if sys.version_info > (3, 8, 0, 'alpha', 3): - args.insert(1, co.co_posonlyargcount) - return code_ctor, tuple(args) - -copyreg.pickle(types.CodeType, reduce_code) diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 281b8f9ea..a2f3da11b 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -18,12 +18,7 @@ from ipython_genutils.py3compat import buffer_to_bytes # This registers a hook when it's imported -try: - # available since ipyparallel 5.1.1 - from ipyparallel.serialize import codeutil -except ImportError: - # Deprecated since ipykernel 4.3.1 - from ipykernel import codeutil +from ipyparallel.serialize import codeutil from traitlets.log import get_logger diff --git a/ipykernel/tests/test_serialize.py b/ipykernel/tests/test_serialize.py index 849f7208b..bee3a2641 100644 --- a/ipykernel/tests/test_serialize.py +++ b/ipykernel/tests/test_serialize.py @@ -6,9 +6,9 @@ import pickle from collections import namedtuple -from ipykernel.serialize import serialize_object, deserialize_object +from ipyparallel.serialize import serialize_object, deserialize_object from IPython.testing import decorators as dec -from ipykernel.pickleutil import CannedArray, CannedClass, interactive +from ipyparallel.serialize.canning import CannedArray, CannedClass, interactive def roundtrip(obj): diff --git a/setup.py b/setup.py index f906aedbf..272cb043d 100644 --- a/setup.py +++ b/setup.py @@ -84,12 +84,13 @@ def run(self): 'appnope;platform_system=="Darwin"', ], extras_require={ - 'test': [ - 'pytest !=5.3.4', - 'pytest-cov', - 'flaky', - 'nose', # nose because there are still a few nose.tools imports hanging around - 'jedi<=0.17.2' + "test": [ + "pytest !=5.3.4", + "pytest-cov", + "flaky", + "nose", # nose because there are still a few nose.tools imports hanging around + "jedi<=0.17.2", + "ipyparallel", ], }, classifiers=[ From 1a50cda50837de89cc2a4047fc02b3aff254d48a Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 14 Jun 2021 12:15:16 +0200 Subject: [PATCH 0551/1195] Return len of item written to OutStream Closes #682 Remove the piece of logic that handle `not isinstance(str)` it is a leftover from Python 2, in pure Python, sys.stdout.write only accepts str, therefore we have no reason not to do the same. --- ipykernel/iostream.py | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index e37840be2..f2f2d7783 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -487,7 +487,21 @@ def _flush(self): self.session.send(self.pub_thread, 'stream', content=content, parent=self.parent_header, ident=self.topic) - def write(self, string): + def write(self, string: str) -> int: + """Write to current stream after encoding if necessary + + Returns + ------- + len : int + number of items from input parameter written to stream. + + """ + + if not isinstance(string, str): + raise ValueError( + "TypeError: write() argument must be str, not {type(string)}" + ) + if self.echo is not None: try: self.echo.write(string) @@ -499,13 +513,10 @@ def write(self, string): if self.pub_thread is None: raise ValueError('I/O operation on closed file') else: - # Make sure that we're handling unicode - if not isinstance(string, str): - string = string.decode(self.encoding, 'replace') is_child = (not self._is_master_process()) # only touch the buffer in the IO thread to avoid races - self.pub_thread.schedule(lambda : self._buffer.write(string)) + self.pub_thread.schedule(lambda: self._buffer.write(string)) if is_child: # mp.Pool cannot be trusted to flush promptly (or ever), # and this helps. @@ -517,6 +528,8 @@ def write(self, string): else: self._schedule_flush() + return len(string) + def writelines(self, sequence): if self.pub_thread is None: raise ValueError('I/O operation on closed file') From 6f12806d3441d5c0ebd2550c8f8499a81827ede6 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 14 Jun 2021 16:01:16 +0200 Subject: [PATCH 0552/1195] Misc Updates to changelog. Make it slightly easier for users to understand why they shoudl migrate (or not) to 6.0. Feel free to add entries. --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e70dfaaa..8db0e82ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,46 @@ * remove `find_connection_file` and profile arg of `connect_qtconsole` and `get_connection_info`, deprecated since IPykernel 4.2.2. +## 6.0.0 + +IPykernel 6.0 is the first major release in about two years, that brings a number of improvements code cleanup and new +features to IPython. + +You should be able to view all closed issues and merged Pull Request for this +milestone [on +GitHub](https://github.com/ipython/ipykernel/issues?q=milestone%3A6.0+is%3Aclosed+), +as for any major releases, we advise greater care when updating that for minor +release and welcome any feedback (~50 Pull-requests). + +IPykernel 6 should contain all changes of the 5.x series, in addition to the +followings non-exhaustive changes. + + + - The control channel on IPykernel 6.0 is ran in a separate thread, this may + change the order in which messages are process, though this change was necessary + to accommodate the debugger. + + - Support for the debugger protocol, when using jupyter lab, retrolab or any + frontend supporting the debugger protocol you should have access to the + debugger functionalities. + + - We now have a new dependency: `matplotlib-inline`, this helps to separate the + circular dependency between IPython/IPykernel and matplotlib. + + - All outputs to stdout/stderr should now be captured, including subprocesses + and output of compiled libraries (blas, lapack....). In notebook + server, some outputs that would previously go to the notebooks logs will now + both head to notebook logs and in notebooks outputs. In terminal frontend + like Jupyter Console, Emacs or other, this may ends up as duplicated outputs. + + - coroutines are now native (async-def) , instead of using tornado's + `@gen.coroutine` + + - OutStreams can now be configured to report `istty() == True`, while this + should make some output nicer (for example colored), it is likely to break + others. Use with care. + + * Set `stop_on_error_timeout` default to 0.0 matching pre 5.5.0 default behavior with correctly working flag from 5.5.0. ## 5.5 From bcd774e2cfc2392a01f8b9983929a9de96fb67af Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 15 Jun 2021 14:57:08 +0200 Subject: [PATCH 0553/1195] Update CHANGELOG.md Co-authored-by: Steven Silvester --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8db0e82ff..576321da8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ followings non-exhaustive changes. change the order in which messages are process, though this change was necessary to accommodate the debugger. - - Support for the debugger protocol, when using jupyter lab, retrolab or any + - Support for the debugger protocol, when using `JupyterLab`, `RetroLab` or any frontend supporting the debugger protocol you should have access to the debugger functionalities. From 93884282ab13b37095f42049b904414b6a0bc863 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 15 Jun 2021 14:59:18 +0200 Subject: [PATCH 0554/1195] more --- CHANGELOG.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 576321da8..ce02f1885 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,14 +17,14 @@ IPykernel 6 should contain all changes of the 5.x series, in addition to the followings non-exhaustive changes. - - The control channel on IPykernel 6.0 is ran in a separate thread, this may - change the order in which messages are process, though this change was necessary - to accommodate the debugger. - - Support for the debugger protocol, when using `JupyterLab`, `RetroLab` or any frontend supporting the debugger protocol you should have access to the debugger functionalities. + - The control channel on IPykernel 6.0 is ran in a separate thread, this may + change the order in which messages are process, though this change was necessary + to accommodate the debugger. + - We now have a new dependency: `matplotlib-inline`, this helps to separate the circular dependency between IPython/IPykernel and matplotlib. @@ -41,6 +41,13 @@ followings non-exhaustive changes. should make some output nicer (for example colored), it is likely to break others. Use with care. +## Deprecations in 6.0 + +## Removal in 6.0 + + - ipykernel.codeutils was deprecated since 4.x series (2016) and has been removed, please import similar + functionalities from `ipyparallel` + * Set `stop_on_error_timeout` default to 0.0 matching pre 5.5.0 default behavior with correctly working flag from 5.5.0. From 8945f814ad898d5e60d781b7063d8cb827cb6fcc Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 15 Jun 2021 15:04:43 +0200 Subject: [PATCH 0555/1195] more changes --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce02f1885..b2d08bcd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,5 @@ # Changes in IPython kernel -* remove `find_connection_file` and profile arg of `connect_qtconsole` and `get_connection_info`, deprecated since IPykernel 4.2.2. ## 6.0.0 @@ -43,11 +42,18 @@ followings non-exhaustive changes. ## Deprecations in 6.0 + - `Kernel`s now support only a single shell stream, multiple streams will now be ignored. The attribute + `Kernel.shell_streams` (plural) is deprecated in ipykernel 6.0. Use `Kernel.shell_stream` (singular) + + - `Kernel._parent_header` is deprecated, even though it was private. Use `.get_parent()` now. + ## Removal in 6.0 - ipykernel.codeutils was deprecated since 4.x series (2016) and has been removed, please import similar functionalities from `ipyparallel` + - remove `find_connection_file` and `profile` argument of `connect_qtconsole` and `get_connection_info`, deprecated since IPykernel 4.2.2 (2016). + * Set `stop_on_error_timeout` default to 0.0 matching pre 5.5.0 default behavior with correctly working flag from 5.5.0. From effe38a71d919d46da817cad29d2c714d81a2233 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 15 Jun 2021 15:10:59 +0200 Subject: [PATCH 0556/1195] Remove deprecated SocketABC since 4.5.0 --- ipykernel/inprocess/socket.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index d1c398b01..ad901d81c 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -11,27 +11,6 @@ from traitlets import HasTraits, Instance, Int -#----------------------------------------------------------------------------- -# Generic socket interface -#----------------------------------------------------------------------------- - -class SocketABC(object, metaclass=abc.ABCMeta): - - @abc.abstractmethod - def recv_multipart(self, flags=0, copy=True, track=False): - raise NotImplementedError - - @abc.abstractmethod - def send_multipart(self, msg_parts, flags=0, copy=True, track=False): - raise NotImplementedError - - @classmethod - def register(cls, other_cls): - if other_cls is not DummySocket: - warnings.warn("SocketABC is deprecated since ipykernel version 4.5.0.", - DeprecationWarning, stacklevel=2) - abc.ABCMeta.register(cls, other_cls) - #----------------------------------------------------------------------------- # Dummy socket class #----------------------------------------------------------------------------- @@ -60,5 +39,3 @@ def send_multipart(self, msg_parts, flags=0, copy=True, track=False): def flush(self, timeout=1.0): """no-op to comply with stream API""" pass - -SocketABC.register(DummySocket) From c526f42abbfb51fd6156a8d192350ccddf834407 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 15 Jun 2021 15:13:55 +0200 Subject: [PATCH 0557/1195] Remove deprecated source parameter since 4.0.1 (2015) --- ipykernel/zmqshell.py | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index f41bcfb70..34308ae5b 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -56,8 +56,6 @@ # Functions and classes #----------------------------------------------------------------------------- -_sentinel = object() - class ZMQDisplayPublisher(DisplayPublisher): """A display publisher that publishes data using a ZeroMQ PUB socket.""" @@ -97,7 +95,6 @@ def publish( self, data, metadata=None, - source=_sentinel, transient=None, update=False, ): @@ -115,23 +112,7 @@ def publish( Transient data should not be persisted to documents. update : bool, optional, keyword-only If True, send an update_display_data message instead of display_data. - source : unused - Value will have no effect on function behavior. Parameter is still - present for backward compatibility but will be removed in the - future. - - .. deprecated:: 4.0.1 - - `source` has been deprecated and no-op since ipykernel 4.0.1 - (2015) """ - if source is not _sentinel: - warnings.warn( - "`source` has been deprecated since ipykernel 4.0.1 " - "and will have no effect", - DeprecationWarning, - stacklevel=2, - ) self._flush_streams() if metadata is None: metadata = {} From 480d4d93b87d4c4b9d16bd6f6028a92a031d0135 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 15 Jun 2021 16:21:08 +0200 Subject: [PATCH 0558/1195] Apply Willingc corrections. Co-authored-by: Carol Willing --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2d08bcd6..6926b4b1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## 6.0.0 -IPykernel 6.0 is the first major release in about two years, that brings a number of improvements code cleanup and new +IPykernel 6.0 is the first major release in about two years, that brings a number of improvements, code cleanup, and new features to IPython. You should be able to view all closed issues and merged Pull Request for this @@ -13,15 +13,15 @@ as for any major releases, we advise greater care when updating that for minor release and welcome any feedback (~50 Pull-requests). IPykernel 6 should contain all changes of the 5.x series, in addition to the -followings non-exhaustive changes. +following non-exhaustive changes. - Support for the debugger protocol, when using `JupyterLab`, `RetroLab` or any frontend supporting the debugger protocol you should have access to the debugger functionalities. - - The control channel on IPykernel 6.0 is ran in a separate thread, this may - change the order in which messages are process, though this change was necessary + - The control channel on IPykernel 6.0 is run in a separate thread, this may + change the order in which messages are processed, though this change was necessary to accommodate the debugger. - We now have a new dependency: `matplotlib-inline`, this helps to separate the @@ -49,7 +49,7 @@ followings non-exhaustive changes. ## Removal in 6.0 - - ipykernel.codeutils was deprecated since 4.x series (2016) and has been removed, please import similar + - `ipykernel.codeutils` was deprecated since 4.x series (2016) and has been removed, please import similar functionalities from `ipyparallel` - remove `find_connection_file` and `profile` argument of `connect_qtconsole` and `get_connection_info`, deprecated since IPykernel 4.2.2 (2016). From bf8006a7d44208fa596d8f2b30f16502c8b35d88 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 16 Jun 2021 11:42:38 -0500 Subject: [PATCH 0559/1195] Remove pin on Jedi because that was already fixed in IPython --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 272cb043d..b0080adb2 100644 --- a/setup.py +++ b/setup.py @@ -89,7 +89,6 @@ def run(self): "pytest-cov", "flaky", "nose", # nose because there are still a few nose.tools imports hanging around - "jedi<=0.17.2", "ipyparallel", ], }, From a818961ef5c2523226739fee33ba3d352b40ad68 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 22 Jun 2021 10:18:42 -0500 Subject: [PATCH 0560/1195] Release 6.0.0rc1 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index c41a0d0cc..16defdfbc 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "rc0") +version_info = (6, 0, 0, "rc1") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From a947f87e06f5b775eb6138db60a49961a0602cfd Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 24 Jun 2021 16:59:09 +0200 Subject: [PATCH 0561/1195] Remove references to deprecated ipyparallel which placed a hard dependency on ipyparallel for kernel startup ipyparallel has these overridden since 5.0 --- ipykernel/tests/test_serialize.py | 201 ------------------------------ ipykernel/zmqshell.py | 9 +- 2 files changed, 1 insertion(+), 209 deletions(-) delete mode 100644 ipykernel/tests/test_serialize.py diff --git a/ipykernel/tests/test_serialize.py b/ipykernel/tests/test_serialize.py deleted file mode 100644 index bee3a2641..000000000 --- a/ipykernel/tests/test_serialize.py +++ /dev/null @@ -1,201 +0,0 @@ -"""test serialization tools""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import pickle -from collections import namedtuple - -from ipyparallel.serialize import serialize_object, deserialize_object -from IPython.testing import decorators as dec -from ipyparallel.serialize.canning import CannedArray, CannedClass, interactive - - -def roundtrip(obj): - """roundtrip an object through serialization""" - bufs = serialize_object(obj) - obj2, remainder = deserialize_object(bufs) - assert remainder == [] - return obj2 - - -SHAPES = ((100,), (1024,10), (10,8,6,5), (), (0,)) -DTYPES = ('uint8', 'float64', 'int32', [('g', 'float32')], '|S10') - - -def new_array(shape, dtype): - import numpy - return numpy.random.random(shape).astype(dtype) - -def test_roundtrip_simple(): - for obj in [ - 'hello', - dict(a='b', b=10), - [1,2,'hi'], - (b'123', 'hello'), - ]: - obj2 = roundtrip(obj) - assert obj == obj2 - -def test_roundtrip_nested(): - for obj in [ - dict(a=range(5), b={1:b'hello'}), - [range(5),[range(3),(1,[b'whoda'])]], - ]: - obj2 = roundtrip(obj) - assert obj == obj2 - -def test_roundtrip_buffered(): - for obj in [ - dict(a=b"x"*1025), - b"hello"*500, - [b"hello"*501, 1,2,3] - ]: - bufs = serialize_object(obj) - assert len(bufs) == 2 - obj2, remainder = deserialize_object(bufs) - assert remainder == [] - assert obj == obj2 - -def test_roundtrip_memoryview(): - b = b'asdf' * 1025 - view = memoryview(b) - bufs = serialize_object(view) - assert len(bufs) == 2 - v2, remainder = deserialize_object(bufs) - assert remainder == [] - assert v2.tobytes() == b - -@dec.skip_without('numpy') -def test_numpy(): - import numpy - from numpy.testing.utils import assert_array_equal - for shape in SHAPES: - for dtype in DTYPES: - A = new_array(shape, dtype=dtype) - bufs = serialize_object(A) - bufs = [memoryview(b) for b in bufs] - B, r = deserialize_object(bufs) - assert r == [] - assert A.shape == B.shape - assert A.dtype == B.dtype - assert_array_equal(A,B) - -@dec.skip_without('numpy') -def test_recarray(): - import numpy - from numpy.testing.utils import assert_array_equal - for shape in SHAPES: - for dtype in [ - [('f', float), ('s', '|S10')], - [('n', int), ('s', '|S1'), ('u', 'uint32')], - ]: - A = new_array(shape, dtype=dtype) - - bufs = serialize_object(A) - B, r = deserialize_object(bufs) - assert r == [] - assert A.shape == B.shape - assert A.dtype == B.dtype - assert_array_equal(A,B) - -@dec.skip_without('numpy') -def test_numpy_in_seq(): - import numpy - from numpy.testing.utils import assert_array_equal - for shape in SHAPES: - for dtype in DTYPES: - A = new_array(shape, dtype=dtype) - bufs = serialize_object((A,1,2,b'hello')) - canned = pickle.loads(bufs[0]) - assert isinstance(canned[0], CannedArray) - tup, r = deserialize_object(bufs) - B = tup[0] - assert r == [] - assert A.shape == B.shape - assert A.dtype == B.dtype - assert_array_equal(A,B) - -@dec.skip_without('numpy') -def test_numpy_in_dict(): - import numpy - from numpy.testing.utils import assert_array_equal - for shape in SHAPES: - for dtype in DTYPES: - A = new_array(shape, dtype=dtype) - bufs = serialize_object(dict(a=A,b=1,c=range(20))) - canned = pickle.loads(bufs[0]) - assert isinstance(canned['a'], CannedArray) - d, r = deserialize_object(bufs) - B = d['a'] - assert r == [] - assert A.shape == B.shape - assert A.dtype == B.dtype - assert_array_equal(A,B) - -def test_class(): - @interactive - class C(object): - a=5 - bufs = serialize_object(dict(C=C)) - canned = pickle.loads(bufs[0]) - assert isinstance(canned['C'], CannedClass) - d, r = deserialize_object(bufs) - C2 = d['C'] - assert C2.a == C.a - -def test_class_oldstyle(): - @interactive - class C: - a=5 - - bufs = serialize_object(dict(C=C)) - canned = pickle.loads(bufs[0]) - assert isinstance(canned['C'], CannedClass) - d, r = deserialize_object(bufs) - C2 = d['C'] - assert C2.a == C.a - -def test_tuple(): - tup = (lambda x:x, 1) - bufs = serialize_object(tup) - canned = pickle.loads(bufs[0]) - assert isinstance(canned, tuple) - t2, r = deserialize_object(bufs) - assert t2[0](t2[1]) == tup[0](tup[1]) - -point = namedtuple('point', 'x y') - -def test_namedtuple(): - p = point(1,2) - bufs = serialize_object(p) - canned = pickle.loads(bufs[0]) - assert isinstance(canned, point) - p2, r = deserialize_object(bufs, globals()) - assert p2.x == p.x - assert p2.y == p.y - -def test_list(): - lis = [lambda x:x, 1] - bufs = serialize_object(lis) - canned = pickle.loads(bufs[0]) - assert isinstance(canned, list) - l2, r = deserialize_object(bufs) - assert l2[0](l2[1]) == lis[0](lis[1]) - -def test_class_inheritance(): - @interactive - class C(object): - a=5 - - @interactive - class D(C): - b=10 - - bufs = serialize_object(dict(D=D)) - canned = pickle.loads(bufs[0]) - assert isinstance(canned['D'], CannedClass) - d, r = deserialize_object(bufs) - D2 = d['D'] - assert D2.a == D.a - assert D2.b == D.b diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 34308ae5b..c7dd5dea2 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -45,13 +45,6 @@ from jupyter_core.paths import jupyter_runtime_dir from jupyter_client.session import extract_header, Session -try: - # available since ipyparallel 5.0.0 - from ipyparallel.engine.datapub import ZMQDataPublisher -except ImportError: - # Deprecated since ipykernel 4.3.0 - from ipykernel.datapub import ZMQDataPublisher - #----------------------------------------------------------------------------- # Functions and classes #----------------------------------------------------------------------------- @@ -445,7 +438,7 @@ class ZMQInteractiveShell(InteractiveShell): displayhook_class = Type(ZMQShellDisplayHook) display_pub_class = Type(ZMQDisplayPublisher) - data_pub_class = Type(ZMQDataPublisher) + data_pub_class = Any() kernel = Any() parent_header = Any() From 13d92202ef1b2466538bf9044c1559d81fc866be Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 24 Jun 2021 10:35:09 -0500 Subject: [PATCH 0562/1195] Release 6.0.0rc2 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 16defdfbc..9260f3dc9 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "rc1") +version_info = (6, 0, 0, "rc2") __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From b6e4ff86beb6d2ce46cd156a1e68e6d1c5132bbc Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 29 Jun 2021 14:51:01 -0500 Subject: [PATCH 0563/1195] Update Changelog for 6.0 Release --- CHANGELOG.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 89 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6926b4b1a..430b3552a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## 6.0.0 +([Full Changelog](https://github.com/ipython/ipykernel/compare/aba2179420a3fa81ee6b8a13f928bf9e5ce50716...6d04ad2bdccd0dc0daf20f8d53555174b5fefc7b)) + IPykernel 6.0 is the first major release in about two years, that brings a number of improvements, code cleanup, and new features to IPython. @@ -40,22 +42,100 @@ following non-exhaustive changes. should make some output nicer (for example colored), it is likely to break others. Use with care. -## Deprecations in 6.0 - - - `Kernel`s now support only a single shell stream, multiple streams will now be ignored. The attribute +### New features added + +- Implementation of the debugger [#597](https://github.com/ipython/ipykernel/pull/597) ([@JohanMabille](https://github.com/JohanMabille)) + +### Enhancements made + +- Make the `isatty` method of `OutStream` return `true` [#683](https://github.com/ipython/ipykernel/pull/683) ([@peendebak](https://github.com/peendebak)) +- Allow setting cell name [#652](https://github.com/ipython/ipykernel/pull/652) ([@davidbrochart](https://github.com/davidbrochart)) +- Try to capture all file descriptor output and err [#630](https://github.com/ipython/ipykernel/pull/630) ([@Carreau](https://github.com/Carreau)) +- Implemented `inspectVariables` request [#624](https://github.com/ipython/ipykernel/pull/624) ([@JohanMabille](https://github.com/JohanMabille)) +- Specify `ipykernel` in kernelspec [#616](https://github.com/ipython/ipykernel/pull/616) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Use `matplotlib-inline` [#591](https://github.com/ipython/ipykernel/pull/591) ([@martinRenou](https://github.com/martinRenou)) +- Run control channel in separate thread [#585](https://github.com/ipython/ipykernel/pull/585) ([@SylvainCorlay](https://github.com/SylvainCorlay)) + +### Bugs fixed + +- Remove references to deprecated `ipyparallel` [#695](https://github.com/ipython/ipykernel/pull/695) ([@minrk](https://github.com/minrk)) +- Return len of item written to `OutStream` [#685](https://github.com/ipython/ipykernel/pull/685) ([@Carreau](https://github.com/Carreau)) +- Call metadata methods on abort replies [#684](https://github.com/ipython/ipykernel/pull/684) ([@minrk](https://github.com/minrk)) +- Fix keyboard interrupt issue in `dispatch_shell` [#673](https://github.com/ipython/ipykernel/pull/673) ([@marcoamonteiro](https://github.com/marcoamonteiro)) +- Update `Trio` mode for compatibility with `Trio >= 0.18.0` [#627](https://github.com/ipython/ipykernel/pull/627) ([@mehaase](https://github.com/mehaase)) +- Follow up `DeprecationWarnin`g Fix [#617](https://github.com/ipython/ipykernel/pull/617) ([@afshin](https://github.com/afshin)) +- Flush control stream upon shutdown [#611](https://github.com/ipython/ipykernel/pull/611) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Fix Handling of `shell.should_run_async` [#605](https://github.com/ipython/ipykernel/pull/605) ([@afshin](https://github.com/afshin)) +- Deacrease lag time for eventloop [#573](https://github.com/ipython/ipykernel/pull/573) ([@impact27](https://github.com/impact27)) +- Fix "Socket operation on nonsocket" in downstream `nbclient` test. [#641](https://github.com/ipython/ipykernel/pull/641) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Stop control thread before closing sockets on it [#659](https://github.com/ipython/ipykernel/pull/659) ([@minrk](https://github.com/minrk)) +- Fix debugging with native coroutines [#651](https://github.com/ipython/ipykernel/pull/651) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Fixup master build [#649](https://github.com/ipython/ipykernel/pull/649) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Fix parent header retrieval [#639](https://github.com/ipython/ipykernel/pull/639) ([@davidbrochart](https://github.com/davidbrochart)) +- Add missing self [#636](https://github.com/ipython/ipykernel/pull/636) ([@Carreau](https://github.com/Carreau)) +- Backwards compat with older versions of zmq [#665](https://github.com/ipython/ipykernel/pull/665) ([@mlucool](https://github.com/mlucool)) + +### Maintenance and upkeep improvements + +- Remove pin on Jedi because that was already fixed in IPython [#692](https://github.com/ipython/ipykernel/pull/692) ([@ccordoba12](https://github.com/ccordoba12)) +- Remove deprecated source parameter since 4.0.1 (2015) [#690](https://github.com/ipython/ipykernel/pull/690) ([@Carreau](https://github.com/Carreau)) +- Remove deprecated `SocketABC` since 4.5.0 [#689](https://github.com/ipython/ipykernel/pull/689) ([@Carreau](https://github.com/Carreau)) +- Remove deprecated profile options of `connect.py` [#688](https://github.com/ipython/ipykernel/pull/688) ([@Carreau](https://github.com/Carreau)) +- Remove `ipykernel.codeutil` deprecated since IPykernel 4.3.1 (Feb 2016) [#687](https://github.com/ipython/ipykernel/pull/687) ([@Carreau](https://github.com/Carreau)) +- Keep preferring `SelectorEventLoop` on Windows [#669](https://github.com/ipython/ipykernel/pull/669) ([@minrk](https://github.com/minrk)) +- Add `Kernel.get_parent` to match `set_parent` [#661](https://github.com/ipython/ipykernel/pull/661) ([@minrk](https://github.com/minrk)) +- Flush control queue prior to handling shell messages [#658](https://github.com/ipython/ipykernel/pull/658) ([@minrk](https://github.com/minrk)) +- Add `Kernel.get_parent_header` [#657](https://github.com/ipython/ipykernel/pull/657) ([@minrk](https://github.com/minrk)) +- Build docs only on Ubuntu: add jobs to check docstring formatting. [#644](https://github.com/ipython/ipykernel/pull/644) ([@Carreau](https://github.com/Carreau)) +- Make deprecated `shell_streams` writable [#638](https://github.com/ipython/ipykernel/pull/638) ([@minrk](https://github.com/minrk)) +- Use channel `get_msg` helper method [#634](https://github.com/ipython/ipykernel/pull/634) ([@davidbrochart](https://github.com/davidbrochart)) +- Use native coroutines instead of tornado coroutines [#632](https://github.com/ipython/ipykernel/pull/632) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Make less use of `ipython_genutils` [#631](https://github.com/ipython/ipykernel/pull/631) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Run GitHub Actions on all branches [#625](https://github.com/ipython/ipykernel/pull/625) ([@afshin](https://github.com/afshin)) +- Move Python-specific bits to ipkernel [#610](https://github.com/ipython/ipykernel/pull/610) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Update Python Requirement to 3.7 [#608](https://github.com/ipython/ipykernel/pull/608) ([@afshin](https://github.com/afshin)) +- Replace import item from `ipython_genutils` to traitlets. [#601](https://github.com/ipython/ipykernel/pull/601) ([@Carreau](https://github.com/Carreau)) +- Some removal of `ipython_genutils.py3compat`. [#600](https://github.com/ipython/ipykernel/pull/600) ([@Carreau](https://github.com/Carreau)) +- Fixup `get_parent_header` call [#662](https://github.com/ipython/ipykernel/pull/662) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Update of `ZMQInteractiveshell`. [#643](https://github.com/ipython/ipykernel/pull/643) ([@Carreau](https://github.com/Carreau)) +- Removed filtering of stack frames for testing [#633](https://github.com/ipython/ipykernel/pull/633) ([@JohanMabille](https://github.com/JohanMabille)) +- Added 'type' field to variables returned by `inspectVariables` request [#628](https://github.com/ipython/ipykernel/pull/628) ([@JohanMabille](https://github.com/JohanMabille)) +- Changed default timeout to 0.0 seconds for `stop_on_error_timeout` [#618](https://github.com/ipython/ipykernel/pull/618) ([@MSeal](https://github.com/MSeal)) +- Attempt longer timeout [#615](https://github.com/ipython/ipykernel/pull/615) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Clean up release process and add tests [#596](https://github.com/ipython/ipykernel/pull/596) ([@afshin](https://github.com/afshin)) +- Kernelspec: ensure path is writable before writing `kernel.json`. [#593](https://github.com/ipython/ipykernel/pull/593) ([@jellelicht](https://github.com/jellelicht)) +- Add `configure_inline_support` and call it in the shell [#590](https://github.com/ipython/ipykernel/pull/590) ([@martinRenou](https://github.com/martinRenou)) + +### Documentation improvements + +- Misc Updates to changelog for 6.0 [#686](https://github.com/ipython/ipykernel/pull/686) ([@Carreau](https://github.com/Carreau)) +- Add 5.5.x Changelog entries [#672](https://github.com/ipython/ipykernel/pull/672) ([@blink1073](https://github.com/blink1073)) +- Build docs only on ubuntu: add jobs to check docstring formatting. [#644](https://github.com/ipython/ipykernel/pull/644) ([@Carreau](https://github.com/Carreau)) +- DOC: Autoreformat all docstrings. [#642](https://github.com/ipython/ipykernel/pull/642) ([@Carreau](https://github.com/Carreau)) +- Bump Python to 3.8 in `readthedocs.yml` [#612](https://github.com/ipython/ipykernel/pull/612) ([@minrk](https://github.com/minrk)) +- Fix typo [#663](https://github.com/ipython/ipykernel/pull/663) ([@SylvainCorlay](https://github.com/SylvainCorlay)) +- Add release note to 5.5.0 about `stop_on_error_timeout` [#613](https://github.com/ipython/ipykernel/pull/613) ([@glentakahashi](https://github.com/glentakahashi)) +- Move changelog to standard location [#604](https://github.com/ipython/ipykernel/pull/604) ([@afshin](https://github.com/afshin)) +- Add changelog for 5.5 [#594](https://github.com/ipython/ipykernel/pull/594) ([@blink1073](https://github.com/blink1073)) +- Change to markdown for changelog [#595](https://github.com/ipython/ipykernel/pull/595) ([@afshin](https://github.com/afshin)) + +### Deprecations in 6.0 + +- `Kernel`s now support only a single shell stream, multiple streams will now be ignored. The attribute `Kernel.shell_streams` (plural) is deprecated in ipykernel 6.0. Use `Kernel.shell_stream` (singular) +- `Kernel._parent_header` is deprecated, even though it was private. Use `.get_parent()` now. - - `Kernel._parent_header` is deprecated, even though it was private. Use `.get_parent()` now. - -## Removal in 6.0 +### Removal in 6.0 - - `ipykernel.codeutils` was deprecated since 4.x series (2016) and has been removed, please import similar +- `ipykernel.codeutils` was deprecated since 4.x series (2016) and has been removed, please import similar functionalities from `ipyparallel` +- remove `find_connection_file` and `profile` argument of `connect_qtconsole` and `get_connection_info`, deprecated since IPykernel 4.2.2 (2016). - - remove `find_connection_file` and `profile` argument of `connect_qtconsole` and `get_connection_info`, deprecated since IPykernel 4.2.2 (2016). +### Contributors to this release +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2021-01-11&to=2021-06-29&type=c)) -* Set `stop_on_error_timeout` default to 0.0 matching pre 5.5.0 default behavior with correctly working flag from 5.5.0. +[@afshin](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aafshin+updated%3A2021-01-11..2021-06-29&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2021-01-11..2021-06-29&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2021-01-11..2021-06-29&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2021-01-11..2021-06-29&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2021-01-11..2021-06-29&type=Issues) | [@dsblank](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adsblank+updated%3A2021-01-11..2021-06-29&type=Issues) | [@glentakahashi](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aglentakahashi+updated%3A2021-01-11..2021-06-29&type=Issues) | [@impact27](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aimpact27+updated%3A2021-01-11..2021-06-29&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2021-01-11..2021-06-29&type=Issues) | [@jellelicht](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajellelicht+updated%3A2021-01-11..2021-06-29&type=Issues) | [@jkablan](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajkablan+updated%3A2021-01-11..2021-06-29&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2021-01-11..2021-06-29&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akevin-bates+updated%3A2021-01-11..2021-06-29&type=Issues) | [@marcoamonteiro](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amarcoamonteiro+updated%3A2021-01-11..2021-06-29&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AmartinRenou+updated%3A2021-01-11..2021-06-29&type=Issues) | [@mehaase](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amehaase+updated%3A2021-01-11..2021-06-29&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2021-01-11..2021-06-29&type=Issues) | [@mlucool](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amlucool+updated%3A2021-01-11..2021-06-29&type=Issues) | [@MSeal](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AMSeal+updated%3A2021-01-11..2021-06-29&type=Issues) | [@peendebak](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apeendebak+updated%3A2021-01-11..2021-06-29&type=Issues) | [@SylvainCorlay](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2021-01-11..2021-06-29&type=Issues) | [@tacaswell](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Atacaswell+updated%3A2021-01-11..2021-06-29&type=Issues) ## 5.5 From 789d44153e56be1f15f3a748672cbc41f83a554d Mon Sep 17 00:00:00 2001 From: Afshin Taylor Darian Date: Tue, 29 Jun 2021 21:08:54 +0100 Subject: [PATCH 0564/1195] Fix typo in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 430b3552a..8472e1c4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,7 +63,7 @@ following non-exhaustive changes. - Call metadata methods on abort replies [#684](https://github.com/ipython/ipykernel/pull/684) ([@minrk](https://github.com/minrk)) - Fix keyboard interrupt issue in `dispatch_shell` [#673](https://github.com/ipython/ipykernel/pull/673) ([@marcoamonteiro](https://github.com/marcoamonteiro)) - Update `Trio` mode for compatibility with `Trio >= 0.18.0` [#627](https://github.com/ipython/ipykernel/pull/627) ([@mehaase](https://github.com/mehaase)) -- Follow up `DeprecationWarnin`g Fix [#617](https://github.com/ipython/ipykernel/pull/617) ([@afshin](https://github.com/afshin)) +- Follow up `DeprecationWarning` Fix [#617](https://github.com/ipython/ipykernel/pull/617) ([@afshin](https://github.com/afshin)) - Flush control stream upon shutdown [#611](https://github.com/ipython/ipykernel/pull/611) ([@SylvainCorlay](https://github.com/SylvainCorlay)) - Fix Handling of `shell.should_run_async` [#605](https://github.com/ipython/ipykernel/pull/605) ([@afshin](https://github.com/afshin)) - Deacrease lag time for eventloop [#573](https://github.com/ipython/ipykernel/pull/573) ([@impact27](https://github.com/impact27)) From b4352b89efb0f9d938af736103b2c456ab0c6e3f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Jun 2021 05:31:51 -0500 Subject: [PATCH 0565/1195] Release 6.0.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 9260f3dc9..e673d0cd8 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0, "rc2") +version_info = (6, 0, 0) __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 4c45db4247b77bbe96fba86e04324f3548160061 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 30 Jun 2021 22:29:33 +0200 Subject: [PATCH 0566/1195] stringify variables that are not json serializable in inspectVariables reply --- ipykernel/debugger.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index f3ea2a917..f0b9dbf62 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -13,6 +13,8 @@ from IPython.core.getipython import get_ipython import debugpy +from .jsonutil import json_clean + # Required for backwards compatiblity ROUTING_ID = getattr(zmq, 'ROUTING_ID', None) or zmq.IDENTITY @@ -417,9 +419,14 @@ async def inspectVariables(self, message): var_list = [] for k, v in get_ipython().user_ns.items(): if self.accept_variable(k): + try: + val = json_clean(v) + + except ValueError: + val = str(v) var_list.append({ 'name': k, - 'value': v, + 'value': val, 'type': str(type(v))[8:-2], 'variablesReference': 0 }) From dfd7f6a0efcdfb8fad52444017993b342a003dfe Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Jun 2021 17:31:03 -0500 Subject: [PATCH 0567/1195] Update changelog for 6.0.1 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8472e1c4c..421234c67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ # Changes in IPython kernel +## 6.0 + +## 6.0.1 + +- Stringify variables that are not json serializable in inspectVariable [#702](https://github.com/ipython/ipykernel/pull/702) ([@JohanMabille](https://github.com/JohanMabille)) + ## 6.0.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/aba2179420a3fa81ee6b8a13f928bf9e5ce50716...6d04ad2bdccd0dc0daf20f8d53555174b5fefc7b)) From 866e240cea250f163b59c8ce8a72cd24a1da9580 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Wed, 30 Jun 2021 22:55:25 -0500 Subject: [PATCH 0568/1195] Fix Tk and asyncio event loops --- ipykernel/eventloops.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index a71121bdd..5b40a9d73 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -232,10 +232,10 @@ def process_stream_events(stream, *a, **kw): # For Tkinter, we create a Tk object and call its withdraw method. kernel.app_wrapper = BasicAppWrapper(app) - notifier = partial(process_stream_events, shell_stream) + notifier = partial(process_stream_events, kernel.shell_stream) # seems to be needed for tk notifier.__name__ = "notifier" - app.tk.createfilehandler(shell_stream.getsockopt(zmq.FD), READABLE, notifier) + app.tk.createfilehandler(kernel.shell_stream.getsockopt(zmq.FD), READABLE, notifier) # schedule initial call after start app.after(0, notifier) @@ -364,8 +364,8 @@ def process_stream_events(stream): if stream.flush(limit=1): loop.stop() - notifier = partial(process_stream_events, shell_stream) - loop.add_reader(shell_stream.getsockopt(zmq.FD), notifier) + notifier = partial(process_stream_events, kernel.shell_stream) + loop.add_reader(kernel.shell_stream.getsockopt(zmq.FD), notifier) loop.call_soon(notifier) while True: From c75338538bca4d7f95c0432a35ab5680683f8535 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 1 Jul 2021 10:39:14 -0500 Subject: [PATCH 0569/1195] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 421234c67..b0884fd73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ## 6.0.1 +- Fix Tk and asyncio event loops [#704](https://github.com/ipython/ipykernel/pull/704) ([@ccordoba12](https://github.com/ccordoba12)) - Stringify variables that are not json serializable in inspectVariable [#702](https://github.com/ipython/ipykernel/pull/702) ([@JohanMabille](https://github.com/JohanMabille)) ## 6.0.0 From eadd81df4c81c0ed8a0730bf0b66b593c565f7c4 Mon Sep 17 00:00:00 2001 From: Jonathan Striebel Date: Fri, 2 Jul 2021 11:28:11 +0200 Subject: [PATCH 0570/1195] minor fix in setup.py (comma before appnope) --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b0080adb2..94594fe70 100644 --- a/setup.py +++ b/setup.py @@ -80,7 +80,7 @@ def run(self): 'traitlets>=4.1.0', 'jupyter_client', 'tornado>=4.2', - 'matplotlib-inline>=0.1.0,<0.2.0' + 'matplotlib-inline>=0.1.0,<0.2.0', 'appnope;platform_system=="Darwin"', ], extras_require={ From 7cbe7241c365c50b00c7a25d953d6c131e115f03 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 2 Jul 2021 06:26:49 -0500 Subject: [PATCH 0571/1195] Release 6.0.1 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index e673d0cd8..f5743bbf6 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 0) +version_info = (6, 0, 1) __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 451514cdfd18ca0e29a00f56f4e3e1cae23e1f2f Mon Sep 17 00:00:00 2001 From: martinRenou Date: Fri, 2 Jul 2021 17:35:14 +0200 Subject: [PATCH 0572/1195] Remove CachingCompiler's filename_mapper This seems to be some leftover from old debugger implementation --- ipykernel/compiler.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 7af9f6b44..a50dc5735 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -2,6 +2,7 @@ import tempfile import os + def murmur2_x86(data, seed): m = 0x5bd1e995 length = len(data) @@ -34,15 +35,18 @@ def murmur2_x86(data, seed): return h + def get_tmp_directory(): tmp_dir = tempfile.gettempdir() pid = os.getpid() return tmp_dir + '/ipykernel_' + str(pid) + def get_tmp_hash_seed(): hash_seed = 0xc70f6907 return hash_seed + def get_file_name(code): cell_name = os.environ.get("IPYKERNEL_CELL_NAME") if cell_name is None: @@ -50,18 +54,12 @@ def get_file_name(code): cell_name = get_tmp_directory() + '/' + str(name) + '.py' return cell_name + class XCachingCompiler(CachingCompiler): def __init__(self, *args, **kwargs): super(XCachingCompiler, self).__init__(*args, **kwargs) - self.filename_mapper = None self.log = None def get_code_name(self, raw_code, code, number): - filename = get_file_name(raw_code) - - if self.filename_mapper is not None: - self.filename_mapper(filename, number) - - return filename - + return get_file_name(raw_code) From dbfe994f6e9e0f6c8e60188e37642423bdb037be Mon Sep 17 00:00:00 2001 From: Scott Lasley Date: Fri, 2 Jul 2021 14:53:55 -0400 Subject: [PATCH 0573/1195] Fix typo in eventloops.py Typo in eventloops.py : Changing kernel_shell_stream to kernel.shell_stream fixes this NameError with the MacOSX backend Using matplotlib backend: MacOSX ERROR:tornado.application:Exception in callback functools.partial(.advance_eventloop at 0x108aefe50>) Traceback (most recent call last): File "/Users/selasley/venvs/py39/lib/python3.9/site-packages/tornado/ioloop.py", line 741, in _run_callback ret = callback() File "/Users/selasley/venvs/py39/lib/python3.9/site-packages/ipykernel/kernelbase.py", line 401, in advance_eventloop eventloop(self) File "/Users/selasley/venvs/py39/lib/python3.9/site-packages/ipykernel/eventloops.py", line 327, in loop_cocoa if kernel_shell_stream.flush(limit=1): NameError: name 'kernel_shell_stream' is not defined --- ipykernel/eventloops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 5b40a9d73..4e601d6aa 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -324,7 +324,7 @@ def handle_int(etype, value, tb): # don't let interrupts during mainloop invoke crash_handler: sys.excepthook = handle_int mainloop(kernel._poll_interval) - if kernel_shell_stream.flush(limit=1): + if kernel.shell_stream.flush(limit=1): # events to process, return control to kernel return except: From 852c7cf14c5eef2ba19577f0de758488743ecffb Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 9 Jul 2021 16:23:40 +0200 Subject: [PATCH 0574/1195] Minimal flake8 config This disable most common issues that are not critical like whitespace, at least for now, so that we can fix all the issues and put flake8 in CI with an exist status. Then we can decide to renable (or not) those checks. --- setup.cfg | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index 05f7972bf..1920192c8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,4 +8,34 @@ license_file = COPYING.md warningfilters= default |.* |DeprecationWarning |ipykernel.* error |.*invalid.* |DeprecationWarning |matplotlib.* - +[flake8] +# References: +# https://flake8.readthedocs.io/en/latest/user/configuration.html +# https://flake8.readthedocs.io/en/latest/user/error-codes.html +# https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes +exclude = __init__.py,versioneer.py +ignore = + E20, # Extra space in brackets + E122, # continuation line missing indentation or outdented + E124, # closing bracket does not match visual indentation + E128,E127,E126 # continuation line over/under-indented for visual indent + E121,E125, # continuation line with same indent as next logical line + E226, # missing whitespace around arithmetic operator + E231,E241, # Multiple spaces around "," + E211, # whitespace before '(' + E221,E225,E228 # missing whitespace around operator + E271, # multiple spaces after keyword + E301,E303,E305,E306 # expected X blank lines + E26, # Comments + E251 # unexpected spaces around keyword / parameter equals + E302 # expected 2 blank lines, found 1 + E4, # Import formatting + E721, # Comparing types instead of isinstance + E731, # Assigning lambda expression + E741, # Ambiguous variable names + W293, # blank line contains whitespace + W503, # line break before binary operator + W504, # line break after binary operator + F811, # redefinition of unused 'loop' from line 10 +max-line-length = 120 + From acb487c14d48a1247891fa836a2ff69a4b63cdee Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 9 Jul 2021 16:30:22 +0200 Subject: [PATCH 0575/1195] Clean most flake8 unused import warnings. Part of the #717 PR-group. --- docs/conf.py | 2 -- ipykernel/comm/manager.py | 3 +-- ipykernel/connect.py | 3 --- ipykernel/debugger.py | 2 -- ipykernel/inprocess/channels.py | 2 -- ipykernel/inprocess/socket.py | 2 -- ipykernel/inprocess/tests/test_kernelmanager.py | 1 - ipykernel/jsonutil.py | 1 - ipykernel/kernelapp.py | 1 - ipykernel/kernelbase.py | 2 +- ipykernel/log.py | 2 -- ipykernel/pickleutil.py | 2 +- ipykernel/pylab/backend_inline.py | 2 +- ipykernel/pylab/config.py | 2 +- ipykernel/tests/test_pickleutil.py | 2 +- ipykernel/tests/test_zmq_shell.py | 1 - ipykernel/tests/utils.py | 3 +-- 17 files changed, 7 insertions(+), 26 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 8b9f63994..086fd7e83 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,9 +12,7 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os -import shlex import shutil # If extensions (or modules to document with autodoc) are in another directory, diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index bf63838e3..9b5907770 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -3,13 +3,12 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import sys import logging from traitlets.config import LoggingConfigurable from traitlets.utils.importstring import import_item -from traitlets import Instance, Unicode, Dict, Any, default +from traitlets import Instance, Dict from .comm import Comm diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 5f3560b39..275139cae 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -6,10 +6,7 @@ import json import sys from subprocess import Popen, PIPE -import warnings -from IPython.core.profiledir import ProfileDir -from IPython.paths import get_ipython_dir from ipython_genutils.path import filefind import jupyter_client diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index f0b9dbf62..3e56c11a6 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,4 +1,3 @@ -import logging import os import re @@ -11,7 +10,6 @@ from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed) from IPython.core.getipython import get_ipython -import debugpy from .jsonutil import json_clean diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 0b78d99b2..00fc1b7e0 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -5,8 +5,6 @@ from jupyter_client.channelsabc import HBChannelABC -from .socket import DummySocket - #----------------------------------------------------------------------------- # Channel classes #----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index ad901d81c..ddaad3a38 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -3,9 +3,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import abc from queue import Queue -import warnings import zmq diff --git a/ipykernel/inprocess/tests/test_kernelmanager.py b/ipykernel/inprocess/tests/test_kernelmanager.py index e498798a9..bd1bb81e2 100644 --- a/ipykernel/inprocess/tests/test_kernelmanager.py +++ b/ipykernel/inprocess/tests/test_kernelmanager.py @@ -3,7 +3,6 @@ import unittest -from ipykernel.inprocess.blocking import BlockingInProcessKernelClient from ipykernel.inprocess.manager import InProcessKernelManager #----------------------------------------------------------------------------- diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 1218c8eae..514955715 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -10,7 +10,6 @@ from datetime import datetime import numbers -from ipython_genutils.encoding import DEFAULT_ENCODING next_attr_name = '__next__' #----------------------------------------------------------------------------- diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 480d98af3..4ff6046ce 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -13,7 +13,6 @@ from io import TextIOWrapper, FileIO from logging import StreamHandler -import tornado from tornado import ioloop import zmq diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 32c4ea9c9..cf6c7a662 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -24,7 +24,7 @@ now = datetime.now from tornado import ioloop -from tornado.queues import Queue, QueueEmpty +from tornado.queues import Queue import zmq from zmq.eventloop.zmqstream import ZMQStream diff --git a/ipykernel/log.py b/ipykernel/log.py index 97789c378..d0b03cc32 100644 --- a/ipykernel/log.py +++ b/ipykernel/log.py @@ -1,5 +1,3 @@ -from logging import INFO, DEBUG, WARN, ERROR, FATAL - from zmq.log.handlers import PUBHandler import warnings diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index a2f3da11b..2343a6258 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -18,7 +18,7 @@ from ipython_genutils.py3compat import buffer_to_bytes # This registers a hook when it's imported -from ipyparallel.serialize import codeutil +from ipyparallel.serialize import codeutil # noqa F401 from traitlets.log import get_logger diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index 3b53c7629..c1935b4b0 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.backend_inline import * # analysis: ignore +from matplotlib_inline.backend_inline import * # analysis: ignore # noqa F401 warnings.warn( diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index 7846cb89e..bc9006f5f 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.config import * # analysis: ignore +from matplotlib_inline.config import * # analysis: ignore # noqa F401 warnings.warn( diff --git a/ipykernel/tests/test_pickleutil.py b/ipykernel/tests/test_pickleutil.py index 856f6c883..598198e9f 100644 --- a/ipykernel/tests/test_pickleutil.py +++ b/ipykernel/tests/test_pickleutil.py @@ -1,7 +1,7 @@ import pickle -from ipykernel.pickleutil import can, uncan, codeutil +from ipykernel.pickleutil import can, uncan def interactive(f): f.__module__ = '__main__' diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index 4cf2ecc67..aa5e0a2c8 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -3,7 +3,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import os from queue import Queue from threading import Thread import unittest diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 386a7a40e..fdeee9192 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -4,13 +4,12 @@ # Distributed under the terms of the Modified BSD License. import atexit -import os import sys from time import time from contextlib import contextmanager from queue import Empty -from subprocess import PIPE, STDOUT +from subprocess import STDOUT import nose From 3918062bb735085c9912bc0fec0de59d8acfc7e8 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 9 Jul 2021 16:47:05 +0200 Subject: [PATCH 0576/1195] Formatting: remove semicolon Some people write too much C or Typescript :-) Part of the #717 PR-group. --- ipykernel/debugger.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index f0b9dbf62..9c744eefa 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -294,9 +294,9 @@ async def dumpCell(self, message): return reply async def setBreakpoints(self, message): - source = message['arguments']['source']['path']; - self.breakpoint_list[source] = message['arguments']['breakpoints'] - return await self._forward_message(message); + source = message["arguments"]["source"]["path"] + self.breakpoint_list[source] = message["arguments"]["breakpoints"] + return await self._forward_message(message) async def source(self, message): reply = { @@ -304,7 +304,7 @@ async def source(self, message): 'request_seq': message['seq'], 'command': message['command'] } - source_path = message['arguments']['source']['path']; + source_path = message["arguments"]["source"]["path"] if os.path.isfile(source_path): with open(source_path) as f: reply['success'] = True @@ -389,7 +389,7 @@ async def configurationDone(self, message): 'success': True, 'command': message['command'] } - return reply; + return reply async def debugInfo(self, message): breakpoint_list = [] From 63f826c7b655b23381b0bd6eb73bcd0678727fe7 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 9 Jul 2021 16:52:40 +0200 Subject: [PATCH 0577/1195] misc whitespace and line too long Just to please flake8 Part of the #717 PR-group. --- examples/embedding/ipkernel_qtapp.py | 2 +- examples/embedding/ipkernel_wxapp.py | 2 +- ipykernel/_eventloop_macos.py | 1 - ipykernel/debugger.py | 13 +++++++++---- ipykernel/eventloops.py | 2 +- ipykernel/ipkernel.py | 20 +++++++++++++++----- ipykernel/tests/test_kernelspec.py | 1 - ipykernel/zmqshell.py | 2 +- 8 files changed, 28 insertions(+), 15 deletions(-) diff --git a/examples/embedding/ipkernel_qtapp.py b/examples/embedding/ipkernel_qtapp.py index 93ab0c0dd..601c74884 100755 --- a/examples/embedding/ipkernel_qtapp.py +++ b/examples/embedding/ipkernel_qtapp.py @@ -65,7 +65,7 @@ def add_widgets(self): #----------------------------------------------------------------------------- if __name__ == "__main__": - app = Qt.QApplication([]) + app = Qt.QApplication([]) # Create our window win = SimpleWindow(app) win.show() diff --git a/examples/embedding/ipkernel_wxapp.py b/examples/embedding/ipkernel_wxapp.py index a85de7b3a..ffb82c864 100755 --- a/examples/embedding/ipkernel_wxapp.py +++ b/examples/embedding/ipkernel_wxapp.py @@ -42,7 +42,7 @@ def __init__(self, parent, title): # Create the menubar menuBar = wx.MenuBar() - # and a menu + # and a menu menu = wx.Menu() # add an item to the menu, using \tKeyName automatically diff --git a/ipykernel/_eventloop_macos.py b/ipykernel/_eventloop_macos.py index fbae6ac0e..8c7cbdd91 100644 --- a/ipykernel/_eventloop_macos.py +++ b/ipykernel/_eventloop_macos.py @@ -150,4 +150,3 @@ def mainloop(duration=1): # Run the loop manually in this case, # since there may be events still to process (ipython/ipython#9734) CoreFoundation.CFRunLoopRun() - diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index f0b9dbf62..84a3e4126 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -330,9 +330,15 @@ async def stackTrace(self, message): # or only the frames from the notebook. # We want to remove all the frames from ipykernel when they are present. try: - sf_list = reply['body']['stackFrames'] - module_idx = len(sf_list) - next(i for i, v in enumerate(reversed(sf_list), 1) if v['name'] == '' and i != 1) - reply['body']['stackFrames'] = reply['body']['stackFrames'][:module_idx+1] + sf_list = reply["body"]["stackFrames"] + module_idx = len(sf_list) - next( + i + for i, v in enumerate(reversed(sf_list), 1) + if v["name"] == "" and i != 1 + ) + reply["body"]["stackFrames"] = reply["body"]["stackFrames"][ + : module_idx + 1 + ] except StopIteration: pass return reply @@ -478,4 +484,3 @@ async def process_request(self, message): self.log.info('The debugger has stopped') return reply - diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 4e601d6aa..5543d3c7e 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -151,7 +151,7 @@ def loop_wx(kernel): import wx - # Wx uses milliseconds + # Wx uses milliseconds poll_interval = int(1000 * kernel._poll_interval) def wake(): diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 77e38a707..ac304a851 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -26,7 +26,10 @@ _asyncio_runner = None try: - from IPython.core.completer import rectify_completions as _rectify_completions, provisionalcompleter as _provisionalcompleter + from IPython.core.completer import ( + rectify_completions as _rectify_completions, + provisionalcompleter as _provisionalcompleter, + ) _use_experimental_60_completion = True except ImportError: _use_experimental_60_completion = False @@ -195,9 +198,12 @@ def finish_metadata(self, parent, metadata, reply_content): """ # FIXME: remove deprecated ipyparallel-specific code # This is required by ipyparallel < 5.0 - metadata['status'] = reply_content['status'] - if reply_content['status'] == 'error' and reply_content['ename'] == 'UnmetDependency': - metadata['dependencies_met'] = False + metadata["status"] = reply_content["status"] + if ( + reply_content["status"] == "error" + and reply_content["ename"] == "UnmetDependency" + ): + metadata["dependencies_met"] = False return metadata @@ -309,7 +315,11 @@ async def run_cell(*args, **kwargs): _asyncio_runner and shell.loop_runner is _asyncio_runner and asyncio.get_event_loop().is_running() - and should_run_async(code, transformed_cell=transformed_cell, preprocessing_exc_tuple=preprocessing_exc_tuple) + and should_run_async( + code, + transformed_cell=transformed_cell, + preprocessing_exc_tuple=preprocessing_exc_tuple, + ) ): coro = run_cell( code, diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index a0a9b5c47..818483682 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -165,4 +165,3 @@ def test_install_env(tmp_path, env): assert spec['env'][k] == v else: assert 'env' not in spec - diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index c7dd5dea2..c60fe6014 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -524,7 +524,7 @@ def ask_exit(self): payload = dict( source='ask_exit', keepkernel=self.keepkernel_on_exit, - ) + ) self.payload_manager.write_payload(payload) def run_cell(self, *args, **kwargs): From c7b593f03317bb3e420b03dfbe28891aac5add56 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 9 Jul 2021 16:43:15 +0200 Subject: [PATCH 0578/1195] Do not use bare except Part of the #717 PR-group. Bare excepts replaced by except Exception/BaseException --- ipykernel/comm/comm.py | 2 +- ipykernel/comm/manager.py | 2 +- ipykernel/debugger.py | 2 +- ipykernel/eventloops.py | 2 +- ipykernel/kernelapp.py | 2 +- ipykernel/kernelbase.py | 8 ++++---- ipykernel/parentpoller.py | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index cb3fbe9fb..3f7192752 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -94,7 +94,7 @@ def open(self, data=None, metadata=None, buffers=None): target_module=self.target_module, ) self._closed = False - except: + except Exception: comm_manager.unregister_comm(self) raise diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index bf63838e3..5854b5f33 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -94,7 +94,7 @@ def comm_open(self, stream, ident, msg): # Failure. try: comm.close() - except: + except Exception: self.log.error("""Could not close comm during `comm_open` failure clean-up. The comm may not have been opened yet.""", exc_info=True) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index f0b9dbf62..f20eaa483 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -241,7 +241,7 @@ def _handle_event(self, msg): elif msg['event'] == 'continued': try: self.stopped_threads.remove(msg['body']['threadId']) - except: + except Exception: pass self.event_callback(msg) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 4e601d6aa..ca5357fae 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -327,7 +327,7 @@ def handle_int(etype, value, tb): if kernel.shell_stream.flush(limit=1): # events to process, return control to kernel return - except: + except BaseException: raise except KeyboardInterrupt: # Ctrl-C shouldn't crash the kernel diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 480d98af3..ce40910c0 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -630,7 +630,7 @@ def initialize(self, argv=None): self.init_io() try: self.init_signal() - except: + except Exception: # Catch exception when initializing signal fails, eg when running the # kernel on a separate thread if self.log_level < logging.CRITICAL: diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 32c4ea9c9..509edb331 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -257,7 +257,7 @@ async def process_control(self, msg): idents, msg = self.session.feed_identities(msg, copy=False) try: msg = self.session.deserialize(msg, content=True, copy=False) - except: + except Exception: self.log.error("Invalid Control Message", exc_info=True) return @@ -309,7 +309,7 @@ async def dispatch_shell(self, msg): idents, msg = self.session.feed_identities(msg, copy=False) try: msg = self.session.deserialize(msg, content=True, copy=False) - except: + except Exception: self.log.error("Invalid Message", exc_info=True) return @@ -624,7 +624,7 @@ async def execute_request(self, stream, ident, parent): store_history = content.get('store_history', not silent) user_expressions = content.get('user_expressions', {}) allow_stdin = content.get('allow_stdin', False) - except: + except Exception: self.log.error("Got bad msg: ") self.log.error("%s", parent) return @@ -847,7 +847,7 @@ async def apply_request(self, stream, ident, parent): content = parent['content'] bufs = parent['buffers'] msg_id = parent['header']['msg_id'] - except: + except Exception: self.log.error("Got bad msg: %s", parent, exc_info=True) return diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 238a397c2..ed389217f 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -3,7 +3,7 @@ try: import ctypes -except: +except ImportError: ctypes = None import os import platform From 4af29c195db518e623c1d4589597c0ad8c364767 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 9 Jul 2021 16:38:03 +0200 Subject: [PATCH 0579/1195] Remove unused variables Unused variables are a code smell. Removing them make it clearer we expect the functions called to be called for their side-effects. Part of the #717 PR-group. --- ipykernel/kernelbase.py | 2 +- ipykernel/tests/test_embed_kernel.py | 14 +++++++------- ipykernel/tests/test_kernel.py | 2 +- ipykernel/tests/test_kernelspec.py | 7 +++---- ipykernel/tests/test_message_spec.py | 27 ++++++++++++++------------- ipykernel/tests/test_start_kernel.py | 18 +++++++++--------- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 32c4ea9c9..81ad7cf5a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1024,7 +1024,7 @@ def _input_request(self, prompt, ident, parent, password=False): except KeyboardInterrupt: # re-raise KeyboardInterrupt, to truncate traceback raise KeyboardInterrupt("Interrupted by user") from None - except Exception as e: + except Exception: self.log.warning("Invalid Message:", exc_info=True) try: diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index b339621f7..0b3b7e562 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -94,18 +94,18 @@ def test_embed_kernel_basic(): with setup_kernel(cmd) as client: # oinfo a (int) - msg_id = client.inspect('a') + client.inspect("a") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] - msg_id = client.execute("c=a*2") + client.execute("c=a*2") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['status'] == 'ok' # oinfo c (should be 10) - msg_id = client.inspect('c') + client.inspect("c") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] @@ -128,7 +128,7 @@ def test_embed_kernel_namespace(): with setup_kernel(cmd) as client: # oinfo a (int) - msg_id = client.inspect('a') + client.inspect("a") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] @@ -136,7 +136,7 @@ def test_embed_kernel_namespace(): assert '5' in text # oinfo b (str) - msg_id = client.inspect('b') + client.inspect("b") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] @@ -144,7 +144,7 @@ def test_embed_kernel_namespace(): assert 'hi there' in text # oinfo c (undefined) - msg_id = client.inspect('c') + client.inspect("c") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert not content['found'] @@ -167,7 +167,7 @@ def test_embed_kernel_reentrant(): with setup_kernel(cmd) as client: for i in range(5): - msg_id = client.inspect('count') + client.inspect("count") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 13afdcb9f..f3e61fe78 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -447,7 +447,7 @@ def test_control_thread_priority(): control_msg_ids.append(msg["header"]["msg_id"]) # finally, collect the replies on both channels for comparison - sleep_reply = get_reply(kc, sleep_msg_id) + get_reply(kc, sleep_msg_id) shell_replies = [] for msg_id in shell_msg_ids: shell_replies.append(get_reply(kc, msg_id)) diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index a0a9b5c47..14af908b3 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -89,10 +89,9 @@ def test_write_kernel_spec_path(): def test_install_kernelspec(): path = tempfile.mkdtemp() - try: - test = InstallIPythonKernelSpecApp.launch_instance(argv=['--prefix', path]) - assert_is_spec(os.path.join( - path, 'share', 'jupyter', 'kernels', KERNEL_NAME)) + try: + InstallIPythonKernelSpecApp.launch_instance(argv=["--prefix", path]) + assert_is_spec(os.path.join(path, "share", "jupyter", "kernels", KERNEL_NAME)) finally: shutil.rmtree(path) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index e700553ff..a0cac9932 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -322,14 +322,15 @@ def test_execute_inc(): """execute request should increment execution_count""" flush_channels() - msg_id, reply = execute(code='x=1') - count = reply['execution_count'] + _, reply = execute(code="x=1") + count = reply["execution_count"] flush_channels() - msg_id, reply = execute(code='x=2') - count_2 = reply['execution_count'] - assert count_2 == count+1 + _, reply = execute(code="x=2") + count_2 = reply["execution_count"] + assert count_2 == count + 1 + def test_execute_stop_on_error(): """execute request should not abort execution queue with stop_on_error False""" @@ -342,7 +343,7 @@ def test_execute_stop_on_error(): 'raise ValueError', ]) KC.execute(code=fail) - msg_id = KC.execute(code='print("Hello")') + KC.execute(code='print("Hello")') KC.get_shell_msg(timeout=TIMEOUT) reply = KC.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'aborted' @@ -350,7 +351,7 @@ def test_execute_stop_on_error(): flush_channels() KC.execute(code=fail, stop_on_error=False) - msg_id = KC.execute(code='print("Hello")') + KC.execute(code='print("Hello")') KC.get_shell_msg(timeout=TIMEOUT) reply = KC.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'ok' @@ -519,8 +520,8 @@ def test_is_complete(): def test_history_range(): flush_channels() - msg_id_exec = KC.execute(code='x=1', store_history = True) - reply_exec = KC.get_shell_msg(timeout=TIMEOUT) + KC.execute(code="x=1", store_history=True) + KC.get_shell_msg(timeout=TIMEOUT) msg_id = KC.history(hist_access_type = 'range', raw = True, output = True, start = 1, stop = 2, session = 0) reply = get_reply(KC, msg_id, TIMEOUT) @@ -531,8 +532,8 @@ def test_history_range(): def test_history_tail(): flush_channels() - msg_id_exec = KC.execute(code='x=1', store_history = True) - reply_exec = KC.get_shell_msg(timeout=TIMEOUT) + KC.execute(code="x=1", store_history=True) + KC.get_shell_msg(timeout=TIMEOUT) msg_id = KC.history(hist_access_type = 'tail', raw = True, output = True, n = 1, session = 0) reply = get_reply(KC, msg_id, TIMEOUT) @@ -543,8 +544,8 @@ def test_history_tail(): def test_history_search(): flush_channels() - msg_id_exec = KC.execute(code='x=1', store_history = True) - reply_exec = KC.get_shell_msg(timeout=TIMEOUT) + KC.execute(code="x=1", store_history=True) + KC.get_shell_msg(timeout=TIMEOUT) msg_id = KC.history(hist_access_type = 'search', raw = True, output = True, n = 1, pattern = '*', session = 0) reply = get_reply(KC, msg_id, TIMEOUT) diff --git a/ipykernel/tests/test_start_kernel.py b/ipykernel/tests/test_start_kernel.py index 8251b2c40..02ffb73bb 100644 --- a/ipykernel/tests/test_start_kernel.py +++ b/ipykernel/tests/test_start_kernel.py @@ -11,7 +11,7 @@ def test_ipython_start_kernel_userns(): 'start_kernel(user_ns=ns)') with setup_kernel(cmd) as client: - msg_id = client.inspect('tre') + client.inspect("tre") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] @@ -19,11 +19,11 @@ def test_ipython_start_kernel_userns(): assert '123' in text # user_module should be an instance of DummyMod - msg_id = client.execute("usermod = get_ipython().user_module") + client.execute("usermod = get_ipython().user_module") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) - content = msg['content'] - assert content['status'] == 'ok' - msg_id = client.inspect('usermod') + content = msg["content"] + assert content["status"] == "ok" + client.inspect("usermod") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] @@ -39,11 +39,11 @@ def test_ipython_start_kernel_no_userns(): with setup_kernel(cmd) as client: # user_module should not be an instance of DummyMod - msg_id = client.execute("usermod = get_ipython().user_module") + client.execute("usermod = get_ipython().user_module") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) - content = msg['content'] - assert content['status'] == 'ok' - msg_id = client.inspect('usermod') + content = msg["content"] + assert content["status"] == "ok" + client.inspect("usermod") msg = client.get_shell_msg(block=True, timeout=TIMEOUT) content = msg['content'] assert content['found'] From 16397ee27a3d5817850eb43ede8179f3ca8166c8 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 12 Jul 2021 17:13:05 +0200 Subject: [PATCH 0580/1195] Replace non-existing function. This was forgotten as part of an earlier pass, when safe_unicode (py2 compat) was removed. safe_unicode is undefiled now and would lead to crash. Part of the #717 PR-group. --- ipykernel/ipkernel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 77e38a707..6163fdfca 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -534,9 +534,9 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): # invoke IPython traceback formatting shell.showtraceback() reply_content = { - 'traceback': shell._last_traceback or [], - 'ename': str(type(e).__name__), - 'evalue': safe_unicode(e), + "traceback": shell._last_traceback or [], + "ename": str(type(e).__name__), + "evalue": str(e), } # FIXME: deprecated piece for ipyparallel (remove in 5.0): e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply') From a6a98b4b06ddad3afedc182b945a96e445c9f470 Mon Sep 17 00:00:00 2001 From: Ray Osborn Date: Tue, 13 Jul 2021 08:50:33 -0500 Subject: [PATCH 0581/1195] Add watchfd keyword to InProcessKernel OutStream initialization Setting `watchfd`to False when creating OutStream instances for `stdout` prevents an exception caused by the lack of a file descriptor in the stream used by the InProcessKernel . --- ipykernel/inprocess/ipkernel.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index bfa715acd..0e86c8f6d 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -152,11 +152,13 @@ def _default_shell_class(self): @default('stdout') def _default_stdout(self): - return OutStream(self.session, self.iopub_thread, 'stdout') + return OutStream(self.session, self.iopub_thread, 'stdout', + watchfd=False) @default('stderr') def _default_stderr(self): - return OutStream(self.session, self.iopub_thread, 'stderr') + return OutStream(self.session, self.iopub_thread, 'stderr', + watchfd=False) #----------------------------------------------------------------------------- # Interactive shell subclass From 5c9c739127b7205a3df6c702a0873c401406c301 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 13 Jul 2021 11:23:29 -0500 Subject: [PATCH 0582/1195] Add changelog for 6.0.2 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0884fd73..e8025e1c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,25 @@ ## 6.0 +## 6.0.2 + +### Bugs fixed + +- Add watchfd keyword to InProcessKernel OutStream initialization [#727](https://github.com/ipython/ipykernel/pull/727) ([@rayosborn](https://github.com/rayosborn)) +- Fix typo in eventloops.py [#711](https://github.com/ipython/ipykernel/pull/711) ([@selasley](https://github.com/selasley)) +- [bugfix] fix in setup.py (comma before appnope) [#709](https://github.com/ipython/ipykernel/pull/709) ([@jstriebel](https://github.com/jstriebel)) + +### Maintenance and upkeep improvements + +- Replace non-existing function. [#723](https://github.com/ipython/ipykernel/pull/723) ([@Carreau](https://github.com/Carreau)) +- Remove unused variables [#722](https://github.com/ipython/ipykernel/pull/722) ([@Carreau](https://github.com/Carreau)) +- Do not use bare except [#721](https://github.com/ipython/ipykernel/pull/721) ([@Carreau](https://github.com/Carreau)) +- misc whitespace and line too long [#720](https://github.com/ipython/ipykernel/pull/720) ([@Carreau](https://github.com/Carreau)) +- Formatting: remove semicolon [#719](https://github.com/ipython/ipykernel/pull/719) ([@Carreau](https://github.com/Carreau)) +- Clean most flake8 unused import warnings. [#718](https://github.com/ipython/ipykernel/pull/718) ([@Carreau](https://github.com/Carreau)) +- Minimal flake8 config [#717](https://github.com/ipython/ipykernel/pull/717) ([@Carreau](https://github.com/Carreau)) +- Remove CachingCompiler's filename_mapper [#710](https://github.com/ipython/ipykernel/pull/710) ([@martinRenou](https://github.com/martinRenou)) + ## 6.0.1 - Fix Tk and asyncio event loops [#704](https://github.com/ipython/ipykernel/pull/704) ([@ccordoba12](https://github.com/ccordoba12)) From cc6c1715117b05af16e51d62ec0b9afc68309099 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Thu, 15 Jul 2021 09:29:17 +0200 Subject: [PATCH 0583/1195] Add upper bound to dependency versions (#714) * Add upper bound to dependency versions This might save us from some troubles if a major release of one of those dependency arrives (thinking of jupyter-client 7.0 coming) * Update setup.py Co-authored-by: David Brochart Co-authored-by: David Brochart --- setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 94594fe70..7ea543f87 100644 --- a/setup.py +++ b/setup.py @@ -75,11 +75,11 @@ def run(self): python_requires='>=3.7', install_requires=[ 'importlib-metadata<4;python_version<"3.8.0"', - 'debugpy>=1.0.0', - 'ipython>=7.23.1', - 'traitlets>=4.1.0', - 'jupyter_client', - 'tornado>=4.2', + 'debugpy>=1.0.0,<2.0', + 'ipython>=7.23.1,<8.0', + 'traitlets>=4.1.0,<6.0', + 'jupyter_client<7.0', + 'tornado>=4.2,<7.0', 'matplotlib-inline>=0.1.0,<0.2.0', 'appnope;platform_system=="Darwin"', ], From 6dbf2f8f5cc4362c5b43eb28ccc77e619d9a2e2a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 15 Jul 2021 05:47:28 -0500 Subject: [PATCH 0584/1195] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e8025e1c9..f8c339d61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Maintenance and upkeep improvements +- Add upper bound to dependency versions. [#714](https://github.com/ipython/ipykernel/pull/714) ([@martinRenou](https://github.com/martinRenou)) - Replace non-existing function. [#723](https://github.com/ipython/ipykernel/pull/723) ([@Carreau](https://github.com/Carreau)) - Remove unused variables [#722](https://github.com/ipython/ipykernel/pull/722) ([@Carreau](https://github.com/Carreau)) - Do not use bare except [#721](https://github.com/ipython/ipykernel/pull/721) ([@Carreau](https://github.com/Carreau)) From 69a0b793adad52329e63658ca7f40ee67de4d3d7 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 15 Jul 2021 06:01:30 -0500 Subject: [PATCH 0585/1195] Release 6.0.2 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index f5743bbf6..77424b8dd 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 1) +version_info = (6, 0, 2) __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 3eae7d4537ee91093eab367088730a25a36b8961 Mon Sep 17 00:00:00 2001 From: Adrian Moreno Date: Thu, 15 Jul 2021 13:38:38 +0200 Subject: [PATCH 0586/1195] kernelapp: rename ports variable to avoid override "ports" is a property defined in ConnectionFileMixin and used internally by it To avoid attribute name colission, rename "ports" to "_ports" Signed-off-by: Adrian Moreno --- ipykernel/kernelapp.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 7414a0709..a85dd6c75 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -129,7 +129,7 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, iopub_thread = Any() control_thread = Any() - ports = Dict() + _ports = Dict() subcommands = { 'install': ( @@ -390,7 +390,7 @@ def log_connection_info(self): for line in lines: print(line, file=sys.__stdout__) - self.ports = dict(shell=self.shell_port, iopub=self.iopub_port, + self._ports = dict(shell=self.shell_port, iopub=self.iopub_port, stdin=self.stdin_port, hb=self.hb_port, control=self.control_port) @@ -500,7 +500,7 @@ def init_kernel(self): user_ns=self.user_ns, ) kernel.record_ports({ - name + '_port': port for name, port in self.ports.items() + name + '_port': port for name, port in self._ports.items() }) self.kernel = kernel From 038afb02ec84761dc99ed5e50f2b52449c3c3a3a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 19 Jul 2021 10:19:19 -0500 Subject: [PATCH 0587/1195] Add changelog for 6.0.3 --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f8c339d61..96064c550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ ## 6.0 +## 6.0.3 + +- `KernelApp`: rename ports variable to avoid override [#731](https://github.com/ipython/ipykernel/pull/731) ([@amorenoz](https://github.com/amorenoz)) + ## 6.0.2 ### Bugs fixed From 4be87ded4e62ab208eb1d7c831edef98af59baa6 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 19 Jul 2021 12:13:36 -0500 Subject: [PATCH 0588/1195] Release 6.0.3 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 77424b8dd..2ccf792a3 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 2) +version_info = (6, 0, 3) __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 81ff0b6b999c0b3d4b44ba06a42e90202de37b58 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Thu, 22 Jul 2021 22:31:36 +0200 Subject: [PATCH 0589/1195] Implemented richInspectVariable request handler --- ipykernel/debugger.py | 56 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index d842d889d..ee53c8b54 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -205,7 +205,7 @@ class Debugger: # Requests that can be handled even if the debugger is not running static_debug_msg_types = [ - 'debugInfo', 'inspectVariables' + 'debugInfo', 'inspectVariables', 'richInspectVariables' ] def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): @@ -445,6 +445,60 @@ async def inspectVariables(self, message): } return reply + async def richInspectVariables(self, message): + var_name = message['arguments']['variableName'] + var_repr_data = var_name + '_repr_data' + var_repr_metadata = var_name + '_repr_metadata' + + if not self.breakpoint_list: + # The code did not hit a breakpoint, we use the intepreter + # to get the rich representation of the variable + var_repr_data, var_repr_metadata = get_ipython().display_formatter.format(var_name) + else: + # The code has stopped on a breakpoint, we use the setExpression + # request to get the rich representation of the variable + lvalue = var_repr_data + ',' + var_repr_metadata + code = 'get_ipython().display_formatter.format(' + var_name+')' + frame_id = message['arguments']['frameId'] + seq = message['seq'] + request = { + 'type': 'request', + 'command': 'setExpression', + 'seq': seq+1, + 'arguments': { + 'expression': lvalue, + 'value': code, + 'frameId': frameId + } + } + await self._forward_message(request) + + reply = { + 'type': 'response', + 'sequence_seq': message['seq'], + 'success': False, + 'command': message['command'] + } + + repr_data = globals()[var_repr_data] + repr_metadata = globals()[var_repr_metadata] + body = { + 'data': {}, + 'metadata': {} + } + + for key, value in repr_data.items(): + body['data']['key'] = value + if repr_metadata.has_key(key): + body['metadata'][key] = repr_metadata[key] + + globals().pop(var_repr_data) + globals().pop(var_repr_metadata) + + reply['body'] = body + reply['success'] = True + return reply + async def process_request(self, message): reply = {} From 3f4736e0fca79bc63955e5c2726892d0d94a4522 Mon Sep 17 00:00:00 2001 From: Simon Krughoff Date: Mon, 12 Jul 2021 16:51:02 -0700 Subject: [PATCH 0590/1195] Test that OutStream.write gives correct exception When sent anything but `str` data, the write method should canonically give a `TypeError`. It currently raises `ValueError`. --- ipykernel/tests/test_io.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index cf68b88ea..9fe3f4e2e 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -38,6 +38,8 @@ def test_io_api(): stream.seek(0) with nt.assert_raises(io.UnsupportedOperation): stream.tell() + with nt.assert_raises(TypeError): + stream.write(b'') def test_io_isatty(): session = Session() From 947be929657e0a6859e98fd26a3312a9eb8fd58d Mon Sep 17 00:00:00 2001 From: Simon Krughoff Date: Mon, 12 Jul 2021 16:54:52 -0700 Subject: [PATCH 0591/1195] Change the `OutStream.write` method exception type This makes sure that the `write` method raises a `TypeError` when sent non-string data. This agrees with the behavior of `sys.stdout.write`. --- ipykernel/iostream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index f2f2d7783..98058d40e 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -498,7 +498,7 @@ def write(self, string: str) -> int: """ if not isinstance(string, str): - raise ValueError( + raise TypeError( "TypeError: write() argument must be str, not {type(string)}" ) From f49f6a234fe83a05fa9ca95c599daa6b20b7ec59 Mon Sep 17 00:00:00 2001 From: Simon Krughoff Date: Mon, 12 Jul 2021 16:56:37 -0700 Subject: [PATCH 0592/1195] Make exception message supply input type info This commit formats the message so that the type information is reported in the exception. It also removes the duplicate 'TypeError:' from the beginning. --- ipykernel/iostream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 98058d40e..56e6d8a15 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -499,7 +499,7 @@ def write(self, string: str) -> int: if not isinstance(string, str): raise TypeError( - "TypeError: write() argument must be str, not {type(string)}" + f"write() argument must be str, not {type(string)}" ) if self.echo is not None: From 971723fc4f2bfc61626a37f5d20c684c478941bf Mon Sep 17 00:00:00 2001 From: David Brochart Date: Mon, 9 Aug 2021 15:43:16 +0200 Subject: [PATCH 0593/1195] Remove block param from get_msg() --- ipykernel/inprocess/blocking.py | 11 ++++++----- ipykernel/inprocess/client.py | 16 ++++++++-------- ipykernel/tests/test_embed_kernel.py | 16 ++++++++-------- ipykernel/tests/test_kernel.py | 20 ++++++++++---------- ipykernel/tests/test_start_kernel.py | 10 +++++----- ipykernel/tests/utils.py | 6 +++--- 6 files changed, 40 insertions(+), 39 deletions(-) diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index cc995a51b..52599d84e 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -29,12 +29,13 @@ def __init__(self, *args, **kwds): def call_handlers(self, msg): self._in_queue.put(msg) - def get_msg(self, block=True, timeout=None): + def get_msg(self, timeout=None): """ Gets a message if there is one that is ready. """ if timeout is None: # Queue.get(timeout=None) has stupid uninteruptible # behavior, so wait for a week instead timeout = 604800 + block = timeout > 0 return self._in_queue.get(block, timeout) def get_msgs(self): @@ -42,7 +43,7 @@ def get_msgs(self): msgs = [] while True: try: - msgs.append(self.get_msg(block=False)) + msgs.append(self.get_msg(timeout=0)) except Empty: break return msgs @@ -78,14 +79,14 @@ def wait_for_ready(self): while True: self.kernel_info() try: - msg = self.shell_channel.get_msg(block=True, timeout=1) + msg = self.shell_channel.get_msg(timeout=1) except Empty: pass else: if msg['msg_type'] == 'kernel_info_reply': # Checking that IOPub is connected. If it is not connected, start over. try: - self.iopub_channel.get_msg(block=True, timeout=0.2) + self.iopub_channel.get_msg(timeout=0.2) except Empty: pass else: @@ -95,7 +96,7 @@ def wait_for_ready(self): # Flush IOPub channel while True: try: - msg = self.iopub_channel.get_msg(block=True, timeout=0.2) + msg = self.iopub_channel.get_msg(timeout=0.2) print(msg['msg_type']) except Empty: break diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 1784a6eaa..605d3447f 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -180,17 +180,17 @@ def _dispatch_to_kernel(self, msg): idents, reply_msg = self.session.recv(stream, copy=False) self.shell_channel.call_handlers_later(reply_msg) - def get_shell_msg(self, block=True, timeout=None): - return self.shell_channel.get_msg(block, timeout) + def get_shell_msg(self, timeout=None): + return self.shell_channel.get_msg(timeout) - def get_iopub_msg(self, block=True, timeout=None): - return self.iopub_channel.get_msg(block, timeout) + def get_iopub_msg(self, timeout=None): + return self.iopub_channel.get_msg(timeout) - def get_stdin_msg(self, block=True, timeout=None): - return self.stdin_channel.get_msg(block, timeout) + def get_stdin_msg(self, timeout=None): + return self.stdin_channel.get_msg(timeout) - def get_control_msg(self, block=True, timeout=None): - return self.control_channel.get_msg(block, timeout) + def get_control_msg(self, timeout=None): + return self.control_channel.get_msg(timeout) #----------------------------------------------------------------------------- diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 0b3b7e562..d8df921aa 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -95,18 +95,18 @@ def test_embed_kernel_basic(): with setup_kernel(cmd) as client: # oinfo a (int) client.inspect("a") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content'] assert content['found'] client.execute("c=a*2") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content'] assert content['status'] == 'ok' # oinfo c (should be 10) client.inspect("c") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] @@ -129,7 +129,7 @@ def test_embed_kernel_namespace(): with setup_kernel(cmd) as client: # oinfo a (int) client.inspect("a") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] @@ -137,7 +137,7 @@ def test_embed_kernel_namespace(): # oinfo b (str) client.inspect("b") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] @@ -145,7 +145,7 @@ def test_embed_kernel_namespace(): # oinfo c (undefined) client.inspect("c") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content'] assert not content['found'] @@ -168,7 +168,7 @@ def test_embed_kernel_reentrant(): with setup_kernel(cmd) as client: for i in range(5): client.inspect("count") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] @@ -176,5 +176,5 @@ def test_embed_kernel_reentrant(): # exit from embed_kernel client.execute("get_ipython().exit_now = True") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) time.sleep(0.2) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index f3e61fe78..a2df85fc6 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -195,13 +195,13 @@ def test_raw_input(): theprompt = "prompt> " code = 'print({input_f}("{theprompt}"))'.format(**locals()) msg_id = kc.execute(code, allow_stdin=True) - msg = kc.get_stdin_msg(block=True, timeout=TIMEOUT) + msg = kc.get_stdin_msg(timeout=TIMEOUT) assert msg['header']['msg_type'] == 'input_request' content = msg['content'] assert content['prompt'] == theprompt text = "some text" kc.input(text) - reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'ok' stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == text + "\n" @@ -249,22 +249,22 @@ def test_is_complete(): # There are more test cases for this in core - here we just check # that the kernel exposes the interface correctly. kc.is_complete('2+2') - reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'complete' # SyntaxError kc.is_complete('raise = 2') - reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'invalid' kc.is_complete('a = [1,\n2,') - reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'incomplete' assert reply['content']['indent'] == '' # Cell magic ends on two blank lines for console UIs kc.is_complete('%%timeit\na\n\n') - reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'complete' @@ -275,7 +275,7 @@ def test_complete(): wait_for_idle(kc) cell = 'import IPython\nb = a.' kc.complete(cell) - reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + reply = kc.get_shell_msg(timeout=TIMEOUT) c = reply['content'] assert c['status'] == 'ok' @@ -341,20 +341,20 @@ def test_unc_paths(): unc_file_path = os.path.join(unc_root, file_path[1:]) kc.execute("cd {0:s}".format(unc_file_path)) - reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'ok' out, err = assemble_output(kc.get_iopub_msg) assert unc_file_path in out flush_channels(kc) kc.execute(code="ls") - reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'ok' out, err = assemble_output(kc.get_iopub_msg) assert 'unc.txt' in out kc.execute(code="cd") - reply = kc.get_shell_msg(block=True, timeout=TIMEOUT) + reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'ok' diff --git a/ipykernel/tests/test_start_kernel.py b/ipykernel/tests/test_start_kernel.py index 02ffb73bb..12d68bf3c 100644 --- a/ipykernel/tests/test_start_kernel.py +++ b/ipykernel/tests/test_start_kernel.py @@ -12,7 +12,7 @@ def test_ipython_start_kernel_userns(): with setup_kernel(cmd) as client: client.inspect("tre") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] @@ -20,11 +20,11 @@ def test_ipython_start_kernel_userns(): # user_module should be an instance of DummyMod client.execute("usermod = get_ipython().user_module") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg["content"] assert content["status"] == "ok" client.inspect("usermod") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] @@ -40,11 +40,11 @@ def test_ipython_start_kernel_no_userns(): with setup_kernel(cmd) as client: # user_module should not be an instance of DummyMod client.execute("usermod = get_ipython().user_module") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg["content"] assert content["status"] == "ok" client.inspect("usermod") - msg = client.get_shell_msg(block=True, timeout=TIMEOUT) + msg = client.get_shell_msg(timeout=TIMEOUT) content = msg['content'] assert content['found'] text = content['data']['text/plain'] diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index fdeee9192..166f52d45 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -45,7 +45,7 @@ def flush_channels(kc=None): for get_msg in (kc.get_shell_msg, kc.get_iopub_msg): while True: try: - msg = get_msg(block=True, timeout=0.1) + msg = get_msg(timeout=0.1) except Empty: break else: @@ -155,7 +155,7 @@ def assemble_output(get_msg): stdout = '' stderr = '' while True: - msg = get_msg(block=True, timeout=1) + msg = get_msg(timeout=1) msg_type = msg['msg_type'] content = msg['content'] if msg_type == 'status' and content['execution_state'] == 'idle': @@ -175,7 +175,7 @@ def assemble_output(get_msg): def wait_for_idle(kc): while True: - msg = kc.get_iopub_msg(block=True, timeout=1) + msg = kc.get_iopub_msg(timeout=1) msg_type = msg['msg_type'] content = msg['content'] if msg_type == 'status' and content['execution_state'] == 'idle': From d2c8c61db96790d64a9e49b805bce2c86c98b5b5 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Tue, 10 Aug 2021 11:10:46 +0200 Subject: [PATCH 0594/1195] Restore block parameter on in-process client --- ipykernel/inprocess/blocking.py | 11 +++++------ ipykernel/inprocess/client.py | 16 ++++++++-------- setup.py | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index 52599d84e..cc995a51b 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -29,13 +29,12 @@ def __init__(self, *args, **kwds): def call_handlers(self, msg): self._in_queue.put(msg) - def get_msg(self, timeout=None): + def get_msg(self, block=True, timeout=None): """ Gets a message if there is one that is ready. """ if timeout is None: # Queue.get(timeout=None) has stupid uninteruptible # behavior, so wait for a week instead timeout = 604800 - block = timeout > 0 return self._in_queue.get(block, timeout) def get_msgs(self): @@ -43,7 +42,7 @@ def get_msgs(self): msgs = [] while True: try: - msgs.append(self.get_msg(timeout=0)) + msgs.append(self.get_msg(block=False)) except Empty: break return msgs @@ -79,14 +78,14 @@ def wait_for_ready(self): while True: self.kernel_info() try: - msg = self.shell_channel.get_msg(timeout=1) + msg = self.shell_channel.get_msg(block=True, timeout=1) except Empty: pass else: if msg['msg_type'] == 'kernel_info_reply': # Checking that IOPub is connected. If it is not connected, start over. try: - self.iopub_channel.get_msg(timeout=0.2) + self.iopub_channel.get_msg(block=True, timeout=0.2) except Empty: pass else: @@ -96,7 +95,7 @@ def wait_for_ready(self): # Flush IOPub channel while True: try: - msg = self.iopub_channel.get_msg(timeout=0.2) + msg = self.iopub_channel.get_msg(block=True, timeout=0.2) print(msg['msg_type']) except Empty: break diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 605d3447f..1784a6eaa 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -180,17 +180,17 @@ def _dispatch_to_kernel(self, msg): idents, reply_msg = self.session.recv(stream, copy=False) self.shell_channel.call_handlers_later(reply_msg) - def get_shell_msg(self, timeout=None): - return self.shell_channel.get_msg(timeout) + def get_shell_msg(self, block=True, timeout=None): + return self.shell_channel.get_msg(block, timeout) - def get_iopub_msg(self, timeout=None): - return self.iopub_channel.get_msg(timeout) + def get_iopub_msg(self, block=True, timeout=None): + return self.iopub_channel.get_msg(block, timeout) - def get_stdin_msg(self, timeout=None): - return self.stdin_channel.get_msg(timeout) + def get_stdin_msg(self, block=True, timeout=None): + return self.stdin_channel.get_msg(block, timeout) - def get_control_msg(self, timeout=None): - return self.control_channel.get_msg(timeout) + def get_control_msg(self, block=True, timeout=None): + return self.control_channel.get_msg(block, timeout) #----------------------------------------------------------------------------- diff --git a/setup.py b/setup.py index 7ea543f87..0422b387e 100644 --- a/setup.py +++ b/setup.py @@ -78,7 +78,7 @@ def run(self): 'debugpy>=1.0.0,<2.0', 'ipython>=7.23.1,<8.0', 'traitlets>=4.1.0,<6.0', - 'jupyter_client<7.0', + 'jupyter_client<8.0', 'tornado>=4.2,<7.0', 'matplotlib-inline>=0.1.0,<0.2.0', 'appnope;platform_system=="Darwin"', From 3513a575ae6322e1d6644f2c53cb9766662f0b35 Mon Sep 17 00:00:00 2001 From: Leopold Talirz Date: Thu, 12 Aug 2021 10:28:43 +0200 Subject: [PATCH 0595/1195] bump importlib-metadata limit for python<3.8The importlib-metadata version was limited to <4 due to a corresponding restriction in the argcomplete package. The latest argcomplete patch release has bumped the limit to <5. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 7ea543f87..2ff5cfba2 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,7 @@ def run(self): keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], python_requires='>=3.7', install_requires=[ - 'importlib-metadata<4;python_version<"3.8.0"', + 'importlib-metadata<5;python_version<"3.8.0"', 'debugpy>=1.0.0,<2.0', 'ipython>=7.23.1,<8.0', 'traitlets>=4.1.0,<6.0', From 52f41d3b03b69ed94da9af5612696e92d61d3638 Mon Sep 17 00:00:00 2001 From: Leopold Talirz Date: Thu, 12 Aug 2021 13:22:22 +0200 Subject: [PATCH 0596/1195] require minimal argcomplete version --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 2ff5cfba2..931d76e8c 100644 --- a/setup.py +++ b/setup.py @@ -75,6 +75,7 @@ def run(self): python_requires='>=3.7', install_requires=[ 'importlib-metadata<5;python_version<"3.8.0"', + 'argcomplete>=1.12.3;python_version<"3.8.0"', 'debugpy>=1.0.0,<2.0', 'ipython>=7.23.1,<8.0', 'traitlets>=4.1.0,<6.0', From c57508ca9d47ca1ea7e9d7fb7b3111b4d507ba01 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 12 Aug 2021 06:31:28 -0500 Subject: [PATCH 0597/1195] Update changelog for 6.1 --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96064c550..24ce5cf6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,22 @@ # Changes in IPython kernel +## 6.1 + +## 6.1.0 + +### Enhancements made + +- Implemented `richInspectVariable` request handler [#734](https://github.com/ipython/ipykernel/pull/734) ([@JohanMabille](https://github.com/JohanMabille)) + +### Maintenance and upkeep improvements + +- Bump `importlib-metadata` limit for `python<3.8` [#738](https://github.com/ipython/ipykernel/pull/738) ([@ltalirz](https://github.com/ltalirz)) + +### Bug Fixes + +- Fix exception raised by `OutStream.write` [#726](https://github.com/ipython/ipykernel/pull/726) ([@SimonKrughoff](https://github.com/SimonKrughoff)) + ## 6.0 ## 6.0.3 From 8185a170ba217aa8a385d8782afc7df2d26f712b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 12 Aug 2021 06:47:20 -0500 Subject: [PATCH 0598/1195] Release 6.1.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 2ccf792a3..cc619e102 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 0, 3) +version_info = (6, 1, 0) __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 418c688ba16835a159e6fe622dd5b486d44df43e Mon Sep 17 00:00:00 2001 From: David Brochart Date: Tue, 13 Apr 2021 11:12:25 +0200 Subject: [PATCH 0599/1195] Test downstream projects --- .github/workflows/downstream.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/downstream.yml diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml new file mode 100644 index 000000000..332c448c1 --- /dev/null +++ b/.github/workflows/downstream.yml @@ -0,0 +1,32 @@ +name: Test downstream projects + +on: + push: + branches: "*" + pull_request: + branches: "*" + +jobs: + tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up Python 3.9 + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + pip install --upgrade pip + pip install . + pip install pytest nbclient[test] ipyparallel[test] + pip freeze + + - name: Run tests + run: | + pytest --pyargs nbclient + pytest --pyargs ipyparallel From 7b957b53d4c7f5c9dbcd903f107e659f1edd8ead Mon Sep 17 00:00:00 2001 From: David Brochart Date: Sat, 17 Apr 2021 09:34:00 +0200 Subject: [PATCH 0600/1195] Test ipyparallel --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 332c448c1..3633f693d 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -28,5 +28,5 @@ jobs: - name: Run tests run: | - pytest --pyargs nbclient + #pytest --pyargs nbclient pytest --pyargs ipyparallel From e399d99d63c831ae9b075249e923e2a51c1a2685 Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Thu, 6 May 2021 11:25:33 +0200 Subject: [PATCH 0601/1195] Install main branch of ipyparallel --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 3633f693d..278a7980a 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -23,7 +23,7 @@ jobs: run: | pip install --upgrade pip pip install . - pip install pytest nbclient[test] ipyparallel[test] + pip install pytest nbclient[test] "ipyparallel[test] @ git+git://github.com/ipython/ipyparallel.git@main" pip freeze - name: Run tests From 8e1b63cc269e448e42112a030fb2e82c6a8f9c30 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 7 May 2021 16:34:36 +0200 Subject: [PATCH 0602/1195] Test nbclient and jupyter_client --- .github/workflows/downstream.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 278a7980a..c5f41a9c0 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -23,10 +23,13 @@ jobs: run: | pip install --upgrade pip pip install . - pip install pytest nbclient[test] "ipyparallel[test] @ git+git://github.com/ipython/ipyparallel.git@main" + pip install pytest "nbclient[test] @ git+git://github.com/jupyter/nbclient.git@master" \ + "ipyparallel[test] @ git+git://github.com/ipython/ipyparallel.git@main" \ + "jupyter_client[test] @ git+git://github.com/jupyter/jupyter_client.git@master" pip freeze - name: Run tests run: | - #pytest --pyargs nbclient + pytest --pyargs nbclient pytest --pyargs ipyparallel + pytest --pyargs jupyter_client From 5fe99b45470e367ba09f3326650f0770425f75e0 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 7 May 2021 16:42:24 +0200 Subject: [PATCH 0603/1195] Set IPYKERNEL_CELL_NAME= --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index c5f41a9c0..1858d9043 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -30,6 +30,6 @@ jobs: - name: Run tests run: | - pytest --pyargs nbclient + IPYKERNEL_CELL_NAME="" pytest --pyargs nbclient pytest --pyargs ipyparallel pytest --pyargs jupyter_client From f1819c6824d133148ecd37454f77bef910e5b3ea Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 7 May 2021 16:49:43 +0200 Subject: [PATCH 0604/1195] Skip testing nbclient --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 1858d9043..b38eea8c9 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -30,6 +30,6 @@ jobs: - name: Run tests run: | - IPYKERNEL_CELL_NAME="" pytest --pyargs nbclient + #IPYKERNEL_CELL_NAME="" pytest --pyargs nbclient pytest --pyargs ipyparallel pytest --pyargs jupyter_client From 45cc61dc3fb562909a1fd340e696f5a0305ca66a Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 7 May 2021 17:05:27 +0200 Subject: [PATCH 0605/1195] Use release of jupyter_client, re-enable nbclient testing --- .github/workflows/downstream.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index b38eea8c9..49894a629 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -25,11 +25,11 @@ jobs: pip install . pip install pytest "nbclient[test] @ git+git://github.com/jupyter/nbclient.git@master" \ "ipyparallel[test] @ git+git://github.com/ipython/ipyparallel.git@main" \ - "jupyter_client[test] @ git+git://github.com/jupyter/jupyter_client.git@master" + jupyter_client[test] pip freeze - name: Run tests run: | - #IPYKERNEL_CELL_NAME="" pytest --pyargs nbclient + IPYKERNEL_CELL_NAME="" pytest --pyargs nbclient pytest --pyargs ipyparallel pytest --pyargs jupyter_client From e4b81dd2c3eb6163ebe6b36066f40924c58d894d Mon Sep 17 00:00:00 2001 From: Sylvain Corlay Date: Fri, 7 May 2021 23:49:54 +0200 Subject: [PATCH 0606/1195] Use master jupyter-client --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 49894a629..1858d9043 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -25,7 +25,7 @@ jobs: pip install . pip install pytest "nbclient[test] @ git+git://github.com/jupyter/nbclient.git@master" \ "ipyparallel[test] @ git+git://github.com/ipython/ipyparallel.git@main" \ - jupyter_client[test] + "jupyter_client[test] @ git+git://github.com/jupyter/jupyter_client.git@master" pip freeze - name: Run tests From a4a27b357b50e14714a16185f4c00539e9786fba Mon Sep 17 00:00:00 2001 From: David Brochart Date: Tue, 11 May 2021 20:38:11 +0200 Subject: [PATCH 0607/1195] Install jupyter_client release and pytest-asyncio --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 1858d9043..c444952a2 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -25,7 +25,7 @@ jobs: pip install . pip install pytest "nbclient[test] @ git+git://github.com/jupyter/nbclient.git@master" \ "ipyparallel[test] @ git+git://github.com/ipython/ipyparallel.git@main" \ - "jupyter_client[test] @ git+git://github.com/jupyter/jupyter_client.git@master" + jupyter_client[test] pytest-asyncio pip freeze - name: Run tests From 3f992f290b9f8295b811d93e015a0ff851090597 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Tue, 13 Jul 2021 11:26:25 +0200 Subject: [PATCH 0608/1195] Test released versions, not dev --- .github/workflows/downstream.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index c444952a2..dd81f1994 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -23,9 +23,7 @@ jobs: run: | pip install --upgrade pip pip install . - pip install pytest "nbclient[test] @ git+git://github.com/jupyter/nbclient.git@master" \ - "ipyparallel[test] @ git+git://github.com/ipython/ipyparallel.git@main" \ - jupyter_client[test] pytest-asyncio + pip install pytest nbclient[test] ipyparallel[test] jupyter_client[test] pytest-asyncio pip freeze - name: Run tests From 4cb6cd20750d980cda475689820968d810c56102 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Tue, 13 Jul 2021 13:45:13 +0200 Subject: [PATCH 0609/1195] Test jupyter_client dev version --- .github/workflows/downstream.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index dd81f1994..2267d392f 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -23,7 +23,10 @@ jobs: run: | pip install --upgrade pip pip install . - pip install pytest nbclient[test] ipyparallel[test] jupyter_client[test] pytest-asyncio + pip install pytest "nbclient[test] @ git+git://github.com/jupyter/nbclient.git@master" \ + "ipyparallel[test] @ git+git://github.com/ipython/ipyparallel.git@main" \ + "jupyter_client[test] @ git+git://github.com/jupyter/jupyter_client.git@master" \ + pytest-asyncio pip freeze - name: Run tests From a1aa247ac34e7af5ebaf6c24a55dd2655d9ab278 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 13 Aug 2021 16:08:28 +0200 Subject: [PATCH 0610/1195] Install downstream packages' pre-releases --- .github/workflows/downstream.yml | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 2267d392f..dba779eda 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -22,15 +22,20 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install . - pip install pytest "nbclient[test] @ git+git://github.com/jupyter/nbclient.git@master" \ - "ipyparallel[test] @ git+git://github.com/ipython/ipyparallel.git@main" \ - "jupyter_client[test] @ git+git://github.com/jupyter/jupyter_client.git@master" \ - pytest-asyncio + pip install pytest pytest-asyncio + pip install nbclient[test] --pre + pip install ipyparallel[test] --pre + pip install jupyter_client[test] --pre + pip install . --force-reinstall pip freeze + python -c 'import ipykernel; print("ipykernel", ipykernel.__version__)' - - name: Run tests - run: | - IPYKERNEL_CELL_NAME="" pytest --pyargs nbclient - pytest --pyargs ipyparallel - pytest --pyargs jupyter_client + - name: Test nbclient + if: ${{ always() }} + run: IPYKERNEL_CELL_NAME="" pytest --pyargs nbclient + - name: Test ipyparallel + if: ${{ always() }} + run: pytest --pyargs ipyparallel + - name: Test jupyter_client + if: ${{ always() }} + run: pytest --pyargs jupyter_client From 679c9a34d56ad352f5da2631ff6906be6465be53 Mon Sep 17 00:00:00 2001 From: "Afshin T. Darian" Date: Fri, 13 Aug 2021 15:26:26 +0100 Subject: [PATCH 0611/1195] Add Support for Message Based Interrupt --- ipykernel/kernelbase.py | 27 ++++++++++++++++++++++++++- ipykernel/tests/test_kernel.py | 20 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e391411e4..bfd107127 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -10,6 +10,7 @@ import itertools import logging import inspect +import os from signal import signal, default_int_handler, SIGINT import sys import time @@ -198,7 +199,7 @@ def _parent_header(self): 'inspect_request', 'history_request', 'comm_info_request', 'kernel_info_request', 'connect_request', 'shutdown_request', - 'is_complete_request', + 'is_complete_request', 'interrupt_request', # deprecated: 'apply_request', ] @@ -780,6 +781,30 @@ async def comm_info_request(self, stream, ident, parent): reply_content, parent, ident) self.log.debug("%s", msg) + async def interrupt_request(self, stream, ident, parent): + pid = os.getpid() + pgid = os.getpgid(pid) + + if os.name == "nt": + self.log.error("Interrupt message not supported on Windows") + + else: + # Prefer process-group over process + if pgid and hasattr(os, "killpg"): + try: + os.killpg(pgid, SIGINT) + return + except OSError: + pass + try: + os.kill(pid, SIGINT) + except OSError: + pass + + content = parent['content'] + self.session.send(stream, 'interrupt_reply', content, parent, ident=ident) + return + async def shutdown_request(self, stream, ident, parent): content = self.do_shutdown(parent['content']['restart']) if inspect.isawaitable(content): diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index a2df85fc6..6724d0f6d 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -391,6 +391,26 @@ def test_interrupt_during_input(): validate_message(reply, 'execute_reply', msg_id) +@pytest.mark.skipif( + os.name == "nt", + reason="Message based interrupt not supported on Windows" +) +def test_interrupt_with_message(): + """ + + """ + with new_kernel() as kc: + km = kc.parent + km.kernel_spec.interrupt_mode = "message" + msg_id = kc.execute("input()") + time.sleep(1) # Make sure it's actually waiting for input. + km.interrupt_kernel() + from .test_message_spec import validate_message + # If we failed to interrupt interrupt, this will timeout: + reply = get_reply(kc, msg_id, TIMEOUT) + validate_message(reply, 'execute_reply', msg_id) + + @pytest.mark.skipif( version.parse(IPython.__version__) < version.parse("7.14.0"), reason="Need new IPython" From fce50404727a7db6f43ecb0c6de3ffdcfbdcbdd1 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Sun, 15 Aug 2021 15:51:25 -0700 Subject: [PATCH 0612/1195] Remove some more dependency on nose/iptest --- ipykernel/tests/test_kernel.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 6724d0f6d..0c24db4f5 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -9,7 +9,6 @@ import sys import time -import nose.tools as nt from flaky import flaky import pytest from packaging import version @@ -108,9 +107,10 @@ def test_sys_path_profile_dir(): @flaky(max_runs=3) -@dec.skipif( - sys.platform == 'win32' or (sys.platform == "darwin" and sys.version_info >=(3, 8)), - "subprocess prints fail on Windows and MacOS Python 3.8+" +@pytest.mark.skipif( + sys.platform == "win32" + or (sys.platform == "darwin" and sys.version_info >= (3, 8)), + reason="subprocess prints fail on Windows and MacOS Python 3.8+", ) def test_subprocess_print(): """printing from forked mp.Process""" @@ -130,10 +130,10 @@ def test_subprocess_print(): msg_id, content = execute(kc=kc, code=code) stdout, stderr = assemble_output(kc.get_iopub_msg) - nt.assert_equal(stdout.count("hello"), np, stdout) + assert stdout.count("hello") == np, stdout for n in range(np): - nt.assert_equal(stdout.count(str(n)), 1, stdout) - assert stderr == '' + assert stdout.count(str(n)) == 1, stdout + assert stderr == "" _check_master(kc, expected=True) _check_master(kc, expected=True, stream="stderr") @@ -161,9 +161,10 @@ def test_subprocess_noprint(): @flaky(max_runs=3) -@dec.skipif( - sys.platform == 'win32' or (sys.platform == "darwin" and sys.version_info >=(3, 8)), - "subprocess prints fail on Windows and MacOS Python 3.8+" +@pytest.mark.skipif( + sys.platform == "win32" + or (sys.platform == "darwin" and sys.version_info >= (3, 8)), + reason="subprocess prints fail on Windows and MacOS Python 3.8+", ) def test_subprocess_error(): """error in mp.Process doesn't crash""" @@ -236,7 +237,7 @@ def test_smoke_faulthandler(): 'if not sys.platform.startswith("win32"):', ' faulthandler.register(signal.SIGTERM)']) _, reply = execute(code, kc=kc) - nt.assert_equal(reply['status'], 'ok', reply.get('traceback', '')) + assert reply["status"] == "ok", reply.get("traceback", "") def test_help_output(): @@ -268,7 +269,7 @@ def test_is_complete(): assert reply['content']['status'] == 'complete' -@dec.skipif(sys.platform != 'win32', "only run on Windows") +@pytest.mark.skipif(sys.platform != "win32", reason="only run on Windows") def test_complete(): with kernel() as kc: execute('a = 1', kc=kc) @@ -330,7 +331,10 @@ def test_message_order(): assert reply['parent_header']['msg_id'] == msg_id -@dec.skipif(sys.platform.startswith('linux') or sys.platform.startswith('darwin')) +@pytest.mark.skipif( + sys.platform.startswith("linux") or sys.platform.startswith("darwin"), + reason="test only on windows", +) def test_unc_paths(): with kernel() as kc, TemporaryDirectory() as td: drive_file_path = os.path.join(td, 'unc.txt') From 7f34c169b3a9bcd8dffbec919bc91d189355e13a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 16 Aug 2021 03:46:30 -0500 Subject: [PATCH 0613/1195] Update changelog for 6.2.0 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ce5cf6c..9c34b2389 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changes in IPython kernel +## 6.2 + +## 6.2.0 + +### Enhancements made + +- Add Support for Message Based Interrupt [#741](https://github.com/ipython/ipykernel/pull/741) ([@afshin](https://github.com/afshin)) + +### Maintenance and upkeep improvements + +- Remove some more dependency on nose/iptest [#743](https://github.com/ipython/ipykernel/pull/743) ([@Carreau](https://github.com/Carreau)) +- Remove block param from get_msg() [#736](https://github.com/ipython/ipykernel/pull/736) ([@davidbrochart](https://github.com/davidbrochart)) ## 6.1 From c36de65d2f275e0ba42f5dc2a7bb619bea4ab02d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 16 Aug 2021 04:11:30 -0500 Subject: [PATCH 0614/1195] Release 6.2.0 --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index cc619e102..fc734ce43 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,4 +1,4 @@ -version_info = (6, 1, 0) +version_info = (6, 2, 0) __version__ = ".".join(map(str, version_info[:3])) # pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools From 26a8e55de0ead963c67f5303992c7b864e372cd4 Mon Sep 17 00:00:00 2001 From: "Afshin T. Darian" Date: Mon, 16 Aug 2021 15:51:25 +0100 Subject: [PATCH 0615/1195] Update release helper plumbing --- .github/workflows/check-release.yml | 55 +++++++++++++++++++++++++++++ CHANGELOG.md | 4 +++ MANIFEST.in | 6 ++-- RELEASE.md | 20 +++++++---- ipykernel/_version.py | 26 +++++++------- pyproject.toml | 20 +++++++++++ setup.cfg | 2 ++ setup.py | 12 ------- 8 files changed, 111 insertions(+), 34 deletions(-) create mode 100644 .github/workflows/check-release.yml diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml new file mode 100644 index 000000000..8e4f4f3c9 --- /dev/null +++ b/.github/workflows/check-release.yml @@ -0,0 +1,55 @@ +name: Check Release +on: + push: + branches: ["master"] + pull_request: + branches: ["*"] + +jobs: + check_release: + runs-on: ubuntu-latest + strategy: + matrix: + group: [check_release, link_check] + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Install Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + architecture: "x64" + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: Cache pip + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }} + restore-keys: | + ${{ runner.os }}-pip- + ${{ runner.os }}-pip- + - name: Cache checked links + if: ${{ matrix.group == 'link_check' }} + uses: actions/cache@v2 + with: + path: ~/.cache/pytest-link-check + key: ${{ runner.os }}-linkcheck-${{ hashFiles('**/*.md', '**/*.rst') }}-md-links + restore-keys: | + ${{ runner.os }}-linkcheck- + - name: Upgrade packaging dependencies + run: | + pip install --upgrade pip setuptools wheel --user + - name: Install Dependencies + run: | + pip install -e . + - name: Check Release + if: ${{ matrix.group == 'check_release' }} + uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + - name: Run Link Check + if: ${{ matrix.group == 'link_check' }} + uses: jupyter-server/jupyter_releaser/.github/actions/check-links@v1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c34b2389..bd6c9b769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ ## 6.1.0 + + ### Enhancements made - Implemented `richInspectVariable` request handler [#734](https://github.com/ipython/ipykernel/pull/734) ([@JohanMabille](https://github.com/JohanMabille)) @@ -29,6 +31,8 @@ - Fix exception raised by `OutStream.write` [#726](https://github.com/ipython/ipykernel/pull/726) ([@SimonKrughoff](https://github.com/SimonKrughoff)) + + ## 6.0 ## 6.0.3 diff --git a/MANIFEST.in b/MANIFEST.in index 7bcad2d18..9f961f4ec 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,4 @@ -include COPYING.md -include CONTRIBUTING.md -include README.md +include *.md include pyproject.toml # Documentation @@ -23,3 +21,5 @@ global-exclude .git global-exclude .ipynb_checkpoints prune data_kernelspec +exclude .mailmap +exclude readthedocs.yml diff --git a/RELEASE.md b/RELEASE.md index aacb2ae2c..21c93a25d 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,19 +1,25 @@ # Release Guide -- Update `docs/changelog.rst` -- Update `ipykernel/_version.py` +## Using `jupyter_releaser` + +The recommended way to make a release is to use [`jupyter_releaser`](https://github.com/jupyter-server/jupyter_releaser#checklist-for-adoption). + +## Manual Release + +- Update `CHANGELOG` + - Run the following: ```bash -version=`python setup.py --version 2>/dev/null` -git commit -a -m "Release $version" -git tag $version; true; +export VERSION= +pip install jupyter_releaser +tbump --only-patch $VERSION +git commit -a -m "Release $VERSION" +git tag $VERSION; true; git push --all git push --tags rm -rf dist build -pip install build twine python -m build . -pip install twine twine check dist/* twine upload dist/* ``` diff --git a/ipykernel/_version.py b/ipykernel/_version.py index fc734ce43..eea34dcf8 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,16 +1,18 @@ -version_info = (6, 2, 0) -__version__ = ".".join(map(str, version_info[:3])) +""" +store the current version info of the server. +""" +import re -# pep440 is annoying, beta/alpha/rc should _not_ have dots or pip/setuptools -# confuses which one between the wheel and sdist is the most recent. -if len(version_info) == 4: - extra = version_info[3] - if extra.startswith(('a','b','rc')): - __version__ = __version__+extra - else: - __version__ = __version__+'.'+extra -if len(version_info) > 4: - raise NotImplementedError +# Version string must appear intact for tbump versioning +__version__ = '6.2.0' + +# Build up version_info tuple for backwards compatibility +pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' +match = re.match(pattern, __version__) +parts = [int(match[part]) for part in ['major', 'minor', 'patch']] +if match['rest']: + parts.append(match['rest']) +version_info = tuple(parts) kernel_protocol_version_info = (5, 3) kernel_protocol_version = '%s.%s' % kernel_protocol_version_info diff --git a/pyproject.toml b/pyproject.toml index 36d4cabc7..baea27242 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,3 +7,23 @@ requires=[ "jupyter_core>=4.2", "jupyter_client", ] + +[tool.check-manifest] +ignore = [] + +[tool.jupyter-releaser] +skip = ["check-links"] + +[tool.tbump.version] +current = "6.2.0" +regex = ''' + (?P\d+)\.(?P\d+)\.(?P\d+) + ((?Pa|b|rc|.dev)(?P\d+))? +''' + +[tool.tbump.git] +message_template = "Bump to {new_version}" +tag_template = "v{new_version}" + +[[tool.tbump.file]] +src = "ipykernel/_version.py" diff --git a/setup.cfg b/setup.cfg index 1920192c8..7fd8e1ae1 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,8 +1,10 @@ + [bdist_wheel] universal=0 [metadata] license_file = COPYING.md +version = attr: ipykernel._version.__version__ [nosetests] warningfilters= default |.* |DeprecationWarning |ipykernel.* diff --git a/setup.py b/setup.py index d3fea9cb6..8517cd99f 100644 --- a/setup.py +++ b/setup.py @@ -39,23 +39,11 @@ def run(self): 'ipykernel': ['resources/*.*'], } -version_ns = {} -with open(pjoin(here, name, '_version.py')) as f: - exec(f.read(), {}, version_ns) - -current_version = version_ns['__version__'] - -loose_pep440re = re.compile(r'^(\d+)\.(\d+)\.(\d+((a|b|rc)\d+)?)(\.post\d+)?(\.dev\d*)?$') -if not loose_pep440re.match(current_version): - raise ValueError("Version number '%s' is not valid (should match [N!]N(.N)*[{a|b|rc}N][.postN][.devN])" % current_version) - - with open(pjoin(here, 'README.md')) as fid: LONG_DESCRIPTION = fid.read() setup_args = dict( name=name, - version=current_version, cmdclass={ 'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled, }, From fff924d233602c80fcef87a4f51fececf391f211 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 16 Aug 2021 20:18:30 -0700 Subject: [PATCH 0616/1195] Remove Nose skipIf in favor of pytest --- ipykernel/tests/test_message_spec.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index a0cac9932..bfa81ca96 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -9,7 +9,10 @@ from queue import Empty import nose.tools as nt -from nose.plugins.skip import SkipTest + +import pytest + +import jupyter_client from traitlets import ( HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum @@ -483,10 +486,12 @@ def test_connect_request(): validate_message(reply, 'connect_reply', msg_id) +@pytest.mark.skipif( + jupyter_client.version_info < (5, 0), + reason="earlier Jupyter Client don't have comm_info", +) def test_comm_info_request(): flush_channels() - if not hasattr(KC, 'comm_info'): - raise SkipTest() msg_id = KC.comm_info() reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, 'comm_info_reply', msg_id) From aff71e6f7b4634aed8e30bcf32b06d58ab51b502 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 16 Aug 2021 20:07:48 -0700 Subject: [PATCH 0617/1195] remove more nose --- ipykernel/tests/test_kernelspec.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index 596960ee3..b42ff39b5 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -23,20 +23,18 @@ import pytest -import nose.tools as nt - pjoin = os.path.join def test_make_ipkernel_cmd(): cmd = make_ipkernel_cmd() - nt.assert_equal(cmd, [ + assert cmd == [ sys.executable, '-m', 'ipykernel_launcher', '-f', '{connection_file}' - ]) + ] def assert_kernel_dict(d): @@ -51,10 +49,9 @@ def test_get_kernel_dict(): def assert_kernel_dict_with_profile(d): - nt.assert_equal(d['argv'], make_ipkernel_cmd( - extra_arguments=["--profile", "test"])) - assert d['display_name'] == 'Python %i (ipykernel)' % sys.version_info[0] - assert d['language'] == 'python' + assert d["argv"] == make_ipkernel_cmd(extra_arguments=["--profile", "test"]) + assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0] + assert d["language"] == "python" def test_get_kernel_dict_with_profile(): @@ -127,7 +124,7 @@ def test_install_profile(): with open(spec) as f: spec = json.load(f) assert spec["display_name"].endswith(" [profile=Test]") - nt.assert_equal(spec["argv"][-2:], ["--profile", "Test"]) + assert spec["argv"][-2:] == ["--profile", "Test"] def test_install_display_name_overrides_profile(): From 6f089a89997499efea577f4e79e716344996e0ef Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 20 Aug 2021 21:18:45 +0200 Subject: [PATCH 0618/1195] Upgrade packages to pre-release, not their test dependencies --- .github/workflows/downstream.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index dba779eda..17f611cbf 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -23,9 +23,12 @@ jobs: run: | pip install --upgrade pip pip install pytest pytest-asyncio - pip install nbclient[test] --pre - pip install ipyparallel[test] --pre - pip install jupyter_client[test] --pre + pip install nbclient[test] + pip install --pre -U --upgrade-strategy=only-if-needed nbclient + pip install ipyparallel[test] + pip install --pre -U --upgrade-strategy=only-if-needed ipyparallel + pip install jupyter_client[test] + pip install --pre -U --upgrade-strategy=only-if-needed jupyter_client pip install . --force-reinstall pip freeze python -c 'import ipykernel; print("ipykernel", ipykernel.__version__)' From 9ad45d0f32022e9384def697b22ade790e4231e3 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Mon, 23 Aug 2021 11:22:00 +0200 Subject: [PATCH 0619/1195] Install pytest-tornado --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 17f611cbf..28d5059b0 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -22,7 +22,7 @@ jobs: - name: Install dependencies run: | pip install --upgrade pip - pip install pytest pytest-asyncio + pip install pytest pytest-asyncio pytest-tornado pip install nbclient[test] pip install --pre -U --upgrade-strategy=only-if-needed nbclient pip install ipyparallel[test] From cfc16dbf7053874bcc24a25228d0d0529d340a13 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 24 Aug 2021 05:50:53 -0500 Subject: [PATCH 0620/1195] Remove more nose test references --- README.md | 18 +++++------------- ipykernel/tests/test_io.py | 17 +++++++++-------- ipykernel/tests/test_jsonutil.py | 7 ++++--- ipykernel/tests/test_message_spec.py | 14 ++++++++------ setup.cfg | 4 ---- setup.py | 2 +- 6 files changed, 27 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 9744da286..06574280f 100644 --- a/README.md +++ b/README.md @@ -6,34 +6,26 @@ This package provides the IPython kernel for Jupyter. 1. `git clone` 2. `cd ipykernel` -3. `pip install -e .` +3. `pip install -e ".[test]"` After that, all normal `ipython` commands will use this newly-installed version of the kernel. ## Running tests -Ensure you have `nosetests` and the `nose-warnings-filters` plugin installed with - -```bash -pip install nose nose-warnings-filters -``` +Follow the instructions from `Installation from source`. and then from the root directory ```bash -nosetests ipykernel +pytest ipykernel ``` ## Running tests with coverage -Follow the instructions from `Running tests`. Ensure you have the `coverage` module installed with - -```bash -pip install coverage -``` +Follow the instructions from `Installation from source`. and then from the root directory ```bash -nosetests --with-coverage --cover-package ipykernel ipykernel +pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 ``` diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index 9fe3f4e2e..cad617879 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -4,10 +4,11 @@ import zmq +import pytest + from jupyter_client.session import Session from ipykernel.iostream import IOPubThread, OutStream -import nose.tools as nt def test_io_api(): """Test that wrapped stdout has the same API as a normal TextIO object""" @@ -26,19 +27,19 @@ def test_io_api(): assert stream.errors is None assert not stream.isatty() - with nt.assert_raises(io.UnsupportedOperation): + with pytest.raises(io.UnsupportedOperation): stream.detach() - with nt.assert_raises(io.UnsupportedOperation): + with pytest.raises(io.UnsupportedOperation): next(stream) - with nt.assert_raises(io.UnsupportedOperation): + with pytest.raises(io.UnsupportedOperation): stream.read() - with nt.assert_raises(io.UnsupportedOperation): + with pytest.raises(io.UnsupportedOperation): stream.readline() - with nt.assert_raises(io.UnsupportedOperation): + with pytest.raises(io.UnsupportedOperation): stream.seek(0) - with nt.assert_raises(io.UnsupportedOperation): + with pytest.raises(io.UnsupportedOperation): stream.tell() - with nt.assert_raises(TypeError): + with pytest.raises(TypeError): stream.write(b'') def test_io_isatty(): diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 4d8ccb604..9e9f0e5e3 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -9,7 +9,7 @@ from datetime import datetime import numbers -import nose.tools as nt +import pytest from .. import jsonutil from ..jsonutil import json_clean, encode_images @@ -84,7 +84,7 @@ def test_encode_images(): assert decoded == value def test_lambda(): - with nt.assert_raises(ValueError): + with pytest.raises(ValueError): json_clean(lambda : 1) @@ -93,7 +93,8 @@ def test_exception(): {True:'bool', 'True':'string'}, ] for d in bad_dicts: - nt.assert_raises(ValueError, json_clean, d) + with pytest.raises(ValueError): + json_clean(d) def test_unicode_dict(): diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index bfa81ca96..40ec5e4e2 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -8,8 +8,6 @@ from distutils.version import LooseVersion as V from queue import Empty -import nose.tools as nt - import pytest import jupyter_client @@ -295,7 +293,9 @@ def test_execute_silent(): validate_message(status, 'status', msg_id) assert status['content']['execution_state'] == 'idle' - nt.assert_raises(Empty, KC.get_iopub_msg, timeout=0.1) + with pytest.raises(Empty): + KC.get_iopub_msg(timeout=0.1) + count = reply['execution_count'] msg_id, reply = execute(code='x=2', silent=True) @@ -305,7 +305,9 @@ def test_execute_silent(): validate_message(status, 'status', msg_id) assert status['content']['execution_state'] == 'idle' - nt.assert_raises(Empty, KC.get_iopub_msg, timeout=0.1) + with pytest.raises(Empty): + KC.get_iopub_msg(timeout=0.1) + count_2 = reply['execution_count'] assert count_2 == count @@ -389,11 +391,11 @@ def test_user_expressions(): msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1')) user_expressions = reply['user_expressions'] - nt.assert_equal(user_expressions, {'foo': { + assert user_expressions == {'foo': { 'status': 'ok', 'data': {'text/plain': '2'}, 'metadata': {}, - }}) + }} def test_user_expressions_fail(): diff --git a/setup.cfg b/setup.cfg index 7fd8e1ae1..065b8a89c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,10 +6,6 @@ universal=0 license_file = COPYING.md version = attr: ipykernel._version.__version__ -[nosetests] -warningfilters= default |.* |DeprecationWarning |ipykernel.* - error |.*invalid.* |DeprecationWarning |matplotlib.* - [flake8] # References: # https://flake8.readthedocs.io/en/latest/user/configuration.html diff --git a/setup.py b/setup.py index 8517cd99f..4d14627aa 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,7 @@ def run(self): "pytest !=5.3.4", "pytest-cov", "flaky", - "nose", # nose because there are still a few nose.tools imports hanging around + "nose", # nose because we are still using nose streams from ipython "ipyparallel", ], }, From effad5a8e5c203a6365d6b018f33936afeba0674 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 26 Aug 2021 11:33:47 +0200 Subject: [PATCH 0621/1195] Add IPKernelApp.capture_fd_output config to disable FD-level capture allows opting out of the new behavior, if so desired --- ipykernel/kernelapp.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index a85dd6c75..8ad42d992 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -10,6 +10,7 @@ import signal import traceback import logging +from functools import partial from io import TextIOWrapper, FileIO from logging import StreamHandler @@ -162,6 +163,12 @@ def abs_connection_file(self): displayhook_class = DottedObjectName('ipykernel.displayhook.ZMQDisplayHook', help="The importstring for the DisplayHook factory").tag(config=True) + capture_fd_output = Bool( + True, + help="""Attempt to capture and forward low-level output, e.g. produced by Extension libraries. + """, + ).tag(config=True) + # polling parent_handle = Integer(int(os.environ.get('JPY_PARENT_PID') or 0), help="""kill this process if its parent dies. On Windows, the argument @@ -413,6 +420,9 @@ def init_io(self): e_stdout = None if self.quiet else sys.__stdout__ e_stderr = None if self.quiet else sys.__stderr__ + if not self.capture_fd_output: + outstream_factory = partial(outstream_factory, watchfd=False) + sys.stdout = outstream_factory(self.session, self.iopub_thread, 'stdout', echo=e_stdout) From 470145ca7d103bdf6081bcfde09933ab81a56a5b Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 27 Aug 2021 12:40:40 +0200 Subject: [PATCH 0622/1195] Implemented deep variable inspection --- ipykernel/debugger.py | 105 +++++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 28 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index ee53c8b54..48705d2e6 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -13,9 +13,53 @@ from .jsonutil import json_clean +# This import is required to have the next ones working... +from debugpy.server import api +from _pydevd_bundle import pydevd_frame_utils +from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager, _FramesTracker + # Required for backwards compatiblity ROUTING_ID = getattr(zmq, 'ROUTING_ID', None) or zmq.IDENTITY +class _FakeCode: + def __init__(self, co_filename, co_name): + self.co_filename = co_filename + self.co_name = co_name + +class _FakeFrame: + def __init__(self, f_code, f_globals, f_locals): + self.f_code = f_code + self.f_globals = f_globals + self.f_locals = f_locals + self.f_back = None + +class _DummyPyDB: + def __init__(self): + from _pydevd_bundle.pydevd_api import PyDevdAPI + self.variable_presentation = PyDevdAPI.VariablePresentation() + +class VariableExplorer: + def __init__(self): + self.suspended_frame_manager = SuspendedFramesManager() + self.py_db = _DummyPyDB() + self.tracker = _FramesTracker(self.suspended_frame_manager, self.py_db) + self.frame = None + + def track(self): + var = get_ipython().user_ns + self.frame = _FakeFrame(_FakeCode('', get_file_name('sys._getframe()')), var, var) + self.tracker.track('thread1', pydevd_frame_utils.create_frames_list_from_frame(self.frame)) + + def untrack_all(self): + self.tracker.untrack_all() + + def get_children_variables(self, variable_ref = None): + var_ref = variable_ref + if not var_ref: + var_ref = id(self.frame) + variables = self.suspended_frame_manager.get_variable(var_ref) + return [x.get_var_data() for x in variables.get_children_variables()] + class DebugpyMessageQueue: HEADER = 'Content-Length: ' @@ -233,6 +277,8 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): self.debugpy_port = 0 self.endpoint = None + self.variable_explorer = VariableExplorer() + def _handle_event(self, msg): if msg['event'] == 'stopped': self.stopped_threads.append(msg['body']['threadId']) @@ -246,6 +292,20 @@ def _handle_event(self, msg): async def _forward_message(self, msg): return await self.debugpy_client.send_dap_request(msg) + def _build_variables_response(self, request, variables): + var_list = [var for var in variables if self.accept_variable(var['name'])] + reply = { + 'seq': request['seq'], + 'type': 'response', + 'request_seq': request['seq'], + 'success': True, + 'command': request['command'], + 'body': { + 'variables': var_list + } + } + return reply + @property def tcp_client(self): return self.debugpy_client @@ -370,10 +430,15 @@ def accept_variable(self, variable_name): return cond async def variables(self, message): - reply = await self._forward_message(message) - # TODO : check start and count arguments work as expected in debugpy - reply['body']['variables'] = \ - [var for var in reply['body']['variables'] if self.accept_variable(var['name'])] + reply = {} + if not self.stopped_threads: + variables = self.variable_explorer.get_children_variables(message['arguments']['variablesReference']) + return self._build_variables_response(message, variables) + else: + reply = await self._forward_message(message) + # TODO : check start and count arguments work as expected in debugpy + reply['body']['variables'] = \ + [var for var in reply['body']['variables'] if self.accept_variable(var['name'])] return reply async def attach(self, message): @@ -420,30 +485,14 @@ async def debugInfo(self, message): return reply async def inspectVariables(self, message): - var_list = [] - for k, v in get_ipython().user_ns.items(): - if self.accept_variable(k): - try: - val = json_clean(v) - - except ValueError: - val = str(v) - var_list.append({ - 'name': k, - 'value': val, - 'type': str(type(v))[8:-2], - 'variablesReference': 0 - }) - reply = { - 'type': 'response', - 'request_seq': message['seq'], - 'success': True, - 'command': message['command'], - 'body': { - 'variables': var_list - } - } - return reply + self.variable_explorer.untrack_all() + # looks like the implementation of untrack_all in ptvsd + # destroys objects we nee din track. We have no choice but + # reinstantiate the object + self.variable_explorer = VariableExplorer() + self.variable_explorer.track() + variables = self.variable_explorer.get_children_variables() + return self._build_variables_response(message, variables) async def richInspectVariables(self, message): var_name = message['arguments']['variableName'] From eec08138b4aedfb56d1e775ac019619929598861 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Aug 2021 11:24:29 +0000 Subject: [PATCH 0623/1195] Automated Changelog Entry for 6.3.0 on master --- CHANGELOG.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd6c9b769..345cada60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,31 @@ +## 6.3.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/6.2.0...07af2633ca88eda583e13649279a5b98473618a2)) + +### Enhancements made + +- Implemented deep variable inspection [#753](https://github.com/ipython/ipykernel/pull/753) ([@JohanMabille](https://github.com/JohanMabille)) +- Add IPKernelApp.capture_fd_output config to disable FD-level capture [#752](https://github.com/ipython/ipykernel/pull/752) ([@minrk](https://github.com/minrk)) + +### Maintenance and upkeep improvements + +- Remove more nose test references [#750](https://github.com/ipython/ipykernel/pull/750) ([@blink1073](https://github.com/blink1073)) +- Remove Nose skipIf in favor of pytest [#748](https://github.com/ipython/ipykernel/pull/748) ([@Carreau](https://github.com/Carreau)) +- remove more nose [#747](https://github.com/ipython/ipykernel/pull/747) ([@Carreau](https://github.com/Carreau)) +- Set up release helper plumbing [#745](https://github.com/ipython/ipykernel/pull/745) ([@afshin](https://github.com/afshin)) +- Test downstream projects [#635](https://github.com/ipython/ipykernel/pull/635) ([@davidbrochart](https://github.com/davidbrochart)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2021-08-16&to=2021-08-30&type=c)) + +[@afshin](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aafshin+updated%3A2021-08-16..2021-08-30&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2021-08-16..2021-08-30&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2021-08-16..2021-08-30&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2021-08-16..2021-08-30&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2021-08-16..2021-08-30&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2021-08-16..2021-08-30&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akevin-bates+updated%3A2021-08-16..2021-08-30&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2021-08-16..2021-08-30&type=Issues) | [@SylvainCorlay](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2021-08-16..2021-08-30&type=Issues) + + + ### Enhancements made - Implemented `richInspectVariable` request handler [#734](https://github.com/ipython/ipykernel/pull/734) ([@JohanMabille](https://github.com/JohanMabille)) @@ -31,8 +56,6 @@ - Fix exception raised by `OutStream.write` [#726](https://github.com/ipython/ipykernel/pull/726) ([@SimonKrughoff](https://github.com/SimonKrughoff)) - - ## 6.0 ## 6.0.3 @@ -80,7 +103,6 @@ release and welcome any feedback (~50 Pull-requests). IPykernel 6 should contain all changes of the 5.x series, in addition to the following non-exhaustive changes. - - Support for the debugger protocol, when using `JupyterLab`, `RetroLab` or any frontend supporting the debugger protocol you should have access to the debugger functionalities. From 85495ecd4c88cbc8672da52f6b719de8e56b9413 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 30 Aug 2021 06:33:22 -0500 Subject: [PATCH 0624/1195] Update CHANGELOG.md --- CHANGELOG.md | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 345cada60..922a7d5ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,22 +1,5 @@ # Changes in IPython kernel -## 6.2 - -## 6.2.0 - -### Enhancements made - -- Add Support for Message Based Interrupt [#741](https://github.com/ipython/ipykernel/pull/741) ([@afshin](https://github.com/afshin)) - -### Maintenance and upkeep improvements - -- Remove some more dependency on nose/iptest [#743](https://github.com/ipython/ipykernel/pull/743) ([@Carreau](https://github.com/Carreau)) -- Remove block param from get_msg() [#736](https://github.com/ipython/ipykernel/pull/736) ([@davidbrochart](https://github.com/davidbrochart)) - -## 6.1 - -## 6.1.0 - ## 6.3.0 @@ -25,14 +8,14 @@ ### Enhancements made -- Implemented deep variable inspection [#753](https://github.com/ipython/ipykernel/pull/753) ([@JohanMabille](https://github.com/JohanMabille)) -- Add IPKernelApp.capture_fd_output config to disable FD-level capture [#752](https://github.com/ipython/ipykernel/pull/752) ([@minrk](https://github.com/minrk)) +- Add deep variable inspection [#753](https://github.com/ipython/ipykernel/pull/753) ([@JohanMabille](https://github.com/JohanMabille)) +- Add `IPKernelApp.capture_fd_output` config to disable FD-level capture [#752](https://github.com/ipython/ipykernel/pull/752) ([@minrk](https://github.com/minrk)) ### Maintenance and upkeep improvements -- Remove more nose test references [#750](https://github.com/ipython/ipykernel/pull/750) ([@blink1073](https://github.com/blink1073)) -- Remove Nose skipIf in favor of pytest [#748](https://github.com/ipython/ipykernel/pull/748) ([@Carreau](https://github.com/Carreau)) -- remove more nose [#747](https://github.com/ipython/ipykernel/pull/747) ([@Carreau](https://github.com/Carreau)) +- Remove more `nose` test references [#750](https://github.com/ipython/ipykernel/pull/750) ([@blink1073](https://github.com/blink1073)) +- Remove `nose` `skipIf` in favor of `pytest` [#748](https://github.com/ipython/ipykernel/pull/748) ([@Carreau](https://github.com/Carreau)) +- Remove more `nose` [#747](https://github.com/ipython/ipykernel/pull/747) ([@Carreau](https://github.com/Carreau)) - Set up release helper plumbing [#745](https://github.com/ipython/ipykernel/pull/745) ([@afshin](https://github.com/afshin)) - Test downstream projects [#635](https://github.com/ipython/ipykernel/pull/635) ([@davidbrochart](https://github.com/davidbrochart)) @@ -44,6 +27,23 @@ +## 6.2 + +## 6.2.0 + +### Enhancements made + +- Add Support for Message Based Interrupt [#741](https://github.com/ipython/ipykernel/pull/741) ([@afshin](https://github.com/afshin)) + +### Maintenance and upkeep improvements + +- Remove some more dependency on nose/iptest [#743](https://github.com/ipython/ipykernel/pull/743) ([@Carreau](https://github.com/Carreau)) +- Remove block param from get_msg() [#736](https://github.com/ipython/ipykernel/pull/736) ([@davidbrochart](https://github.com/davidbrochart)) + +## 6.1 + +## 6.1.0 + ### Enhancements made - Implemented `richInspectVariable` request handler [#734](https://github.com/ipython/ipykernel/pull/734) ([@JohanMabille](https://github.com/JohanMabille)) From ca290033bec65fa782bd92497a3a6e9e5b9e310e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 30 Aug 2021 06:34:07 -0500 Subject: [PATCH 0625/1195] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 922a7d5ba..51fe7f0d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ +## 6.3 + ## 6.3.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/6.2.0...07af2633ca88eda583e13649279a5b98473618a2)) From 00ca9f073095335d6f5b8b6a17da9fae01eda195 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 30 Aug 2021 11:47:58 +0000 Subject: [PATCH 0626/1195] Publish 6.3.0 SHA256 hashes: ipykernel-6.3.0-py3-none-any.whl: e87cff7dff9a04453e1f21921d937a5ce57b978698cfd0d6b1c1e00c865fa0f6 ipykernel-6.3.0.tar.gz: 5314690a638f893e2cc3bf3d25042920e9fbb873f7d8263033390264caeb95f4 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index eea34dcf8..4f18c3a68 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.2.0' +__version__ = '6.3.0' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index baea27242..a2d6b8147 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.2.0" +current = "6.3.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 10044cce504926b6ad6262a30683d3a81f787b7a Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 30 Aug 2021 18:01:26 -0700 Subject: [PATCH 0627/1195] Add dependency on IPython genutils. this was an implicit dependency via IPython, which does not requires it anymore and traitlets, which also does not requires it anymore since today. Closes #755 --- ipykernel/connect.py | 2 +- setup.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 275139cae..91646b6c8 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -7,7 +7,6 @@ import sys from subprocess import Popen, PIPE -from ipython_genutils.path import filefind import jupyter_client from jupyter_client import write_connection_file @@ -22,6 +21,7 @@ def get_connection_file(app=None): app : IPKernelApp instance [optional] If unspecified, the currently running app will be used """ + from ipython_genutils.path import filefind if app is None: from ipykernel.kernelapp import IPKernelApp if not IPKernelApp.initialized(): diff --git a/setup.py b/setup.py index 4d14627aa..1e4979071 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,7 @@ def run(self): keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], python_requires='>=3.7', install_requires=[ + "ipython_genutils", 'importlib-metadata<5;python_version<"3.8.0"', 'argcomplete>=1.12.3;python_version<"3.8.0"', 'debugpy>=1.0.0,<2.0', From 2e9052f8ad5dd4e9b76dc080b1919a4d3d60c5dd Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 01:23:05 +0000 Subject: [PATCH 0628/1195] Automated Changelog Entry for 6.3.1 on master --- CHANGELOG.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51fe7f0d4..8722bad52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.3.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.3.0...0b4a8eaa080fc11e240ada9c44c95841463da58c)) + +### Merged PRs + +- Add dependency on IPython genutils. [#756](https://github.com/ipython/ipykernel/pull/756) ([@Carreau](https://github.com/Carreau)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2021-08-30&to=2021-08-31&type=c)) + +[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2021-08-30..2021-08-31&type=Issues) + + + ## 6.3 ## 6.3.0 @@ -27,8 +43,6 @@ [@afshin](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aafshin+updated%3A2021-08-16..2021-08-30&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2021-08-16..2021-08-30&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2021-08-16..2021-08-30&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2021-08-16..2021-08-30&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2021-08-16..2021-08-30&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2021-08-16..2021-08-30&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akevin-bates+updated%3A2021-08-16..2021-08-30&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2021-08-16..2021-08-30&type=Issues) | [@SylvainCorlay](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2021-08-16..2021-08-30&type=Issues) - - ## 6.2 ## 6.2.0 From 81f8ea0c0978fde83d83e6096797ac597e1a9198 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 30 Aug 2021 20:24:24 -0500 Subject: [PATCH 0629/1195] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8722bad52..4af0e8271 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changes in IPython kernel +## 6.3 + ## 6.3.1 @@ -18,8 +20,6 @@ -## 6.3 - ## 6.3.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/6.2.0...07af2633ca88eda583e13649279a5b98473618a2)) From be9d40dd00db80388b698f46e65e279b012b4f10 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 31 Aug 2021 01:31:55 +0000 Subject: [PATCH 0630/1195] Publish 6.3.1 SHA256 hashes: ipykernel-6.3.1-py3-none-any.whl: 3d34530e031067f04e88b72715e92c27871368c15998692d665d57027ceb18d9 ipykernel-6.3.1.tar.gz: 6dd4b107ab755ed9286c820b2f69c2cd895046ef2a25c878929ac8b5540477a1 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 4f18c3a68..c4b558ffb 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.3.0' +__version__ = '6.3.1' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index a2d6b8147..d8f33fe5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.3.0" +current = "6.3.1" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From cf2d56ecbe92bd557842137b9e9689fad1eedac3 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Sep 2021 10:26:43 -0700 Subject: [PATCH 0631/1195] Update some warnings with instructions and version number. I came across this when trying to reproduce an unrelated bug with someone in an outdated environment, but at least having the version number will make us more confident when e remove the conditional branch later. --- ipykernel/iostream.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 56e6d8a15..2f760b8dd 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -252,8 +252,13 @@ def __getattr__(self, attr): # don't wrap magic methods super(BackgroundSocket, self).__getattr__(attr) if hasattr(self.io_thread.socket, attr): - warnings.warn("Accessing zmq Socket attribute %s on BackgroundSocket" % attr, - DeprecationWarning, stacklevel=2) + warnings.warn( + "Accessing zmq Socket attribute {attr} on BackgroundSocket" + " is deprecated since ipykernel 4.3.0" + " use .io_thread.socket.{attr}".format(attr=attr), + DeprecationWarning, + stacklevel=2, + ) return getattr(self.io_thread.socket, attr) super(BackgroundSocket, self).__getattr__(attr) @@ -261,8 +266,13 @@ def __setattr__(self, attr, value): if attr == 'io_thread' or (attr.startswith('__' and attr.endswith('__'))): super(BackgroundSocket, self).__setattr__(attr, value) else: - warnings.warn("Setting zmq Socket attribute %s on BackgroundSocket" % attr, - DeprecationWarning, stacklevel=2) + warnings.warn( + "Setting zmq Socket attribute {attr} on BackgroundSocket" + " is deprecated since ipykernel 4.3.0" + " use .io_thread.socket.{attr}".format(attr=attr), + DeprecationWarning, + stacklevel=2, + ) setattr(self.io_thread.socket, attr, value) def send(self, msg, *args, **kwargs): From 0e7cf85263617cf2a18ef272199f850739793799 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Mon, 6 Sep 2021 14:15:05 +0200 Subject: [PATCH 0632/1195] Fix undefined variable + Fix some pep8 issues --- ipykernel/debugger.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 48705d2e6..aa4ea8beb 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -11,21 +11,21 @@ from IPython.core.getipython import get_ipython -from .jsonutil import json_clean - # This import is required to have the next ones working... -from debugpy.server import api +from debugpy.server import api # noqa from _pydevd_bundle import pydevd_frame_utils from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager, _FramesTracker # Required for backwards compatiblity ROUTING_ID = getattr(zmq, 'ROUTING_ID', None) or zmq.IDENTITY + class _FakeCode: def __init__(self, co_filename, co_name): self.co_filename = co_filename self.co_name = co_name + class _FakeFrame: def __init__(self, f_code, f_globals, f_locals): self.f_code = f_code @@ -33,11 +33,13 @@ def __init__(self, f_code, f_globals, f_locals): self.f_locals = f_locals self.f_back = None + class _DummyPyDB: def __init__(self): from _pydevd_bundle.pydevd_api import PyDevdAPI self.variable_presentation = PyDevdAPI.VariablePresentation() + class VariableExplorer: def __init__(self): self.suspended_frame_manager = SuspendedFramesManager() @@ -60,6 +62,7 @@ def get_children_variables(self, variable_ref = None): variables = self.suspended_frame_manager.get_variable(var_ref) return [x.get_var_data() for x in variables.get_children_variables()] + class DebugpyMessageQueue: HEADER = 'Content-Length: ' @@ -102,7 +105,7 @@ def put_tcp_frame(self, frame): self.header_pos = self.tcp_buffer.find(DebugpyMessageQueue.HEADER) if self.header_pos == -1: return - + self.log.debug('QUEUE - found header at pos %i', self.header_pos) #Finds separator @@ -138,7 +141,7 @@ def put_tcp_frame(self, frame): async def get_message(self): return await self.message_queue.get() - + class DebugpyClient: @@ -175,7 +178,7 @@ def _send_request(self, msg): self.log.debug(self.routing_id) self.log.debug(buf) self.debugpy_stream.send_multipart((self.routing_id, buf)) - + async def _wait_for_response(self): # Since events are never pushed to the message_queue # we can safely assume the next message in queue @@ -185,7 +188,7 @@ async def _wait_for_response(self): async def _handle_init_sequence(self): # 1] Waits for initialized event await self.init_event.wait() - + # 2] Sends configurationDone request configurationDone = { 'type': 'request', @@ -246,7 +249,7 @@ class Debugger: 'variables', 'attach', 'configurationDone' ] - + # Requests that can be handled even if the debugger is not running static_debug_msg_types = [ 'debugInfo', 'inspectVariables', 'richInspectVariables' @@ -259,7 +262,7 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): self.session = session self.is_started = False self.event_callback = event_callback - + self.started_debug_handlers = {} for msg_type in Debugger.started_debug_msg_types: self.started_debug_handlers[msg_type] = getattr(self, msg_type) @@ -324,7 +327,7 @@ def start(self): } self.session.send(self.shell_socket, 'execute_request', content, None, (self.shell_socket.getsockopt(ROUTING_ID))) - + ident, msg = self.session.recv(self.shell_socket, mode=0) self.debugpy_initialized = msg['content']['status'] == 'ok' self.debugpy_client.connect_tcp_socket() @@ -517,7 +520,7 @@ async def richInspectVariables(self, message): 'arguments': { 'expression': lvalue, 'value': code, - 'frameId': frameId + 'frameId': frame_id } } await self._forward_message(request) @@ -535,10 +538,10 @@ async def richInspectVariables(self, message): 'data': {}, 'metadata': {} } - + for key, value in repr_data.items(): body['data']['key'] = value - if repr_metadata.has_key(key): + if key in repr_metadata: body['metadata'][key] = repr_metadata[key] globals().pop(var_repr_data) From 168548ebbcb0880c50cab3d13fca39c3d10b1461 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 1 Sep 2021 13:55:46 -0700 Subject: [PATCH 0633/1195] Stop using deprecated recv_multipart when using in-process socket. Found while working on https://github.com/napari/napari/issues/3314 This should be the right fix, as BackgroundSocket is used only in inprocess kernel, and while in general iopub_socket looks like it can be `Any()` for this particular class we have a trait saying iopub_socket has to be a BackgroundSocket The recv in jupyter_client side (which is called by the line I change here) is def recv(self, socket, mode=zmq.NOBLOCK, content=True, copy=True): """Receive and unpack a message. Parameters ---------- socket : ZMQStream or Socket The socket or stream to use in receiving. Returns ------- [idents], msg [idents] is a list of idents and msg is a nested message dict of same format as self.msg returns. """ if isinstance(socket, ZMQStream): socket = socket.socket try: msg_list = socket.recv_multipart(mode, copy=copy) # this will trigger deprecation warning except zmq.ZMQError as e: ... And I doubt we want to make that aware of background socket. --- ipykernel/inprocess/ipkernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 0e86c8f6d..9a10117eb 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -131,7 +131,7 @@ def _redirected_io(self): def _io_dispatch(self, change): """ Called when a message is sent to the IO socket. """ - ident, msg = self.session.recv(self.iopub_socket, copy=False) + ident, msg = self.session.recv(self.iopub_socket.io_thread.socket, copy=False) for frontend in self.frontends: frontend.iopub_channel.call_handlers(msg) From 1483408acb809bc197304cb373d5fcfca32b16b5 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 6 Sep 2021 14:48:53 -0700 Subject: [PATCH 0634/1195] Don't assume kernels have loops. In particular the in-process kernels don't. This does not fix all the issues in particular the quit() and exit() autocaller that become no-op, but that's another story --- ipykernel/zmqshell.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index c60fe6014..a53dfbe2c 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -464,8 +464,9 @@ def _default_exiter(self): def _update_exit_now(self, change): """stop eventloop when exit_now fires""" if change['new']: - loop = self.kernel.io_loop - loop.call_later(0.1, loop.stop) + if hasattr(self.kernel, 'io_loop'): + loop = self.kernel.io_loop + loop.call_later(0.1, loop.stop) if self.kernel.eventloop: exit_hook = getattr(self.kernel.eventloop, 'exit_hook', None) if exit_hook: From 544fdb2b222c760b8ae457bbe94d2bf5cfc59715 Mon Sep 17 00:00:00 2001 From: Lumir Balhar Date: Tue, 7 Sep 2021 09:01:49 +0200 Subject: [PATCH 0635/1195] Make ipykernel work without debugpy debugpy is an optional dependency because only frontends with debugging support (Jupyter lab) can really use its features. Fixes: https://github.com/ipython/ipykernel/issues/712 --- .github/workflows/ci.yml | 41 ++++++++++++++++++++++++++++++++++++++++ ipykernel/ipkernel.py | 33 ++++++++++++++++++++------------ ipykernel/kernelspec.py | 4 +++- 3 files changed, 65 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82bd2fb50..86fc6ff68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,3 +110,44 @@ jobs: - name: Check Docstrings run: | velin . --check --compact + test_without_debugpy: + runs-on: ${{ matrix.os }}-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu] + python-version: [ '3.9' ] + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Install Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + architecture: 'x64' + - name: Upgrade packaging dependencies + run: | + pip install --upgrade pip setuptools wheel --user + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: Cache pip + uses: actions/cache@v1 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.python-version }}- + ${{ runner.os }}-pip- + - name: Install the Python dependencies without debugpy + run: | + pip install --pre --upgrade --upgrade-strategy=eager .[test] + pip uninstall --yes debugpy + - name: List installed packages + run: | + pip freeze + - name: Run the tests + timeout-minutes: 10 + run: | + pytest ipykernel -vv -s --durations 10 diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 6943deacc..2cc8eab54 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -17,7 +17,6 @@ from .kernelbase import Kernel as KernelBase from .zmqshell import ZMQInteractiveShell from .eventloops import _use_appnope -from .debugger import Debugger from .compiler import XCachingCompiler try: @@ -34,6 +33,13 @@ except ImportError: _use_experimental_60_completion = False +try: + import debugpy + from .debugger import Debugger + _is_debugpy_available = True +except ImportError: + _is_debugpy_available = False + _EXPERIMENTAL_KEY_NAME = '_jupyter_types_experimental' @@ -46,7 +52,7 @@ class IPythonKernel(KernelBase): help="Set this flag to False to deactivate the use of experimental IPython completion APIs.", ).tag(config=True) - debugpy_stream = Instance(ZMQStream, allow_none=True) + debugpy_stream = Instance(ZMQStream, allow_none=True) if _is_debugpy_available else None user_module = Any() @observe('user_module') @@ -72,11 +78,12 @@ def __init__(self, **kwargs): super(IPythonKernel, self).__init__(**kwargs) # Initialize the Debugger - self.debugger = Debugger(self.log, - self.debugpy_stream, - self._publish_debug_event, - self.debug_shell_socket, - self.session) + if _is_debugpy_available: + self.debugger = Debugger(self.log, + self.debugpy_stream, + self._publish_debug_event, + self.debug_shell_socket, + self.session) # Initialize the InteractiveShell subclass self.shell = self.shell_class.instance(parent=self, @@ -152,10 +159,11 @@ def __init__(self, **kwargs): } def dispatch_debugpy(self, msg): - # The first frame is the socket id, we can drop it - frame = msg[1].bytes.decode('utf-8') - self.log.debug("Debugpy received: %s", frame) - self.debugger.tcp_client.receive_dap_frame(frame) + if _is_debugpy_available: + # The first frame is the socket id, we can drop it + frame = msg[1].bytes.decode('utf-8') + self.log.debug("Debugpy received: %s", frame) + self.debugger.tcp_client.receive_dap_frame(frame) @property def banner(self): @@ -414,7 +422,8 @@ def do_complete(self, code, cursor_pos): 'status' : 'ok'} async def do_debug_request(self, msg): - return await self.debugger.process_request(msg) + if _is_debugpy_available: + return await self.debugger.process_request(msg) def _experimental_do_complete(self, code, cursor_pos): """ diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 5f93c9fff..c7514084a 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -13,6 +13,8 @@ from jupyter_client.kernelspec import KernelSpecManager +from .ipkernel import _is_debugpy_available + pjoin = os.path.join KERNEL_NAME = 'python%i' % sys.version_info[0] @@ -52,7 +54,7 @@ def get_kernel_dict(extra_arguments=None): 'argv': make_ipkernel_cmd(extra_arguments=extra_arguments), 'display_name': 'Python %i (ipykernel)' % sys.version_info[0], 'language': 'python', - 'metadata': { 'debugger': True} + 'metadata': { 'debugger': _is_debugpy_available} } From a7aa5a0a83b3e46364251e607b350b30eb34c7cc Mon Sep 17 00:00:00 2001 From: martinRenou Date: Wed, 30 Jun 2021 16:51:20 +0200 Subject: [PATCH 0636/1195] Make json_clean a no-op for jupyter-client >= 7 --- ipykernel/datapub.py | 2 +- ipykernel/debugger.py | 17 ++++++++++++++--- ipykernel/inprocess/ipkernel.py | 4 ++-- ipykernel/jsonutil.py | 17 +++++++++++++---- ipykernel/tests/test_jsonutil.py | 18 +++++++++++++++--- 5 files changed, 45 insertions(+), 13 deletions(-) diff --git a/ipykernel/datapub.py b/ipykernel/datapub.py index c124195cb..fbc263d08 100644 --- a/ipykernel/datapub.py +++ b/ipykernel/datapub.py @@ -66,6 +66,6 @@ def publish_data(data): DeprecationWarning, stacklevel=2 ) - + from ipykernel.zmqshell import ZMQInteractiveShell ZMQInteractiveShell.instance().data_pub.publish_data(data) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index aa4ea8beb..0f514aba7 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -7,10 +7,15 @@ from tornado.queues import Queue from tornado.locks import Event -from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed) - from IPython.core.getipython import get_ipython +try: + from jupyter_client.jsonutil import json_default +except ImportError: + from jupyter_client.jsonutil import date_default as json_default + +from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed) + # This import is required to have the next ones working... from debugpy.server import api # noqa from _pydevd_bundle import pydevd_frame_utils @@ -170,7 +175,12 @@ def _forward_event(self, msg): def _send_request(self, msg): if self.routing_id is None: self.routing_id = self.debugpy_stream.socket.getsockopt(ROUTING_ID) - content = jsonapi.dumps(msg) + content = jsonapi.dumps( + msg, + default=json_default, + ensure_ascii=False, + allow_nan=False, + ) content_length = str(len(content)) buf = (DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR).encode('ascii') buf += content @@ -240,6 +250,7 @@ async def send_dap_request(self, msg): self.log.debug(rep) return rep + class Debugger: # Requests that requires that the debugger has started diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 9a10117eb..bbe2b4345 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -152,12 +152,12 @@ def _default_shell_class(self): @default('stdout') def _default_stdout(self): - return OutStream(self.session, self.iopub_thread, 'stdout', + return OutStream(self.session, self.iopub_thread, 'stdout', watchfd=False) @default('stderr') def _default_stderr(self): - return OutStream(self.session, self.iopub_thread, 'stderr', + return OutStream(self.session, self.iopub_thread, 'stderr', watchfd=False) #----------------------------------------------------------------------------- diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 514955715..a777cc72a 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -9,6 +9,7 @@ import types from datetime import datetime import numbers +from jupyter_client._version import version_info as jupyter_client_version next_attr_name = '__next__' @@ -42,6 +43,9 @@ # front of PDF base64-encoded PDF64 = b'JVBER' +JUPYTER_CLIENT_MAJOR_VERSION = jupyter_client_version[0] + + def encode_images(format_dict): """b64-encodes images in a displaypub format dict @@ -68,7 +72,9 @@ def encode_images(format_dict): def json_clean(obj): - """Clean an object to ensure it's safe to encode in JSON. + """Deprecated, this is a no-op for jupyter-client>=7. + + Clean an object to ensure it's safe to encode in JSON. Atomic, immutable objects are returned unmodified. Sets and tuples are converted to lists, lists are copied and dicts are also copied. @@ -89,6 +95,9 @@ def json_clean(obj): it simply sanitizes it so that there will be no encoding errors later. """ + if JUPYTER_CLIENT_MAJOR_VERSION >= 7: + return obj + # types that are 'atomic' and ok in json as-is. atomic_ok = (str, type(None)) @@ -110,10 +119,10 @@ def json_clean(obj): if math.isnan(obj) or math.isinf(obj): return repr(obj) return float(obj) - + if isinstance(obj, atomic_ok): return obj - + if isinstance(obj, bytes): # unanmbiguous binary data is base64-encoded # (this probably should have happened upstream) @@ -142,6 +151,6 @@ def json_clean(obj): return out if isinstance(obj, datetime): return obj.strftime(ISO8601) - + # we don't understand it, it's probably an unserializable object raise ValueError("Can't clean for JSON: %r" % obj) diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 9e9f0e5e3..511cad39e 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -11,20 +11,28 @@ import pytest +from jupyter_client._version import version_info as jupyter_client_version + from .. import jsonutil from ..jsonutil import json_clean, encode_images + +JUPYTER_CLIENT_MAJOR_VERSION = jupyter_client_version[0] + + class MyInt(object): def __int__(self): return 389 numbers.Integral.register(MyInt) + class MyFloat(object): def __float__(self): return 3.14 numbers.Real.register(MyFloat) +@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op") def test(): # list of input/expected output. Use None for the expected output if it # can be the same as the input. @@ -47,7 +55,7 @@ def test(): (MyFloat(), 3.14), (MyInt(), 389) ] - + for val, jval in pairs: if jval is None: jval = val @@ -58,13 +66,14 @@ def test(): json.loads(json.dumps(out)) +@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op") def test_encode_images(): # invalid data, but the header and footer are from real files pngdata = b'\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82' jpegdata = b'\xff\xd8\xff\xe0\x00\x10JFIFblahblahjpeg(\xa0\x0f\xff\xd9' pdfdata = b'%PDF-1.\ntrailer<>]>>>>>>' bindata = b'\xff\xff\xff\xff' - + fmt = { 'image/png' : pngdata, 'image/jpeg' : jpegdata, @@ -78,16 +87,18 @@ def test_encode_images(): assert decoded == value encoded2 = json_clean(encode_images(encoded)) assert encoded == encoded2 - + for key, value in fmt.items(): decoded = a2b_base64(encoded[key]) assert decoded == value +@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op") def test_lambda(): with pytest.raises(ValueError): json_clean(lambda : 1) +@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op") def test_exception(): bad_dicts = [{1:'number', '1':'string'}, {True:'bool', 'True':'string'}, @@ -97,6 +108,7 @@ def test_exception(): json_clean(d) +@pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op") def test_unicode_dict(): data = {'üniço∂e': 'üniço∂e'} clean = jsonutil.json_clean(data) From 49d6c1c7cae1974ac7e961aa9273abe36e566d05 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Sep 2021 08:48:16 +0000 Subject: [PATCH 0637/1195] Automated Changelog Entry for 6.4.0 on master --- CHANGELOG.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4af0e8271..8dcee8c19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,33 @@ +## 6.4.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.3.1...1ba6b48a97877ff7a564af32c531618efb7d2a57)) + +### Enhancements made + +- Make json_clean a no-op for jupyter-client >= 7 [#708](https://github.com/ipython/ipykernel/pull/708) ([@martinRenou](https://github.com/martinRenou)) + +### Bugs fixed + +- Don't assume kernels have loops. [#766](https://github.com/ipython/ipykernel/pull/766) ([@Carreau](https://github.com/Carreau)) +- Fix undefined variable [#765](https://github.com/ipython/ipykernel/pull/765) ([@martinRenou](https://github.com/martinRenou)) + +### Maintenance and upkeep improvements + +- Make ipykernel work without debugpy [#767](https://github.com/ipython/ipykernel/pull/767) ([@frenzymadness](https://github.com/frenzymadness)) +- Stop using deprecated recv_multipart when using in-process socket. [#762](https://github.com/ipython/ipykernel/pull/762) ([@Carreau](https://github.com/Carreau)) +- Update some warnings with instructions and version number. [#761](https://github.com/ipython/ipykernel/pull/761) ([@Carreau](https://github.com/Carreau)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2021-08-31&to=2021-09-09&type=c)) + +[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2021-08-31..2021-09-09&type=Issues) | [@frenzymadness](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afrenzymadness+updated%3A2021-08-31..2021-09-09&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AmartinRenou+updated%3A2021-08-31..2021-09-09&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2021-08-31..2021-09-09&type=Issues) + + + ## 6.3.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.3.0...0b4a8eaa080fc11e240ada9c44c95841463da58c)) @@ -18,8 +45,6 @@ [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2021-08-30..2021-08-31&type=Issues) - - ## 6.3.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/6.2.0...07af2633ca88eda583e13649279a5b98473618a2)) From e89b6f285bccddc02146ead5ef7e050e8d8f40bc Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 9 Sep 2021 03:49:38 -0500 Subject: [PATCH 0638/1195] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8dcee8c19..227d2d0df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,17 +10,17 @@ ### Enhancements made -- Make json_clean a no-op for jupyter-client >= 7 [#708](https://github.com/ipython/ipykernel/pull/708) ([@martinRenou](https://github.com/martinRenou)) +- Make `json_clean` a no-op for `jupyter-client` >= 7 [#708](https://github.com/ipython/ipykernel/pull/708) ([@martinRenou](https://github.com/martinRenou)) ### Bugs fixed -- Don't assume kernels have loops. [#766](https://github.com/ipython/ipykernel/pull/766) ([@Carreau](https://github.com/Carreau)) +- Do not assume kernels have loops [#766](https://github.com/ipython/ipykernel/pull/766) ([@Carreau](https://github.com/Carreau)) - Fix undefined variable [#765](https://github.com/ipython/ipykernel/pull/765) ([@martinRenou](https://github.com/martinRenou)) ### Maintenance and upkeep improvements -- Make ipykernel work without debugpy [#767](https://github.com/ipython/ipykernel/pull/767) ([@frenzymadness](https://github.com/frenzymadness)) -- Stop using deprecated recv_multipart when using in-process socket. [#762](https://github.com/ipython/ipykernel/pull/762) ([@Carreau](https://github.com/Carreau)) +- Make `ipykernel` work without `debugpy` [#767](https://github.com/ipython/ipykernel/pull/767) ([@frenzymadness](https://github.com/frenzymadness)) +- Stop using deprecated `recv_multipart` when using in-process socket. [#762](https://github.com/ipython/ipykernel/pull/762) ([@Carreau](https://github.com/Carreau)) - Update some warnings with instructions and version number. [#761](https://github.com/ipython/ipykernel/pull/761) ([@Carreau](https://github.com/Carreau)) ### Contributors to this release From 339d13ce11709ca64a434a4e136e7035e033a3eb Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 9 Sep 2021 09:04:01 +0000 Subject: [PATCH 0639/1195] Publish 6.4.0 SHA256 hashes: ipykernel-6.4.0-py3-none-any.whl: 72d62a362777ee91eb990b90e55bf4375621aac478a34bf49c0ed1f3ae64167f ipykernel-6.4.0.tar.gz: 0bf7a9563e5ff053049c95d18b1488f4307cf334e3381020a83983274295d625 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index c4b558ffb..5b0b7c40b 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.3.1' +__version__ = '6.4.0' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index d8f33fe5b..2a27c4658 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.3.1" +current = "6.4.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 8ce8c4dd6d8f2b4ae16db0579f47d51d23295d79 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 10 Sep 2021 14:48:52 +0200 Subject: [PATCH 0640/1195] debugpy is a build requirement to get the right debugger metadata in the wheel kernelspec --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 2a27c4658..ab331bca1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ build-backend = "setuptools.build_meta" requires=[ "setuptools", "wheel", + "debugpy", "ipython>=5", "jupyter_core>=4.2", "jupyter_client", From 018587782433fffac6f7fd33c94996edba1c90ec Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Sep 2021 13:00:17 +0000 Subject: [PATCH 0641/1195] Automated Changelog Entry for 6.4.1 on master --- CHANGELOG.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 227d2d0df..390baae95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ +## 6.4.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.4.0...4da7623c1ae733f32c0792d70e7af283a7b19d22)) + +### Merged PRs + +- debugpy is now a build requirement [#773](https://github.com/ipython/ipykernel/pull/773) ([@minrk](https://github.com/minrk)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2021-09-09&to=2021-09-10&type=c)) + +[@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2021-09-09..2021-09-10&type=Issues) + + + ## 6.4.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.3.1...1ba6b48a97877ff7a564af32c531618efb7d2a57)) @@ -29,8 +45,6 @@ [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2021-08-31..2021-09-09&type=Issues) | [@frenzymadness](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afrenzymadness+updated%3A2021-08-31..2021-09-09&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AmartinRenou+updated%3A2021-08-31..2021-09-09&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2021-08-31..2021-09-09&type=Issues) - - ## 6.3.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.3.0...0b4a8eaa080fc11e240ada9c44c95841463da58c)) From 81a640bed36ccbfa97fc1032f4a14dd0e51eab91 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Sep 2021 13:07:58 +0000 Subject: [PATCH 0642/1195] Publish 6.4.1 SHA256 hashes: ipykernel-6.4.1-py3-none-any.whl: a3f6c2dda2ecf63b37446808a70ed825fea04790779ca524889c596deae0def8 ipykernel-6.4.1.tar.gz: df3355e5eec23126bc89767a676c5f0abfc7f4c3497d118c592b83b316e8c0cd --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 5b0b7c40b..d2a860062 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.4.0' +__version__ = '6.4.1' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index ab331bca1..7e8919d8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.4.0" +current = "6.4.1" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From e04306189ac75c654967a2de686555f248eb372d Mon Sep 17 00:00:00 2001 From: Ray Osborn Date: Wed, 6 Oct 2021 15:44:12 -0500 Subject: [PATCH 0643/1195] Remove setting of eventloop function If the InProcessInteractiveShell version of `enable_gui` calls the parent function in eventloops.py, it initializes `kernel.eventloop`, whereas it should be `None` in the InProcessKernel. This triggers exceptions in the kernel's `entering_eventloop` function. --- ipykernel/inprocess/ipkernel.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index bbe2b4345..d8fe1d5e2 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -175,13 +175,10 @@ class InProcessInteractiveShell(ZMQInteractiveShell): def enable_gui(self, gui=None): """Enable GUI integration for the kernel.""" - from ipykernel.eventloops import enable_gui if not gui: gui = self.kernel.gui - enable_gui(gui, kernel=self.kernel) self.active_eventloop = gui - def enable_matplotlib(self, gui=None): """Enable matplotlib integration for the kernel.""" if not gui: From 904ec5a4b3ec9f052808d78b3323086449dd1b16 Mon Sep 17 00:00:00 2001 From: Eric Muccino <33988506+emuccino@users.noreply.github.com> Date: Thu, 7 Oct 2021 15:57:38 -0400 Subject: [PATCH 0644/1195] Add python version classifiers Expand classifiers list to specify capability with python versions 3.7, 3.8, and 3.9 --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 1e4979071..1cb41b4a4 100644 --- a/setup.py +++ b/setup.py @@ -89,6 +89,9 @@ def run(self): 'License :: OSI Approved :: BSD License', 'Programming Language :: Python', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9' ], ) From 37c68468f3a8208bc7d16934da51927819f06a1a Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 19 Oct 2021 15:16:02 +0200 Subject: [PATCH 0645/1195] Enabled rich rendering of variables in the debugger --- ipykernel/debugger.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 0f514aba7..3d895f350 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -493,7 +493,8 @@ async def debugInfo(self, message): 'tmpFilePrefix': get_tmp_directory() + '/', 'tmpFileSuffix': '.py', 'breakpoints': breakpoint_list, - 'stoppedThreads': self.stopped_threads + 'stoppedThreads': self.stopped_threads, + 'richRendering': True } } return reply @@ -509,7 +510,24 @@ async def inspectVariables(self, message): return self._build_variables_response(message, variables) async def richInspectVariables(self, message): + reply = { + 'type': 'response', + 'sequence_seq': message['seq'], + 'success': False, + 'command': message['command'] + } + var_name = message['arguments']['variableName'] + valid_name = str.isidentifier(var_name) + if not valid_name: + reply['body'] = { + 'data': {}, + 'metadata': {} + } + if var_name == 'special variables' or var_name == 'function variables': + reply['success'] = True + return reply + var_repr_data = var_name + '_repr_data' var_repr_metadata = var_name + '_repr_metadata' @@ -536,13 +554,6 @@ async def richInspectVariables(self, message): } await self._forward_message(request) - reply = { - 'type': 'response', - 'sequence_seq': message['seq'], - 'success': False, - 'command': message['command'] - } - repr_data = globals()[var_repr_data] repr_metadata = globals()[var_repr_metadata] body = { From af033676731db65043ae796833330ae916718ab9 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 19 Oct 2021 18:29:55 +0000 Subject: [PATCH 0646/1195] Automated Changelog Entry for 6.4.2 on master --- CHANGELOG.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 390baae95..8de4111cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,30 @@ +## 6.4.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.4.1...231fd3c65f8a15e9e015546c0a6846e22df9ba2a)) + +### Enhancements made + +- Enabled rich rendering of variables in the debugger [#787](https://github.com/ipython/ipykernel/pull/787) ([@JohanMabille](https://github.com/JohanMabille)) + +### Bugs fixed + +- Remove setting of the eventloop function in the InProcessKernel [#781](https://github.com/ipython/ipykernel/pull/781) ([@rayosborn](https://github.com/rayosborn)) + +### Maintenance and upkeep improvements + +- Add python version classifiers [#783](https://github.com/ipython/ipykernel/pull/783) ([@emuccino](https://github.com/emuccino)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2021-09-10&to=2021-10-19&type=c)) + +[@emuccino](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aemuccino+updated%3A2021-09-10..2021-10-19&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2021-09-10..2021-10-19&type=Issues) | [@rayosborn](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Arayosborn+updated%3A2021-09-10..2021-10-19&type=Issues) + + + ## 6.4.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.4.0...4da7623c1ae733f32c0792d70e7af283a7b19d22)) @@ -18,8 +42,6 @@ [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2021-09-09..2021-09-10&type=Issues) - - ## 6.4.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.3.1...1ba6b48a97877ff7a564af32c531618efb7d2a57)) From fdda069bba36cafcc25df4d2353b26fbdb9e4d15 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 20 Oct 2021 01:16:29 +0000 Subject: [PATCH 0647/1195] Publish 6.4.2 SHA256 hashes: ipykernel-6.4.2-py3-none-any.whl: 5657a0ab3d9a9eba3eb78b261771d22158388f182e85668c536c1a9932b80e03 ipykernel-6.4.2.tar.gz: 0140f78bfd60e47e387b6433b4bed0f228986420dc4d5fac0e251c9711e23e29 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index d2a860062..390f81965 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.4.1' +__version__ = '6.4.2' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index 7e8919d8f..31a313c59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.4.1" +current = "6.4.2" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 0d139822a23d4b997fea5372dc702537a6b37c43 Mon Sep 17 00:00:00 2001 From: Alexander Stukowski Date: Sat, 23 Oct 2021 17:37:25 +0200 Subject: [PATCH 0648/1195] Do not call setQuitOnLastWindowClosed() on a QCoreApplication --- ipykernel/eventloops.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 1f7f47f3e..e2c8e9040 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -111,9 +111,11 @@ def loop_qt4(kernel): """Start a kernel with PyQt4 event loop integration.""" from IPython.lib.guisupport import get_app_qt4 + from IPython.external.qt_for_kernel import QtGui kernel.app = get_app_qt4([" "]) - kernel.app.setQuitOnLastWindowClosed(False) + if isinstance(kernel.app, QtGui.QApplication): + kernel.app.setQuitOnLastWindowClosed(False) _notify_stream_qt(kernel, kernel.shell_stream) _loop_qt(kernel.app) From 826cd562223273c8008367b3dbf8b5d48ec55a73 Mon Sep 17 00:00:00 2001 From: Aleksei Stepanov Date: Mon, 25 Oct 2021 17:01:28 +0200 Subject: [PATCH 0649/1195] Drop ipython_genutils requirement * use `from traitlets.utils import filefind` * `ensure_dir_exists` -> `os.makedirs(..., exist_ok=True)` * `buffer_to_bytes` - use in-place copy * `TemporaryDirectory` - use from `tempfile` * `TemporaryWorkingDirectory` - make private copy * `str_to_bytes` - use `str.encode("utf-8")` for test cases --- ipykernel/connect.py | 3 +-- ipykernel/kernelapp.py | 4 ++-- ipykernel/pickleutil.py | 15 +++++++++++---- ipykernel/tests/test_connect.py | 26 +++++++++++++++++--------- ipykernel/tests/test_embed_kernel.py | 6 ++---- ipykernel/tests/test_kernel.py | 2 +- ipykernel/tests/utils.py | 21 +++++++++++++++++++++ setup.py | 1 - 8 files changed, 55 insertions(+), 23 deletions(-) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 91646b6c8..73ff22b3f 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -7,7 +7,6 @@ import sys from subprocess import Popen, PIPE - import jupyter_client from jupyter_client import write_connection_file @@ -21,7 +20,7 @@ def get_connection_file(app=None): app : IPKernelApp instance [optional] If unspecified, the currently running app will be used """ - from ipython_genutils.path import filefind + from traitlets.utils import filefind if app is None: from ipykernel.kernelapp import IPKernelApp if not IPKernelApp.initialized(): diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 8ad42d992..46366c6f4 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -26,11 +26,11 @@ from IPython.core.shellapp import ( InteractiveShellApp, shell_flags, shell_aliases ) -from ipython_genutils.path import filefind, ensure_dir_exists from traitlets import ( Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type, default ) from traitlets.utils.importstring import import_item +from traitlets.utils import filefind from jupyter_core.paths import jupyter_runtime_dir from jupyter_client import write_connection_file from jupyter_client.connect import ConnectionFileMixin @@ -260,7 +260,7 @@ def init_connection_file(self): except IOError: self.log.debug("Connection file not found: %s", self.connection_file) # This means I own it, and I'll create it in this directory: - ensure_dir_exists(os.path.dirname(self.abs_connection_file), 0o700) + os.makedirs(os.path.dirname(self.abs_connection_file), mode=0o700, exist_ok=True) # Also, I will clean it up: atexit.register(self.cleanup_connection_file) return diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 2343a6258..f91545840 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -2,8 +2,9 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - +import typing import warnings + warnings.warn("ipykernel.pickleutil is deprecated. It has moved to ipyparallel.", DeprecationWarning, stacklevel=2 @@ -15,7 +16,6 @@ from types import FunctionType from traitlets.utils.importstring import import_item -from ipython_genutils.py3compat import buffer_to_bytes # This registers a hook when it's imported from ipyparallel.serialize import codeutil # noqa F401 @@ -279,11 +279,18 @@ def get_object(self, g=None): class CannedBytes(CannedObject): - wrap = staticmethod(buffer_to_bytes) + @staticmethod + def wrap(buf: typing.Union[memoryview, bytes, typing.SupportsBytes]) -> bytes: + """Cast a buffer or memoryview object to bytes""" + if isinstance(buf, memoryview): + return buf.tobytes() + if not isinstance(buf, bytes): + return bytes(buf) + return buf def __init__(self, obj): self.buffers = [obj] - + def get_object(self, g=None): data = self.buffers[0] return self.wrap(data) diff --git a/ipykernel/tests/test_connect.py b/ipykernel/tests/test_connect.py index 0eee4282b..16c220785 100644 --- a/ipykernel/tests/test_connect.py +++ b/ipykernel/tests/test_connect.py @@ -6,22 +6,30 @@ import errno import json import os +from tempfile import TemporaryDirectory from unittest.mock import patch import pytest import zmq from traitlets.config import Config -from ipython_genutils.tempdir import TemporaryDirectory, TemporaryWorkingDirectory -from ipython_genutils.py3compat import str_to_bytes from ipykernel import connect from ipykernel.kernelapp import IPKernelApp +from .utils import TemporaryWorkingDirectory -sample_info = dict(ip='1.2.3.4', transport='ipc', - shell_port=1, hb_port=2, iopub_port=3, stdin_port=4, control_port=5, - key=b'abc123', signature_scheme='hmac-md5', - ) + +sample_info = { + 'ip': '1.2.3.4', + 'transport': 'ipc', + 'shell_port': 1, + 'hb_port': 2, + 'iopub_port': 3, + 'stdin_port': 4, + 'control_port': 5, + 'key': b'abc123', + 'signature_scheme': 'hmac-md5', +} class DummyKernelApp(IPKernelApp): @@ -60,12 +68,12 @@ def test_get_connection_info(): info = connect.get_connection_info(cf, unpack=True) assert isinstance(json_info, str) - sub_info = {k:v for k,v in info.items() if k in sample_info} + sub_info = {k: v for k, v in info.items() if k in sample_info} assert sub_info == sample_info info2 = json.loads(json_info) - info2['key'] = str_to_bytes(info2['key']) - sub_info2 = {k:v for k,v in info.items() if k in sample_info} + info2['key'] = info2['key'].encode("utf-8") + sub_info2 = {k: v for k, v in info.items() if k in sample_info} assert sub_info2 == sample_info diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index d8df921aa..b7fa53cb9 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -14,7 +14,6 @@ from jupyter_client import BlockingKernelClient from jupyter_core import paths -from ipython_genutils import py3compat SETUP_TIMEOUT = 60 @@ -41,7 +40,7 @@ def connection_file_ready(connection_file): except ValueError: return False - kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE) + kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE, encoding="utf-8") try: connection_file = os.path.join( paths.jupyter_runtime_dir(), @@ -58,8 +57,7 @@ def connection_file_ready(connection_file): time.sleep(0.1) if kernel.poll() is not None: - o,e = kernel.communicate() - e = py3compat.cast_unicode(e) + o, e = kernel.communicate() raise IOError("Kernel failed to start:\n%s" % e) if not os.path.exists(connection_file): diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 0c24db4f5..288296680 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -8,6 +8,7 @@ import os.path import sys import time +from tempfile import TemporaryDirectory from flaky import flaky import pytest @@ -16,7 +17,6 @@ from IPython.testing import decorators as dec, tools as tt import IPython from IPython.paths import locate_profile -from ipython_genutils.tempdir import TemporaryDirectory from .utils import ( new_kernel, kernel, TIMEOUT, assemble_output, execute, diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 166f52d45..6319c2144 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -4,7 +4,9 @@ # Distributed under the terms of the Modified BSD License. import atexit +import os import sys +from tempfile import TemporaryDirectory from time import time from contextlib import contextmanager @@ -180,3 +182,22 @@ def wait_for_idle(kc): content = msg['content'] if msg_type == 'status' and content['execution_state'] == 'idle': break + + +class TemporaryWorkingDirectory(TemporaryDirectory): + """ + Creates a temporary directory and sets the cwd to that directory. + Automatically reverts to previous cwd upon cleanup. + Usage example: + + with TemporaryWorkingDirectory() as tmpdir: + ... + """ + def __enter__(self): + self.old_wd = os.getcwd() + os.chdir(self.name) + return super().__enter__() + + def __exit__(self, exc, value, tb): + os.chdir(self.old_wd) + return super().__exit__(exc, value, tb) diff --git a/setup.py b/setup.py index 1cb41b4a4..fd1c76d9a 100644 --- a/setup.py +++ b/setup.py @@ -62,7 +62,6 @@ def run(self): keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], python_requires='>=3.7', install_requires=[ - "ipython_genutils", 'importlib-metadata<5;python_version<"3.8.0"', 'argcomplete>=1.12.3;python_version<"3.8.0"', 'debugpy>=1.0.0,<2.0', From d4c031d815675975ac79dd74b8395f893002aac4 Mon Sep 17 00:00:00 2001 From: Aleksei Stepanov Date: Mon, 25 Oct 2021 19:52:56 +0200 Subject: [PATCH 0650/1195] traitlets 5.1.0 is minimal supported version --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index fd1c76d9a..26203492d 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,6 @@ # Distributed under the terms of the Modified BSD License. import sys -import re from glob import glob import os import shutil @@ -66,7 +65,7 @@ def run(self): 'argcomplete>=1.12.3;python_version<"3.8.0"', 'debugpy>=1.0.0,<2.0', 'ipython>=7.23.1,<8.0', - 'traitlets>=4.1.0,<6.0', + 'traitlets>=5.1.0,<6.0', 'jupyter_client<8.0', 'tornado>=4.2,<7.0', 'matplotlib-inline>=0.1.0,<0.2.0', From 8b98f3ebdbe6081c28d051f0823bf26978aae3a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Fri, 29 Oct 2021 16:33:14 +0200 Subject: [PATCH 0651/1195] Add rich inspection tests --- ipykernel/tests/test_debugger.py | 199 +++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 ipykernel/tests/test_debugger.py diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py new file mode 100644 index 000000000..39830aad2 --- /dev/null +++ b/ipykernel/tests/test_debugger.py @@ -0,0 +1,199 @@ +import pytest + +from .utils import execute, new_kernel, get_reply + +seq = 0 + + +def wait_for_debug_request(kernel, command, arguments=None): + """Carry out a debug request and return the reply content. + + It does not check if the request was successful. + """ + global seq + seq += 1 + + msg = kernel.session.msg( + "debug_request", + { + "type": "request", + "seq": seq, + "command": command, + "arguments": arguments or {}, + }, + ) + kernel.control_channel.send(msg) + reply = get_reply(kernel, msg["header"]["msg_id"], channel="control") + return reply["content"] + + +@pytest.fixture +def kernel(): + with new_kernel() as kc: + yield kc + + +@pytest.fixture +def kernel_with_debug(kernel): + # Initialize + wait_for_debug_request( + kernel, + "initialize", + { + "clientID": "test-client", + "clientName": "testClient", + "adapterID": "", + "pathFormat": "path", + "linesStartAt1": True, + "columnsStartAt1": True, + "supportsVariableType": True, + "supportsVariablePaging": True, + "supportsRunInTerminalRequest": True, + "locale": "en", + }, + ) + + # Attach + wait_for_debug_request(kernel, "attach") + + try: + yield kernel + finally: + # Detach + wait_for_debug_request( + kernel, "disconnect", {"restart": False, "terminateDebuggee": True} + ) + + +def test_debug_initialize(kernel): + reply = wait_for_debug_request( + kernel, + "initialize", + { + "clientID": "test-client", + "clientName": "testClient", + "adapterID": "", + "pathFormat": "path", + "linesStartAt1": True, + "columnsStartAt1": True, + "supportsVariableType": True, + "supportsVariablePaging": True, + "supportsRunInTerminalRequest": True, + "locale": "en", + }, + ) + assert reply["success"] + + +def test_attach_debug(kernel_with_debug): + reply = wait_for_debug_request( + kernel_with_debug, "evaluate", {"expression": "'a' + 'b'", "context": "repl"} + ) + assert reply["success"] + assert reply["body"]["result"] == "" + + +def test_set_breakpoints(kernel_with_debug): + code = """def f(a, b): + c = a + b + return c + +f(2, 3)""" + + r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) + source = r["body"]["sourcePath"] + + reply = wait_for_debug_request( + kernel_with_debug, + "setBreakpoints", + { + "breakpoints": [{"line": 2}], + "source": {"path": source}, + "sourceModified": False, + }, + ) + assert reply["success"] + assert len(reply["body"]["breakpoints"]) == 1 + assert reply["body"]["breakpoints"][0]["verified"] + assert reply["body"]["breakpoints"][0]["source"]["path"] == source + + r = wait_for_debug_request(kernel_with_debug, "debugInfo") + assert source in map(lambda b: b["source"], r["body"]["breakpoints"]) + + r = wait_for_debug_request(kernel_with_debug, "configurationDone") + assert r["success"] + + +def test_rich_inspect_not_at_breakpoint(kernel_with_debug): + var_name = "text" + value = "Hello the world" + code = """{0}='{1}' +print({0}) +""".format(var_name, value) + + msg_id = kernel_with_debug.execute(code) + get_reply(kernel_with_debug, msg_id) + + r = wait_for_debug_request(kernel_with_debug, "inspectVariables") + assert var_name in list(map(lambda v: v["name"], r["body"]["variables"])) + + reply = wait_for_debug_request( + kernel_with_debug, + "richInspectVariables", + {"variableName": var_name}, + ) + + assert reply["body"]["data"] == {"text/plain": "'{}'".format(value)} + + +def test_rich_inspect_at_breakpoint(kernel_with_debug): + code = """def f(a, b): + c = a + b + return c + +f(2, 3)""" + + r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) + source = r["body"]["sourcePath"] + + wait_for_debug_request( + kernel_with_debug, + "setBreakpoints", + { + "breakpoints": [{"line": 2}], + "source": {"path": source}, + "sourceModified": False, + }, + ) + + r = wait_for_debug_request(kernel_with_debug, "debugInfo") + + r = wait_for_debug_request(kernel_with_debug, "configurationDone") + + kernel_with_debug.execute(code) + + stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})[ + "body" + ]["stackFrames"] + + scopes = wait_for_debug_request( + kernel_with_debug, "scopes", {"frameId": stacks[0]["id"]} + )["body"]["scopes"] + + locals_ = wait_for_debug_request( + kernel_with_debug, + "variables", + { + "variablesReference": next(filter(lambda s: s["name"] == "Locals", scopes))[ + "variablesReference" + ] + }, + )["body"]["variables"] + + reply = wait_for_debug_request( + kernel_with_debug, + "richInspectVariables", + {"variableName": locals_[0]["name"], "frameId": stacks[0]["id"]}, + ) + + assert reply["body"]["data"] == {"text/plain": locals_[0]["value"]} From 9cd3afad9e79efb83c2a2e57c91754a99aabeaad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Fri, 29 Oct 2021 16:43:58 +0200 Subject: [PATCH 0652/1195] Skip debug tests if debugpy is not available --- ipykernel/tests/test_debugger.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index 39830aad2..71a2302e3 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -1,9 +1,12 @@ import pytest -from .utils import execute, new_kernel, get_reply +from .utils import new_kernel, get_reply seq = 0 +# Skip if debugpy is not available +pytest.importorskip("debugpy") + def wait_for_debug_request(kernel, command, arguments=None): """Carry out a debug request and return the reply content. From e41c242c7a170388f09f1da17df9c7305b881d18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Fri, 29 Oct 2021 16:44:19 +0200 Subject: [PATCH 0653/1195] Omit test files from coverage report --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 000000000..6b3faf553 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,2 @@ +[run] +omit = ipykernel/tests/* From 73efd890b64145b0d28ea011df58e0745270458b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Fri, 29 Oct 2021 16:45:47 +0200 Subject: [PATCH 0654/1195] Fix rich inspection Fixes Rich variable inspection vs `user_expressions` #772 --- ipykernel/debugger.py | 77 ++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 45 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 3d895f350..5fc760a10 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -511,66 +511,53 @@ async def inspectVariables(self, message): async def richInspectVariables(self, message): reply = { - 'type': 'response', - 'sequence_seq': message['seq'], - 'success': False, - 'command': message['command'] + "type": "response", + "sequence_seq": message["seq"], + "success": False, + "command": message["command"], } - - var_name = message['arguments']['variableName'] + + var_name = message["arguments"]["variableName"] valid_name = str.isidentifier(var_name) if not valid_name: - reply['body'] = { - 'data': {}, - 'metadata': {} - } - if var_name == 'special variables' or var_name == 'function variables': - reply['success'] = True + reply["body"] = {"data": {}, "metadata": {}} + if var_name == "special variables" or var_name == "function variables": + reply["success"] = True return reply - var_repr_data = var_name + '_repr_data' - var_repr_metadata = var_name + '_repr_metadata' - - if not self.breakpoint_list: + repr_data = {} + repr_metadata = {} + if not self.stopped_threads: # The code did not hit a breakpoint, we use the intepreter # to get the rich representation of the variable - var_repr_data, var_repr_metadata = get_ipython().display_formatter.format(var_name) + result = get_ipython().user_expressions({var_name: var_name})[var_name] + if result.get("status", "error") == "ok": + repr_data = result.get("data", {}) + repr_metadata = result.get("metadata", {}) else: # The code has stopped on a breakpoint, we use the setExpression # request to get the rich representation of the variable - lvalue = var_repr_data + ',' + var_repr_metadata - code = 'get_ipython().display_formatter.format(' + var_name+')' - frame_id = message['arguments']['frameId'] - seq = message['seq'] - request = { - 'type': 'request', - 'command': 'setExpression', - 'seq': seq+1, - 'arguments': { - 'expression': lvalue, - 'value': code, - 'frameId': frame_id + code = "get_ipython().display_formatter.format(" + var_name + ")" + frame_id = message["arguments"]["frameId"] + seq = message["seq"] + reply = await self._forward_message( + { + "type": "request", + "command": "evaluate", + "seq": seq + 1, + "arguments": {"expression": code, "frameId": frame_id}, } - } - await self._forward_message(request) + ) + if reply["success"]: + repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) - repr_data = globals()[var_repr_data] - repr_metadata = globals()[var_repr_metadata] body = { - 'data': {}, - 'metadata': {} + "data": repr_data, + "metadata": {k: v for k, v in repr_metadata.items() if k in repr_data}, } - for key, value in repr_data.items(): - body['data']['key'] = value - if key in repr_metadata: - body['metadata'][key] = repr_metadata[key] - - globals().pop(var_repr_data) - globals().pop(var_repr_metadata) - - reply['body'] = body - reply['success'] = True + reply["body"] = body + reply["success"] = True return reply async def process_request(self, message): From 3734ec6267501529311846b5a3594239eabccd5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Fri, 29 Oct 2021 17:06:13 +0200 Subject: [PATCH 0655/1195] Exclude coverage option --- MANIFEST.in | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MANIFEST.in b/MANIFEST.in index 9f961f4ec..155a5e4c4 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -23,3 +23,5 @@ global-exclude .ipynb_checkpoints prune data_kernelspec exclude .mailmap exclude readthedocs.yml + +exclude .coveragerc From 7c6ef62524d832de1d567c821c544683cbcf0f81 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 16:31:06 +0000 Subject: [PATCH 0656/1195] Automated Changelog Entry for 6.5.0 on master --- CHANGELOG.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8de4111cd..5c9b497c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ +## 6.5.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.4.2...e8d4f66e0f65e284aab444c53e9812dbbc814cb2)) + +### Bugs fixed + +- Fix rich variables inspection [#793](https://github.com/ipython/ipykernel/pull/793) ([@fcollonval](https://github.com/fcollonval)) +- Do not call setQuitOnLastWindowClosed() on a QCoreApplication [#791](https://github.com/ipython/ipykernel/pull/791) ([@stukowski](https://github.com/stukowski)) + +### Maintenance and upkeep improvements + +- Drop ipython_genutils requirement [#792](https://github.com/ipython/ipykernel/pull/792) ([@penguinolog](https://github.com/penguinolog)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2021-10-20&to=2021-11-01&type=c)) + +[@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2021-10-20..2021-11-01&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afcollonval+updated%3A2021-10-20..2021-11-01&type=Issues) | [@penguinolog](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apenguinolog+updated%3A2021-10-20..2021-11-01&type=Issues) | [@stukowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Astukowski+updated%3A2021-10-20..2021-11-01&type=Issues) + + + ## 6.4.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.4.1...231fd3c65f8a15e9e015546c0a6846e22df9ba2a)) @@ -26,8 +47,6 @@ [@emuccino](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aemuccino+updated%3A2021-09-10..2021-10-19&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2021-09-10..2021-10-19&type=Issues) | [@rayosborn](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Arayosborn+updated%3A2021-09-10..2021-10-19&type=Issues) - - ## 6.4.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.4.0...4da7623c1ae733f32c0792d70e7af283a7b19d22)) From 8fbc7728271cb0e1a44d63001300efbf3b9e06de Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 1 Nov 2021 11:32:47 -0500 Subject: [PATCH 0657/1195] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c9b497c9..db4e0cf76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,11 @@ ### Bugs fixed - Fix rich variables inspection [#793](https://github.com/ipython/ipykernel/pull/793) ([@fcollonval](https://github.com/fcollonval)) -- Do not call setQuitOnLastWindowClosed() on a QCoreApplication [#791](https://github.com/ipython/ipykernel/pull/791) ([@stukowski](https://github.com/stukowski)) +- Do not call `setQuitOnLastWindowClosed()` on a `QCoreApplication` [#791](https://github.com/ipython/ipykernel/pull/791) ([@stukowski](https://github.com/stukowski)) ### Maintenance and upkeep improvements -- Drop ipython_genutils requirement [#792](https://github.com/ipython/ipykernel/pull/792) ([@penguinolog](https://github.com/penguinolog)) +- Drop `ipython_genutils` requirement [#792](https://github.com/ipython/ipykernel/pull/792) ([@penguinolog](https://github.com/penguinolog)) ### Contributors to this release From 360685c67c71cd33691c72c1e9e88d23a88f4021 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 1 Nov 2021 16:41:27 +0000 Subject: [PATCH 0658/1195] Publish 6.5.0 SHA256 hashes: ipykernel-6.5.0-py3-none-any.whl: f43de132feea90f86d68c51013afe9694f9415f440053ec9909dd656c75b04b5 ipykernel-6.5.0.tar.gz: 299795cca2c4aed7e233e3ad5360e1c73627fd0dcec11a9e75d5b2df43629353 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 390f81965..14f5030bd 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.4.2' +__version__ = '6.5.0' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index 31a313c59..864ed6cf4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.4.2" +current = "6.5.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 587f45cdf7bb4eaa1b289dc875d67236168d9540 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 8 Nov 2021 08:37:02 -0800 Subject: [PATCH 0659/1195] Unpin IPython, and remove some dependencies on it. This makes it annoying to test whether IPython breaks IPykernel. I'm almost always on IPython master branch and I'd like to know when/if I break things. Prompted by https://github.com/ipython/ipython/pull/13252, which is not breaking ipykernel, but it's better to decouple where we can. --- ipykernel/inprocess/tests/test_kernel.py | 4 ++-- ipykernel/tests/test_kernel.py | 6 +++--- setup.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index c2904a006..952c66905 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -13,10 +13,10 @@ from ipykernel.inprocess.manager import InProcessKernelManager from ipykernel.inprocess.ipkernel import InProcessKernel from ipykernel.tests.utils import assemble_output -from IPython.testing.decorators import skipif_not_matplotlib from IPython.utils.io import capture_output + def _init_asyncio_patch(): """set default asyncio policy to be compatible with tornado @@ -60,9 +60,9 @@ def setUp(self): self.kc.start_channels() self.kc.wait_for_ready() - @skipif_not_matplotlib def test_pylab(self): """Does %pylab work in the in-process kernel?""" + matplotlib = pytest.importorskip('matplotlib', reason='This test requires matplotlib') kc = self.kc kc.execute('%pylab') out, err = assemble_output(kc.get_iopub_msg) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 288296680..5b9b26682 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -14,7 +14,7 @@ import pytest from packaging import version -from IPython.testing import decorators as dec, tools as tt +from IPython.testing import tools as tt import IPython from IPython.paths import locate_profile @@ -225,8 +225,8 @@ def test_save_history(): assert 'b="abcþ"' in content -@dec.skip_without('faulthandler') def test_smoke_faulthandler(): + faulthadler = pytest.importorskip('faulthandler', reason='this test needs faulthandler') with kernel() as kc: # Note: faulthandler.register is not available on windows. code = '\n'.join([ @@ -296,8 +296,8 @@ def test_complete(): assert completed.startswith(cell) -@dec.skip_without('matplotlib') def test_matplotlib_inline_on_import(): + pytest.importorskip('matplotlib', reason='this test requires matplotlib') with kernel() as kc: cell = '\n'.join([ 'import matplotlib, matplotlib.pyplot as plt', diff --git a/setup.py b/setup.py index 26203492d..db5830a96 100644 --- a/setup.py +++ b/setup.py @@ -64,7 +64,7 @@ def run(self): 'importlib-metadata<5;python_version<"3.8.0"', 'argcomplete>=1.12.3;python_version<"3.8.0"', 'debugpy>=1.0.0,<2.0', - 'ipython>=7.23.1,<8.0', + 'ipython>=7.23.1', 'traitlets>=5.1.0,<6.0', 'jupyter_client<8.0', 'tornado>=4.2,<7.0', From fdde382950744ad0708eeacc762e271d11603a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=B4=A2=E6=AF=85?= <944102712@qq.com> Date: Wed, 17 Nov 2021 23:36:35 +0800 Subject: [PATCH 0660/1195] fix #800 the temp file name generated by murmur2 in python and ts is not the same --- ipykernel/compiler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index a50dc5735..2dbf6fb5d 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -5,6 +5,7 @@ def murmur2_x86(data, seed): m = 0x5bd1e995 + data = [chr(d) for d in str.encode(data, "utf8")] length = len(data) h = seed ^ length rounded_end = (length & 0xfffffffc) From 29e6e35aaf7518ee4b3e529aa3b6dd07ab049f7b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 18 Nov 2021 09:54:15 -0600 Subject: [PATCH 0661/1195] Enforce labels on PRs --- .github/workflows/enforce-label.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/workflows/enforce-label.yml diff --git a/.github/workflows/enforce-label.yml b/.github/workflows/enforce-label.yml new file mode 100644 index 000000000..354a0468d --- /dev/null +++ b/.github/workflows/enforce-label.yml @@ -0,0 +1,11 @@ +name: Enforce PR label + +on: + pull_request: + types: [labeled, unlabeled, opened, edited, synchronize] +jobs: + enforce-label: + runs-on: ubuntu-latest + steps: + - name: enforce-triage-label + uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 From e8700405983210d1cad836c9e2f39bbcf93772e5 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Nov 2021 22:30:42 +0000 Subject: [PATCH 0662/1195] Automated Changelog Entry for 6.5.1 on master --- CHANGELOG.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index db4e0cf76..e236de1d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ +## 6.5.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.5.0...1ef2017781435d54348fbb170b8c5d096e3e1351)) + +### Bugs fixed + +- fix #800 the temp file name generated by murmur2 in python and ts is not same [#801](https://github.com/ipython/ipykernel/pull/801) ([@eastonsuo](https://github.com/eastonsuo)) + +### Maintenance and upkeep improvements + +- Enforce labels on PRs [#803](https://github.com/ipython/ipykernel/pull/803) ([@blink1073](https://github.com/blink1073)) +- Unpin IPython, and remove some dependencies on it. [#796](https://github.com/ipython/ipykernel/pull/796) ([@Carreau](https://github.com/Carreau)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2021-11-01&to=2021-11-18&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2021-11-01..2021-11-18&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2021-11-01..2021-11-18&type=Issues) | [@eastonsuo](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aeastonsuo+updated%3A2021-11-01..2021-11-18&type=Issues) + + + ## 6.5.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.4.2...e8d4f66e0f65e284aab444c53e9812dbbc814cb2)) @@ -23,8 +44,6 @@ [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2021-10-20..2021-11-01&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afcollonval+updated%3A2021-10-20..2021-11-01&type=Issues) | [@penguinolog](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apenguinolog+updated%3A2021-10-20..2021-11-01&type=Issues) | [@stukowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Astukowski+updated%3A2021-10-20..2021-11-01&type=Issues) - - ## 6.4.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.4.1...231fd3c65f8a15e9e015546c0a6846e22df9ba2a)) From a099c1f0e710b22c0167d01d0043899431447b7f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 18 Nov 2021 16:35:04 -0600 Subject: [PATCH 0663/1195] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e236de1d6..e97e4a837 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,12 @@ ### Bugs fixed -- fix #800 the temp file name generated by murmur2 in python and ts is not same [#801](https://github.com/ipython/ipykernel/pull/801) ([@eastonsuo](https://github.com/eastonsuo)) +- Fix the temp file name created by the debugger [#801](https://github.com/ipython/ipykernel/pull/801) ([@eastonsuo](https://github.com/eastonsuo)) ### Maintenance and upkeep improvements - Enforce labels on PRs [#803](https://github.com/ipython/ipykernel/pull/803) ([@blink1073](https://github.com/blink1073)) -- Unpin IPython, and remove some dependencies on it. [#796](https://github.com/ipython/ipykernel/pull/796) ([@Carreau](https://github.com/Carreau)) +- Unpin `IPython`, and remove some dependencies on it. [#796](https://github.com/ipython/ipykernel/pull/796) ([@Carreau](https://github.com/Carreau)) ### Contributors to this release From 3997621ede84607a34994c2065ece632ab3783ba Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 Nov 2021 22:41:50 +0000 Subject: [PATCH 0664/1195] Publish 6.5.1 SHA256 hashes: ipykernel-6.5.1-py3-none-any.whl: ff0cb4a67326d2f903b7d7a2e63719d082434b46f00536410bd4e3ad2b98f3b7 ipykernel-6.5.1.tar.gz: dd27172bccbbcfef952991e49372e4c6fd1c14eed0df05ebd5b4335cb27a81a2 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 14f5030bd..79aee278b 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.5.0' +__version__ = '6.5.1' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index 864ed6cf4..d9a5c50d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.5.0" +current = "6.5.1" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 9c2513215f3a4299fed514b84923830ed9fd8508 Mon Sep 17 00:00:00 2001 From: David Lukes Date: Mon, 22 Nov 2021 22:28:15 +0100 Subject: [PATCH 0665/1195] Add explicit encoding to open calls --- ipykernel/debugger.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 5fc760a10..e3da33ca9 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -351,7 +351,7 @@ async def dumpCell(self, message): code = message['arguments']['code'] file_name = get_file_name(code) - with open(file_name, 'w') as f: + with open(file_name, 'w', encoding='utf-8') as f: f.write(code) reply = { @@ -378,7 +378,7 @@ async def source(self, message): } source_path = message["arguments"]["source"]["path"] if os.path.isfile(source_path): - with open(source_path) as f: + with open(source_path, encoding='utf-8') as f: reply['success'] = True reply['body'] = { 'content': f.read() From 9a5544e3c74f7980fd441e6b8e52dd981f55deda Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Tue, 23 Nov 2021 19:09:15 +0300 Subject: [PATCH 0666/1195] Remove Nose dependency --- ipykernel/tests/test_kernel.py | 9 +++++++-- ipykernel/tests/utils.py | 8 ++++---- setup.py | 1 - 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 5b9b26682..ec2db9edf 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -14,7 +14,6 @@ import pytest from packaging import version -from IPython.testing import tools as tt import IPython from IPython.paths import locate_profile @@ -242,7 +241,13 @@ def test_smoke_faulthandler(): def test_help_output(): """ipython kernel --help-all works""" - tt.help_all_output_test('kernel') + from IPython.utils.process import get_output_error_code + cmd = [sys.executable, "-m", "IPython", "kernel", "--help-all"] + out, err, rc = get_output_error_code(cmd) + assert rc == 0, err + assert "Traceback" not in err + assert "Options" in out + assert "Class" in out def test_is_complete(): diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 6319c2144..daeccf64d 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -13,8 +13,6 @@ from queue import Empty from subprocess import STDOUT -import nose - from jupyter_client import manager @@ -32,8 +30,9 @@ def start_new_kernel(**kwargs): """ kwargs['stderr'] = STDOUT try: + import nose kwargs['stdout'] = nose.iptest_stdstreams_fileno() - except AttributeError: + except (ImportError, AttributeError): pass return manager.start_new_kernel(startup_timeout=STARTUP_TIMEOUT, **kwargs) @@ -145,8 +144,9 @@ def new_kernel(argv=None): """ kwargs = {'stderr': STDOUT} try: + import nose kwargs['stdout'] = nose.iptest_stdstreams_fileno() - except AttributeError: + except (ImportError, AttributeError): pass if argv is not None: kwargs['extra_arguments'] = argv diff --git a/setup.py b/setup.py index db5830a96..fc2828660 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,6 @@ def run(self): "pytest !=5.3.4", "pytest-cov", "flaky", - "nose", # nose because we are still using nose streams from ipython "ipyparallel", ], }, From 6a727a48678a908bd3ddfb5aaab3463fb70c43f6 Mon Sep 17 00:00:00 2001 From: Nikita Kniazev Date: Tue, 23 Nov 2021 19:53:14 +0300 Subject: [PATCH 0667/1195] Replace IPython.utils.process.get_output_error_code with subprocess.run --- ipykernel/tests/test_kernel.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index ec2db9edf..e6af1e2d2 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -6,6 +6,7 @@ import ast import io import os.path +import subprocess import sys import time from tempfile import TemporaryDirectory @@ -241,13 +242,12 @@ def test_smoke_faulthandler(): def test_help_output(): """ipython kernel --help-all works""" - from IPython.utils.process import get_output_error_code cmd = [sys.executable, "-m", "IPython", "kernel", "--help-all"] - out, err, rc = get_output_error_code(cmd) - assert rc == 0, err - assert "Traceback" not in err - assert "Options" in out - assert "Class" in out + proc = subprocess.run(cmd, timeout=30, capture_output=True) + assert proc.returncode == 0, proc.stderr + assert b"Traceback" not in proc.stderr + assert b"Options" in proc.stdout + assert b"Class" in proc.stdout def test_is_complete(): From 77cdd28096d56b2c0e8cbc5de6237ae9849906d9 Mon Sep 17 00:00:00 2001 From: Ahmed Fasih Date: Thu, 18 Nov 2021 16:25:52 -0800 Subject: [PATCH 0668/1195] Send omit_sections to IPython if available This leverages a recent pull request in IPython: https://github.com/ipython/ipython/pull/13343 --- ipykernel/ipkernel.py | 5 +++-- ipykernel/kernelbase.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 2cc8eab54..3a9817874 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -459,7 +459,7 @@ def _experimental_do_complete(self, code, cursor_pos): 'metadata': {_EXPERIMENTAL_KEY_NAME: comps}, 'status': 'ok'} - def do_inspect(self, code, cursor_pos, detail_level=0): + def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): name = token_at_cursor(code, cursor_pos) reply_content = {'status' : 'ok'} @@ -469,7 +469,8 @@ def do_inspect(self, code, cursor_pos, detail_level=0): reply_content['data'].update( self.shell.object_inspect_mime( name, - detail_level=detail_level + detail_level=detail_level, + omit_sections=omit_sections, ) ) if not self.shell.enable_html_pager: diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index bfd107127..a0a7c98e2 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -702,6 +702,7 @@ async def inspect_request(self, stream, ident, parent): reply_content = self.do_inspect( content['code'], content['cursor_pos'], content.get('detail_level', 0), + set(content.get('omit_sections', [])), ) if inspect.isawaitable(reply_content): reply_content = await reply_content @@ -712,7 +713,7 @@ async def inspect_request(self, stream, ident, parent): reply_content, parent, ident) self.log.debug("%s", msg) - def do_inspect(self, code, cursor_pos, detail_level=0): + def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): """Override in subclasses to allow introspection. """ return {'status': 'ok', 'data': {}, 'metadata': {}, 'found': False} From 0bdfd0bf9ae8e1dd61802ac90f16a9d8d4ed74af Mon Sep 17 00:00:00 2001 From: Ahmed Fasih Date: Mon, 29 Nov 2021 10:24:46 -0800 Subject: [PATCH 0669/1195] Make backwards-compatible There are two options suggested in https://github.com/ipython/ipykernel/pull/809: 1. use TypeError to catch non-existent omit_sections kw. This will work with master but will be less clear when we deprecate IPython 7.x in a few years. 2. use IPython version_info, which makes cleanup easier but means we won't be able to use this feature with master IPython, only v8. Going with #2 because it's cleaner. --- ipykernel/ipkernel.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 3a9817874..2c61cf4b2 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -5,6 +5,7 @@ from contextlib import contextmanager from functools import partial import getpass +import re import signal import sys @@ -466,13 +467,20 @@ def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): reply_content['data'] = {} reply_content['metadata'] = {} try: - reply_content['data'].update( - self.shell.object_inspect_mime( + if release.version_info >= (8,): + # `omit_sections` keyword will be available in IPython 8, see + # https://github.com/ipython/ipython/pull/13343 + bundle = self.shell.object_inspect_mime( name, detail_level=detail_level, omit_sections=omit_sections, ) - ) + else: + bundle = self.shell.object_inspect_mime( + name, + detail_level=detail_level + ) + reply_content['data'].update(bundle) if not self.shell.enable_html_pager: reply_content['data'].pop('text/html') reply_content['found'] = True From ff8d5ea378b522051063f56194179db2cee55b11 Mon Sep 17 00:00:00 2001 From: Ahmed Fasih Date: Mon, 29 Nov 2021 10:30:08 -0800 Subject: [PATCH 0670/1195] clean up accidental import added by editor --- ipykernel/ipkernel.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 2c61cf4b2..bb50c04cc 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -5,7 +5,6 @@ from contextlib import contextmanager from functools import partial import getpass -import re import signal import sys From d3fc735cd04b86c64499b034b30934f226669047 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 30 Nov 2021 11:11:43 +0100 Subject: [PATCH 0671/1195] set debugOptions for breakpoints in python standard library source --- ipykernel/debugger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index e3da33ca9..0828509c0 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -462,6 +462,8 @@ async def attach(self, message): 'port': port } message['arguments']['logToFile'] = True + # Set debugOptions for breakpoints in python standard library source. + message['arguments']['debugOptions'] = [ 'DebugStdLib' ] return await self._forward_message(message) async def configurationDone(self, message): From eb3057de1b919cbf024fed9b3b43347d374311f9 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 30 Nov 2021 18:41:34 -0600 Subject: [PATCH 0672/1195] Test jupyter_kernel_test as downstream --- .github/workflows/downstream.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 28d5059b0..5e95108a1 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -42,3 +42,9 @@ jobs: - name: Test jupyter_client if: ${{ always() }} run: pytest --pyargs jupyter_client + - name: Test jupyter_kernel_test + run: + git clone git@github.com:jupyter/jupyter_kernel_test.git + cd jupyter_kernel_test + pip install -e ".[test]" + python test_ipykernel.py From a7d569c904446f3632cb009cfee489c44c7a18e5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 30 Nov 2021 18:43:44 -0600 Subject: [PATCH 0673/1195] Update downstream.yml --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 5e95108a1..cb89f3484 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -43,7 +43,7 @@ jobs: if: ${{ always() }} run: pytest --pyargs jupyter_client - name: Test jupyter_kernel_test - run: + run: | git clone git@github.com:jupyter/jupyter_kernel_test.git cd jupyter_kernel_test pip install -e ".[test]" From a4358dc5fd1c662183cead2ab85060b070459d94 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 30 Nov 2021 18:51:27 -0600 Subject: [PATCH 0674/1195] Update downstream.yml --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index cb89f3484..94c139649 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -44,7 +44,7 @@ jobs: run: pytest --pyargs jupyter_client - name: Test jupyter_kernel_test run: | - git clone git@github.com:jupyter/jupyter_kernel_test.git + git clone https://github.com/jupyter/jupyter_kernel_test.git cd jupyter_kernel_test pip install -e ".[test]" python test_ipykernel.py From 096d4535e7af577cea330d9447e5ce0da5df382e Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 1 Dec 2021 07:04:23 +0100 Subject: [PATCH 0675/1195] Added missing excpeionPaths field to debugInfo reply --- ipykernel/debugger.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 0828509c0..09b0ef487 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -496,7 +496,8 @@ async def debugInfo(self, message): 'tmpFileSuffix': '.py', 'breakpoints': breakpoint_list, 'stoppedThreads': self.stopped_threads, - 'richRendering': True + 'richRendering': True, + 'exceptionPaths': ['Python Exceptions'] } } return reply From a3d35f5cbf169e0d3e8e36e0963d6c846ba12495 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Dec 2021 12:45:55 +0000 Subject: [PATCH 0676/1195] Automated Changelog Entry for 6.6.0 on master --- CHANGELOG.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e97e4a837..4b3e12514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,33 @@ +## 6.6.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.5.1...9566304175d844c23a1f2b1d70c10df475ed2868)) + +### Enhancements made + +- Set debugOptions for breakpoints in python standard library source [#812](https://github.com/ipython/ipykernel/pull/812) ([@echarles](https://github.com/echarles)) +- Send omit_sections to IPython to choose which sections of documentation you don't want [#809](https://github.com/ipython/ipykernel/pull/809) ([@fasiha](https://github.com/fasiha)) + +### Bugs fixed + +- Added missing excpeionPaths field to debugInfo reply [#814](https://github.com/ipython/ipykernel/pull/814) ([@JohanMabille](https://github.com/JohanMabille)) + +### Maintenance and upkeep improvements + +- Test jupyter_kernel_test as downstream [#813](https://github.com/ipython/ipykernel/pull/813) ([@blink1073](https://github.com/blink1073)) +- Remove Nose dependency [#808](https://github.com/ipython/ipykernel/pull/808) ([@Kojoley](https://github.com/Kojoley)) +- Add explicit encoding to open calls in debugger [#807](https://github.com/ipython/ipykernel/pull/807) ([@dlukes](https://github.com/dlukes)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2021-11-18&to=2021-12-01&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2021-11-18..2021-12-01&type=Issues) | [@dlukes](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adlukes+updated%3A2021-11-18..2021-12-01&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2021-11-18..2021-12-01&type=Issues) | [@fasiha](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afasiha+updated%3A2021-11-18..2021-12-01&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2021-11-18..2021-12-01&type=Issues) | [@Kojoley](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AKojoley+updated%3A2021-11-18..2021-12-01&type=Issues) + + + ## 6.5.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.5.0...1ef2017781435d54348fbb170b8c5d096e3e1351)) @@ -23,8 +50,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2021-11-01..2021-11-18&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2021-11-01..2021-11-18&type=Issues) | [@eastonsuo](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aeastonsuo+updated%3A2021-11-01..2021-11-18&type=Issues) - - ## 6.5.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.4.2...e8d4f66e0f65e284aab444c53e9812dbbc814cb2)) From 9e92535c6ec37abc6a49e5c7b8a96912fe53d0e0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 1 Dec 2021 07:00:13 -0600 Subject: [PATCH 0677/1195] Update CHANGELOG.md --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b3e12514..bb0740e74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,17 +10,17 @@ ### Enhancements made -- Set debugOptions for breakpoints in python standard library source [#812](https://github.com/ipython/ipykernel/pull/812) ([@echarles](https://github.com/echarles)) -- Send omit_sections to IPython to choose which sections of documentation you don't want [#809](https://github.com/ipython/ipykernel/pull/809) ([@fasiha](https://github.com/fasiha)) +- Set `debugOptions` for breakpoints in python standard library source [#812](https://github.com/ipython/ipykernel/pull/812) ([@echarles](https://github.com/echarles)) +- Send `omit_sections` to IPython to choose which sections of documentation you do not want [#809](https://github.com/ipython/ipykernel/pull/809) ([@fasiha](https://github.com/fasiha)) ### Bugs fixed -- Added missing excpeionPaths field to debugInfo reply [#814](https://github.com/ipython/ipykernel/pull/814) ([@JohanMabille](https://github.com/JohanMabille)) +- Added missing `exceptionPaths` field to `debugInfo` reply [#814](https://github.com/ipython/ipykernel/pull/814) ([@JohanMabille](https://github.com/JohanMabille)) ### Maintenance and upkeep improvements -- Test jupyter_kernel_test as downstream [#813](https://github.com/ipython/ipykernel/pull/813) ([@blink1073](https://github.com/blink1073)) -- Remove Nose dependency [#808](https://github.com/ipython/ipykernel/pull/808) ([@Kojoley](https://github.com/Kojoley)) +- Test `jupyter_kernel_test` as downstream [#813](https://github.com/ipython/ipykernel/pull/813) ([@blink1073](https://github.com/blink1073)) +- Remove `nose` dependency [#808](https://github.com/ipython/ipykernel/pull/808) ([@Kojoley](https://github.com/Kojoley)) - Add explicit encoding to open calls in debugger [#807](https://github.com/ipython/ipykernel/pull/807) ([@dlukes](https://github.com/dlukes)) ### Contributors to this release From fa81835b239cc006a58b4870e5695e12638b59d5 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Dec 2021 13:25:06 +0000 Subject: [PATCH 0678/1195] Publish 6.6.0 SHA256 hashes: ipykernel-6.6.0-py3-none-any.whl: 82ded8919fa7f5483be2b6219c3b13380d93faab1fc49cc2cfcd10e9e24cc158 ipykernel-6.6.0.tar.gz: 3a227788216b43982d9ac28195949467627b0d16e6b8af9741d95dcaa8c41a89 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 79aee278b..ae135677a 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.5.1' +__version__ = '6.6.0' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index d9a5c50d5..f41667f4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.5.1" +current = "6.6.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 45ee38a979708a3e0e817f9286027e8a7b140e6e Mon Sep 17 00:00:00 2001 From: Paul McCarthy Date: Thu, 2 Dec 2021 11:34:39 +0000 Subject: [PATCH 0679/1195] RF: Handle both asyncio.QueueEmpty and tornado.queues.QueueEmpty --- ipykernel/kernelbase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index a0a7c98e2..39d6d2661 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -25,7 +25,7 @@ now = datetime.now from tornado import ioloop -from tornado.queues import Queue +from tornado.queues import Queue, QueueEmpty import zmq from zmq.eventloop.zmqstream import ZMQStream @@ -441,7 +441,7 @@ async def process_one(self, wait=True): else: try: t, dispatch, args = self.msg_queue.get_nowait() - except asyncio.QueueEmpty: + except (asyncio.QueueEmpty, QueueEmpty): return None await dispatch(*args) From 2d15ed6a4aad182fd0ba276cd7058c90155bfba3 Mon Sep 17 00:00:00 2001 From: Paul Ivanov Date: Thu, 2 Dec 2021 18:08:46 -0800 Subject: [PATCH 0680/1195] keep with previous behavior my interpretation of suggestion from @ccordoba12 and @stukowski --- ipykernel/eventloops.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 51b502001..b64345559 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -134,7 +134,7 @@ def loop_qt5(kernel): import PySide2 os.environ['QT_API'] = 'pyside2' except ImportError: - pass + os.environ['QT_API'] = 'pyqt5' return loop_qt4(kernel) From 2d0f2ddf608b2ea320ad9d21b8ea8e08867eb083 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 5 Dec 2021 15:34:09 -0600 Subject: [PATCH 0681/1195] consolidate actions --- .github/workflows/check-release.yml | 37 +++------- .github/workflows/ci.yml | 102 ++++++++++------------------ .github/workflows/downstream.yml | 41 +++++------ 3 files changed, 60 insertions(+), 120 deletions(-) diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index 8e4f4f3c9..94372cc08 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -14,42 +14,23 @@ jobs: steps: - name: Checkout uses: actions/checkout@v2 - - name: Install Python - uses: actions/setup-python@v2 - with: - python-version: 3.9 - architecture: "x64" - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(pip cache dir)" - - name: Cache pip - uses: actions/cache@v2 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ hashFiles('setup.cfg') }} - restore-keys: | - ${{ runner.os }}-pip- - ${{ runner.os }}-pip- - - name: Cache checked links - if: ${{ matrix.group == 'link_check' }} - uses: actions/cache@v2 - with: - path: ~/.cache/pytest-link-check - key: ${{ runner.os }}-linkcheck-${{ hashFiles('**/*.md', '**/*.rst') }}-md-links - restore-keys: | - ${{ runner.os }}-linkcheck- - - name: Upgrade packaging dependencies - run: | - pip install --upgrade pip setuptools wheel --user + + - name: Checkout + uses: actions/checkout@v2 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install Dependencies run: | pip install -e . + - name: Check Release if: ${{ matrix.group == 'check_release' }} uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 with: token: ${{ secrets.GITHUB_TOKEN }} + - name: Run Link Check if: ${{ matrix.group == 'link_check' }} uses: jupyter-server/jupyter_releaser/.github/actions/check-links@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86fc6ff68..0a93f946b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,10 @@ name: ipykernel tests -on: [push, pull_request] +on: + push: + branches: "master" + pull_request: + branches: "*" jobs: build: @@ -9,67 +13,59 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos, windows] - python-version: [ '3.7', '3.8', '3.9' ] + python-version: [ '3.7', '3.8', '3.9', '3.10', 'pypy-3.7' ] exclude: - os: windows - python-version: pypy3 + python-version: pypy-3.7 steps: - name: Checkout - uses: actions/checkout@v1 - - name: Install Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - architecture: 'x64' - - name: Upgrade packaging dependencies - run: | - pip install --upgrade pip setuptools wheel --user - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(pip cache dir)" - - name: Cache pip - uses: actions/cache@v1 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} - restore-keys: | - ${{ runner.os }}-pip-${{ matrix.python-version }}- - ${{ runner.os }}-pip- + uses: actions/checkout@v2 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install the Python dependencies run: | pip install --pre --upgrade --upgrade-strategy=eager .[test] codecov + - name: Install matplotlib if: ${{ matrix.os != 'macos' && matrix.python-version != 'pypy3' }} run: | - pip install matplotlib || echo 'failed to install matplolib' + pip install matplotlib || echo 'failed to install matplotlib' + - name: Install alternate event loops if: ${{ matrix.os != 'windows' }} run: | pip install curio || echo 'ignoring curio install failure' pip install trio || echo 'ignoring trio install failure' + - name: List installed packages run: | pip freeze pip check + - name: Run the tests timeout-minutes: 10 run: | pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 + - name: Build the docs if: ${{ matrix.os == 'ubuntu' && matrix.python-version == '3.9'}} run: | cd docs pip install -r requirements.txt make html + - name: Build and check the dist files run: | pip install build twine python -m build . twine check dist/* + - name: Coverage run: | codecov + check_docstrings: runs-on: ${{ matrix.os }}-latest strategy: @@ -82,34 +78,20 @@ jobs: python-version: pypy3 steps: - name: Checkout - uses: actions/checkout@v1 - - name: Install Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - architecture: 'x64' - - name: Upgrade packaging dependencies - run: | - pip install --upgrade pip setuptools wheel --user - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(pip cache dir)" - - name: Cache pip - uses: actions/cache@v1 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} - restore-keys: | - ${{ runner.os }}-pip-${{ matrix.python-version }}- - ${{ runner.os }}-pip- + uses: actions/checkout@v2 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install the Python dependencies run: | pip install --pre --upgrade --upgrade-strategy=eager . pip install velin + - name: Check Docstrings run: | velin . --check --compact + test_without_debugpy: runs-on: ${{ matrix.os }}-latest strategy: @@ -119,34 +101,20 @@ jobs: python-version: [ '3.9' ] steps: - name: Checkout - uses: actions/checkout@v1 - - name: Install Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - architecture: 'x64' - - name: Upgrade packaging dependencies - run: | - pip install --upgrade pip setuptools wheel --user - - name: Get pip cache dir - id: pip-cache - run: | - echo "::set-output name=dir::$(pip cache dir)" - - name: Cache pip - uses: actions/cache@v1 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('setup.py') }} - restore-keys: | - ${{ runner.os }}-pip-${{ matrix.python-version }}- - ${{ runner.os }}-pip- + uses: actions/checkout@v2 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install the Python dependencies without debugpy run: | pip install --pre --upgrade --upgrade-strategy=eager .[test] pip uninstall --yes debugpy + - name: List installed packages run: | pip freeze + - name: Run the tests timeout-minutes: 10 run: | diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 94c139649..7f1c866a4 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -2,7 +2,7 @@ name: Test downstream projects on: push: - branches: "*" + branches: "master" pull_request: branches: "*" @@ -14,34 +14,25 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Set up Python 3.9 - uses: actions/setup-python@v2 - with: - python-version: 3.9 - - - name: Install dependencies - run: | - pip install --upgrade pip - pip install pytest pytest-asyncio pytest-tornado - pip install nbclient[test] - pip install --pre -U --upgrade-strategy=only-if-needed nbclient - pip install ipyparallel[test] - pip install --pre -U --upgrade-strategy=only-if-needed ipyparallel - pip install jupyter_client[test] - pip install --pre -U --upgrade-strategy=only-if-needed jupyter_client - pip install . --force-reinstall - pip freeze - python -c 'import ipykernel; print("ipykernel", ipykernel.__version__)' + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Test nbclient - if: ${{ always() }} - run: IPYKERNEL_CELL_NAME="" pytest --pyargs nbclient + uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 + with: + package_name: nbclient + env_values: IPYKERNEL_CELL_NAME=\ + - name: Test ipyparallel - if: ${{ always() }} - run: pytest --pyargs ipyparallel + uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 + with: + package_name: ipyparallel + - name: Test jupyter_client - if: ${{ always() }} - run: pytest --pyargs jupyter_client + uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 + with: + package_name: jupyter_client + - name: Test jupyter_kernel_test run: | git clone https://github.com/jupyter/jupyter_kernel_test.git From edd9e4391c75daf4c76e25be22e3a537e132a222 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 5 Dec 2021 19:53:48 -0600 Subject: [PATCH 0682/1195] test reliability --- ipykernel/tests/test_kernel.py | 5 +++++ ipykernel/tests/utils.py | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index e6af1e2d2..9dc30ad7f 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -6,6 +6,7 @@ import ast import io import os.path +import platform import subprocess import sys import time @@ -367,6 +368,10 @@ def test_unc_paths(): assert reply['content']['status'] == 'ok' +@pytest.mark.skipif( + platform.python_implementation() == "PyPy", + reason="does not work on PyPy", +) def test_shutdown(): """Kernel exits after polite shutdown_request""" with new_kernel() as kc: diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index daeccf64d..91d87f86e 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -5,6 +5,7 @@ import atexit import os +import platform import sys from tempfile import TemporaryDirectory from time import time @@ -17,7 +18,7 @@ STARTUP_TIMEOUT = 60 -TIMEOUT = 60 +TIMEOUT = 100 KM = None KC = None From 9e46aea3e5f721b83575aa86d965b36e9d630029 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 5 Dec 2021 20:10:31 -0600 Subject: [PATCH 0683/1195] more cleanup --- .github/workflows/ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0a93f946b..dc192707f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,9 +46,18 @@ jobs: - name: Run the tests timeout-minutes: 10 + if: ${{ !startsWith( matrix.python-version, 'pypy' ) }} run: | pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 + - name: Run the tests + + - name: Run the tests on pypy + timeout-minutes: 15 + if: ${{ startsWith( matrix.python-version, 'pypy' ) }} + run: | + pytest -vv ipykernel + - name: Build the docs if: ${{ matrix.os == 'ubuntu' && matrix.python-version == '3.9'}} run: | @@ -56,12 +65,6 @@ jobs: pip install -r requirements.txt make html - - name: Build and check the dist files - run: | - pip install build twine - python -m build . - twine check dist/* - - name: Coverage run: | codecov From 4b50bdd89d890db201a013ec8fccfc6277c81fe9 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 5 Dec 2021 20:58:50 -0600 Subject: [PATCH 0684/1195] fix syntax --- .github/workflows/ci.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc192707f..7f70f151b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,8 +50,6 @@ jobs: run: | pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 - - name: Run the tests - - name: Run the tests on pypy timeout-minutes: 15 if: ${{ startsWith( matrix.python-version, 'pypy' ) }} From d26c614a38b7664214c9e3daa329c9efb07320df Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 5 Dec 2021 21:08:42 -0600 Subject: [PATCH 0685/1195] break downstream into two jobs --- .github/workflows/downstream.yml | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 7f1c866a4..3f1909e02 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -7,7 +7,7 @@ on: branches: "*" jobs: - tests: + downstream1: runs-on: ubuntu-latest steps: @@ -23,15 +23,25 @@ jobs: package_name: nbclient env_values: IPYKERNEL_CELL_NAME=\ - - name: Test ipyparallel + - name: Test jupyter_client uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: - package_name: ipyparallel + package_name: jupyter_client - - name: Test jupyter_client + downstream2: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Test ipyparallel uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: - package_name: jupyter_client + package_name: ipyparallel - name: Test jupyter_kernel_test run: | From 4715b9b493ff780eadca1a8551a3556a37fd4624 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 9 Dec 2021 09:40:26 -0800 Subject: [PATCH 0686/1195] IPython's start_kernel is deprecated. Technically it has been deprecated since 5.0 but indirectly as it imports from IPython.kernel with itself is a shim module that exposes ipykernel. So it is useless to call into IPython if it's for calling back into ipykernel. Though the start_kernel itself was not emitting a deprecation warning, which will be the case in 8.0, and hopefully we can finish removing the shim modules later. --- ipykernel/tests/test_start_kernel.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/ipykernel/tests/test_start_kernel.py b/ipykernel/tests/test_start_kernel.py index 12d68bf3c..876b5bdcc 100644 --- a/ipykernel/tests/test_start_kernel.py +++ b/ipykernel/tests/test_start_kernel.py @@ -1,14 +1,19 @@ from .test_embed_kernel import setup_kernel from flaky import flaky +from textwrap import dedent TIMEOUT = 15 @flaky(max_runs=3) def test_ipython_start_kernel_userns(): - cmd = ('from IPython import start_kernel\n' - 'ns = {"tre": 123}\n' - 'start_kernel(user_ns=ns)') + cmd = dedent( + """ + from ipykernel.kernelapp import launch_new_instance + ns = {"tre": 123} + launch_new_instance(user_ns=ns) + """ + ) with setup_kernel(cmd) as client: client.inspect("tre") @@ -34,8 +39,12 @@ def test_ipython_start_kernel_userns(): @flaky(max_runs=3) def test_ipython_start_kernel_no_userns(): # Issue #4188 - user_ns should be passed to shell as None, not {} - cmd = ('from IPython import start_kernel\n' - 'start_kernel()') + cmd = dedent( + """ + from ipykernel.kernelapp import launch_new_instance + launch_new_instance() + """ + ) with setup_kernel(cmd) as client: # user_module should not be an instance of DummyMod From db3a59678eb8617d05a8cf5ab4a07e5a72c97fcc Mon Sep 17 00:00:00 2001 From: Aleksei Stepanov Date: Wed, 22 Dec 2021 16:35:12 +0100 Subject: [PATCH 0687/1195] Clean python 2 artifacts. Fix #826 * `IOError` is `OSError` in python3 * `io.open` -> `open` * `return str()` -> `return ''` directly * direct `super()` call * do not subclass `object` --- examples/embedding/internal_ipkernel.py | 2 +- ipykernel/comm/comm.py | 2 +- ipykernel/compiler.py | 2 +- ipykernel/displayhook.py | 2 +- ipykernel/eventloops.py | 4 ++-- ipykernel/gui/gtk3embed.py | 2 +- ipykernel/gui/gtkembed.py | 2 +- ipykernel/inprocess/blocking.py | 2 +- ipykernel/inprocess/channels.py | 8 ++++---- ipykernel/inprocess/client.py | 4 ++-- ipykernel/inprocess/ipkernel.py | 10 +++++----- ipykernel/inprocess/manager.py | 2 +- ipykernel/iostream.py | 14 +++++++------- ipykernel/ipkernel.py | 10 +++++----- ipykernel/kernelapp.py | 6 +++--- ipykernel/kernelbase.py | 2 +- ipykernel/parentpoller.py | 4 ++-- ipykernel/pickleutil.py | 2 +- ipykernel/tests/__init__.py | 2 +- ipykernel/tests/test_debugger.py | 2 +- ipykernel/tests/test_embed_kernel.py | 4 ++-- ipykernel/tests/test_jsonutil.py | 6 +++--- ipykernel/tests/test_kernel.py | 6 +++--- ipykernel/tests/test_kernelspec.py | 3 +-- ipykernel/tests/test_message_spec.py | 4 ++-- ipykernel/tests/test_pickleutil.py | 1 - ipykernel/tests/test_zmq_shell.py | 6 +++--- ipykernel/zmqshell.py | 8 ++++---- 28 files changed, 60 insertions(+), 62 deletions(-) diff --git a/examples/embedding/internal_ipkernel.py b/examples/embedding/internal_ipkernel.py index f20b112f3..f5a1b3577 100644 --- a/examples/embedding/internal_ipkernel.py +++ b/examples/embedding/internal_ipkernel.py @@ -20,7 +20,7 @@ def mpl_kernel(gui): return kernel -class InternalIPKernel(object): +class InternalIPKernel: def init_ipkernel(self, backend): # Start IPython kernel with GUI event loop and mpl support diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 3f7192752..97dff2bec 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -50,7 +50,7 @@ def _default_topic(self): def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwargs): if target_name: kwargs['target_name'] = target_name - super(Comm, self).__init__(**kwargs) + super().__init__(**kwargs) if self.kernel: if self.primary: # I am primary, open my peer. diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 2dbf6fb5d..0b1384021 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -59,7 +59,7 @@ def get_file_name(code): class XCachingCompiler(CachingCompiler): def __init__(self, *args, **kwargs): - super(XCachingCompiler, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) self.log = None def get_code_name(self, raw_code, code, number): diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index fe26c7a20..19ee151f4 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -12,7 +12,7 @@ from jupyter_client.session import extract_header, Session -class ZMQDisplayHook(object): +class ZMQDisplayHook: """A simple displayhook that publishes the object's repr over a ZeroMQ socket.""" topic = b'execute_result' diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 5e4b1af94..45a978123 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -229,7 +229,7 @@ def loop_tk(kernel): # file handlers are not available on Windows if hasattr(app, 'createfilehandler'): # A basic wrapper for structural similarity with the Windows version - class BasicAppWrapper(object): + class BasicAppWrapper: def __init__(self, app): self.app = app self.app.withdraw() @@ -257,7 +257,7 @@ def process_stream_events(stream, *a, **kw): # Tk uses milliseconds poll_interval = int(1000 * kernel._poll_interval) - class TimedAppWrapper(object): + class TimedAppWrapper: def __init__(self, app, func): self.app = app self.app.withdraw() diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index 5cea1adb6..4d6803877 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -23,7 +23,7 @@ # Classes and functions #----------------------------------------------------------------------------- -class GTKEmbed(object): +class GTKEmbed: """A class to embed a kernel into the GTK main event loop. """ def __init__(self, kernel): diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index d9dc7e6f4..d77224a3f 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -21,7 +21,7 @@ # Classes and functions #----------------------------------------------------------------------------- -class GTKEmbed(object): +class GTKEmbed: """A class to embed a kernel into the GTK main event loop. """ def __init__(self, kernel): diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index cc995a51b..a36b95a29 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -23,7 +23,7 @@ class BlockingInProcessChannel(InProcessChannel): def __init__(self, *args, **kwds): - super(BlockingInProcessChannel, self).__init__(*args, **kwds) + super().__init__(*args, **kwds) self._in_queue = Queue() def call_handlers(self, msg): diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 00fc1b7e0..14fcf62fa 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -9,12 +9,12 @@ # Channel classes #----------------------------------------------------------------------------- -class InProcessChannel(object): +class InProcessChannel: """Base class for in-process channels.""" proxy_methods = [] def __init__(self, client=None): - super(InProcessChannel, self).__init__() + super().__init__() self.client = client self._is_alive = False @@ -57,7 +57,7 @@ def process_events(self): -class InProcessHBChannel(object): +class InProcessHBChannel: """A dummy heartbeat channel interface for in-process kernels. Normally we use the heartbeat to check that the kernel process is alive. @@ -68,7 +68,7 @@ class InProcessHBChannel(object): time_to_dead = 3.0 def __init__(self, client=None): - super(InProcessHBChannel, self).__init__() + super().__init__() self.client = client self._is_alive = False self._pause = True diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 1784a6eaa..84ac82a49 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -58,12 +58,12 @@ def _default_blocking_class(self): return BlockingInProcessKernelClient def get_connection_info(self): - d = super(InProcessKernelClient, self).get_connection_info() + d = super().get_connection_info() d['kernel'] = self.kernel return d def start_channels(self, *args, **kwargs): - super(InProcessKernelClient, self).start_channels() + super().start_channels() self.kernel.frontends.append(self) @property diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index d8fe1d5e2..bc17236d6 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -69,7 +69,7 @@ def _default_iopub_socket(self): stdin_socket = Instance(DummySocket, ()) def __init__(self, **traits): - super(InProcessKernel, self).__init__(**traits) + super().__init__(**traits) self._underlying_iopub_socket.observe(self._io_dispatch, names=['message_sent']) self.shell.kernel = self @@ -77,7 +77,7 @@ def __init__(self, **traits): async def execute_request(self, stream, ident, parent): """ Override for temporary IO redirection. """ with self._redirected_io(): - await super(InProcessKernel, self).execute_request(stream, ident, parent) + await super().execute_request(stream, ident, parent) def start(self): """ Override registration of dispatchers for streams. """ @@ -106,7 +106,7 @@ def _input_request(self, prompt, ident, parent, password=False): break else: logging.error('No frontend found for raw_input request') - return str() + return '' # Await a response. while self.raw_input_str is None: @@ -183,13 +183,13 @@ def enable_matplotlib(self, gui=None): """Enable matplotlib integration for the kernel.""" if not gui: gui = self.kernel.gui - return super(InProcessInteractiveShell, self).enable_matplotlib(gui) + return super().enable_matplotlib(gui) def enable_pylab(self, gui=None, import_all=True, welcome_message=False): """Activate pylab support at runtime.""" if not gui: gui = self.kernel.gui - return super(InProcessInteractiveShell, self).enable_pylab(gui, import_all, + return super().enable_pylab(gui, import_all, welcome_message) InteractiveShellABC.register(InProcessInteractiveShell) diff --git a/ipykernel/inprocess/manager.py b/ipykernel/inprocess/manager.py index ccdaccadf..f94c3a62b 100644 --- a/ipykernel/inprocess/manager.py +++ b/ipykernel/inprocess/manager.py @@ -71,7 +71,7 @@ def is_alive(self): def client(self, **kwargs): kwargs['kernel'] = self.kernel - return super(InProcessKernelManager, self).client(**kwargs) + return super().client(**kwargs) #----------------------------------------------------------------------------- diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 2f760b8dd..f2df4df2b 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -39,7 +39,7 @@ #----------------------------------------------------------------------------- -class IOPubThread(object): +class IOPubThread: """An object for sending IOPub messages in a background thread Prevents a blocking main thread from delaying output from threads. @@ -239,7 +239,7 @@ def _really_send(self, msg, *args, **kwargs): ctx.term() -class BackgroundSocket(object): +class BackgroundSocket: """Wrapper around IOPub thread that provides zmq send[_multipart]""" io_thread = None @@ -250,7 +250,7 @@ def __getattr__(self, attr): """Wrap socket attr access for backward-compatibility""" if attr.startswith('__') and attr.endswith('__'): # don't wrap magic methods - super(BackgroundSocket, self).__getattr__(attr) + super().__getattr__(attr) if hasattr(self.io_thread.socket, attr): warnings.warn( "Accessing zmq Socket attribute {attr} on BackgroundSocket" @@ -260,11 +260,11 @@ def __getattr__(self, attr): stacklevel=2, ) return getattr(self.io_thread.socket, attr) - super(BackgroundSocket, self).__getattr__(attr) + super().__getattr__(attr) def __setattr__(self, attr, value): if attr == 'io_thread' or (attr.startswith('__' and attr.endswith('__'))): - super(BackgroundSocket, self).__setattr__(attr, value) + super().__setattr__(attr, value) else: warnings.warn( "Setting zmq Socket attribute {attr} on BackgroundSocket" @@ -484,7 +484,7 @@ def _flush(self): self.echo.flush() except OSError as e: if self.echo is not sys.__stderr__: - print("Flush failed: {}".format(e), + print(f"Flush failed: {e}", file=sys.__stderr__) data = self._flush_buffer() @@ -517,7 +517,7 @@ def write(self, string: str) -> int: self.echo.write(string) except OSError as e: if self.echo is not sys.__stderr__: - print("Write failed: {}".format(e), + print(f"Write failed: {e}", file=sys.__stderr__) if self.pub_thread is None: diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index bb50c04cc..fd2462043 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -75,7 +75,7 @@ def _user_ns_changed(self, change): _sys_eval_input = Any() def __init__(self, **kwargs): - super(IPythonKernel, self).__init__(**kwargs) + super().__init__(**kwargs) # Initialize the Debugger if _is_debugpy_available: @@ -175,13 +175,13 @@ def start(self): self.log.warning("debugpy_stream undefined, debugging will not be enabled") else: self.debugpy_stream.on_recv(self.dispatch_debugpy, copy=False) - super(IPythonKernel, self).start() + super().start() def set_parent(self, ident, parent, channel='shell'): """Overridden from parent to tell the display hook and output streams about the parent message. """ - super(IPythonKernel, self).set_parent(ident, parent, channel) + super().set_parent(ident, parent, channel) if channel == 'shell': self.shell.set_parent(parent) @@ -190,7 +190,7 @@ def init_metadata(self, parent): Run at the beginning of each execution request. """ - md = super(IPythonKernel, self).init_metadata(parent) + md = super().init_metadata(parent) # FIXME: remove deprecated ipyparallel-specific code # This is required for ipyparallel < 5.0 md.update({ @@ -591,4 +591,4 @@ def __init__(self, *args, **kwargs): import warnings warnings.warn('Kernel is a deprecated alias of ipykernel.ipkernel.IPythonKernel', DeprecationWarning) - super(Kernel, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 46366c6f4..dc33530f8 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -247,7 +247,7 @@ def cleanup_connection_file(self): self.log.debug("Cleaning up connection file: %s", cf) try: os.remove(cf) - except (IOError, OSError): + except OSError: pass self.cleanup_ipc_files() @@ -257,7 +257,7 @@ def init_connection_file(self): self.connection_file = "kernel-%s.json"%os.getpid() try: self.connection_file = filefind(self.connection_file, ['.', self.connection_dir]) - except IOError: + except OSError: self.log.debug("Connection file not found: %s", self.connection_file) # This means I own it, and I'll create it in this directory: os.makedirs(os.path.dirname(self.abs_connection_file), mode=0o700, exist_ok=True) @@ -621,7 +621,7 @@ def init_pdb(self): @catch_config_error def initialize(self, argv=None): self._init_asyncio_patch() - super(IPKernelApp, self).initialize(argv) + super().initialize(argv) if self.subapp is not None: return diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 39d6d2661..ceeebc23a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -207,7 +207,7 @@ def _parent_header(self): control_msg_types = msg_types + ['clear_request', 'abort_request', 'debug_request'] def __init__(self, **kwargs): - super(Kernel, self).__init__(**kwargs) + super().__init__(**kwargs) # Build dict of handlers for message types self.shell_handlers = {} for msg_type in self.msg_types: diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index ed389217f..4a2124407 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -22,7 +22,7 @@ class ParentPollerUnix(Thread): """ def __init__(self): - super(ParentPollerUnix, self).__init__() + super().__init__() self.daemon = True def run(self): @@ -60,7 +60,7 @@ def __init__(self, interrupt_handle=None, parent_handle=None): handle is signaled. """ assert(interrupt_handle or parent_handle) - super(ParentPollerWindows, self).__init__() + super().__init__() if ctypes is None: raise ImportError("ParentPollerWindows requires ctypes") self.daemon = True diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index f91545840..fc8e4f2e3 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -110,7 +110,7 @@ def use_cloudpickle(): #------------------------------------------------------------------------------- -class CannedObject(object): +class CannedObject: def __init__(self, obj, keys=[], hook=None): """can an object for safe pickling diff --git a/ipykernel/tests/__init__.py b/ipykernel/tests/__init__.py index c646095d1..58b12bfa6 100644 --- a/ipykernel/tests/__init__.py +++ b/ipykernel/tests/__init__.py @@ -40,6 +40,6 @@ def teardown(): try: shutil.rmtree(tmp) - except (OSError, IOError): + except OSError: # no such file pass diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index 71a2302e3..2a3223e62 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -146,7 +146,7 @@ def test_rich_inspect_not_at_breakpoint(kernel_with_debug): {"variableName": var_name}, ) - assert reply["body"]["data"] == {"text/plain": "'{}'".format(value)} + assert reply["body"]["data"] == {"text/plain": f"'{value}'"} def test_rich_inspect_at_breakpoint(kernel_with_debug): diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index b7fa53cb9..78806a30a 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -58,12 +58,12 @@ def connection_file_ready(connection_file): if kernel.poll() is not None: o, e = kernel.communicate() - raise IOError("Kernel failed to start:\n%s" % e) + raise OSError("Kernel failed to start:\n%s" % e) if not os.path.exists(connection_file): if kernel.poll() is None: kernel.terminate() - raise IOError("Connection file %r never arrived" % connection_file) + raise OSError("Connection file %r never arrived" % connection_file) client = BlockingKernelClient(connection_file=connection_file) client.load_connection_file() diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 511cad39e..aaf9fdfad 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -20,13 +20,13 @@ JUPYTER_CLIENT_MAJOR_VERSION = jupyter_client_version[0] -class MyInt(object): +class MyInt: def __int__(self): return 389 numbers.Integral.register(MyInt) -class MyFloat(object): +class MyFloat: def __float__(self): return 3.14 numbers.Real.register(MyFloat) @@ -45,7 +45,7 @@ def test(): # Containers ([1, 2], None), ((1, 2), [1, 2]), - (set([1, 2]), [1, 2]), + ({1, 2}, [1, 2]), (dict(x=1), None), ({'x': 1, 'y':[1,2,3], '1':'int'}, None), # More exotic objects diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 9dc30ad7f..6702b6c15 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -4,7 +4,6 @@ # Distributed under the terms of the Modified BSD License. import ast -import io import os.path import platform import subprocess @@ -186,6 +185,7 @@ def test_subprocess_error(): _check_master(kc, expected=True) _check_master(kc, expected=True, stream="stderr") + # raw_input tests def test_raw_input(): @@ -220,7 +220,7 @@ def test_save_history(): wait_for_idle(kc) _, reply = execute("%hist -f " + file, kc=kc) assert reply['status'] == 'ok' - with io.open(file, encoding='utf-8') as f: + with open(file, encoding='utf-8') as f: content = f.read() assert 'a=1' in content assert 'b="abcþ"' in content @@ -350,7 +350,7 @@ def test_unc_paths(): file_path = os.path.splitdrive(os.path.dirname(drive_file_path))[1] unc_file_path = os.path.join(unc_root, file_path[1:]) - kc.execute("cd {0:s}".format(unc_file_path)) + kc.execute(f"cd {unc_file_path:s}") reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'ok' out, err = assemble_output(kc.get_iopub_msg) diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index b42ff39b5..c736a5833 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -2,7 +2,6 @@ # Distributed under the terms of the Modified BSD License. import json -import io import os import shutil import sys @@ -65,7 +64,7 @@ def assert_is_spec(path): assert os.path.exists(dst) kernel_json = pjoin(path, 'kernel.json') assert os.path.exists(kernel_json) - with io.open(kernel_json, encoding='utf8') as f: + with open(kernel_json, encoding='utf8') as f: json.load(f) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 40ec5e4e2..b66c124d6 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -62,7 +62,7 @@ def __init__(self, *args, **kwargs): self.min = kwargs.pop('min', None) self.max = kwargs.pop('max', None) kwargs['default_value'] = self.min - super(Version, self).__init__(*args, **kwargs) + super().__init__(*args, **kwargs) def validate(self, obj, value): if self.min and V(value) < V(self.min): @@ -79,7 +79,7 @@ class RMessage(Reference): content = Dict() def check(self, d): - super(RMessage, self).check(d) + super().check(d) RHeader().check(self.header) if self.parent_header: RHeader().check(self.parent_header) diff --git a/ipykernel/tests/test_pickleutil.py b/ipykernel/tests/test_pickleutil.py index 598198e9f..0bc6e457a 100644 --- a/ipykernel/tests/test_pickleutil.py +++ b/ipykernel/tests/test_pickleutil.py @@ -1,4 +1,3 @@ - import pickle from ipykernel.pickleutil import can, uncan diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index aa5e0a2c8..56f587e74 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -14,7 +14,7 @@ from jupyter_client.session import Session -class NoReturnDisplayHook(object): +class NoReturnDisplayHook: """ A dummy DisplayHook which allows us to monitor the number of times an object is called, but which @@ -33,7 +33,7 @@ class ReturnDisplayHook(NoReturnDisplayHook): message when it is called. """ def __call__(self, obj): - super(ReturnDisplayHook, self).__call__(obj) + super().__call__(obj) return obj @@ -51,7 +51,7 @@ def send(self, *args, **kwargs): with an increment to the send counter. """ self.send_count += 1 - super(CounterSession, self).send(*args, **kwargs) + super().send(*args, **kwargs) class ZMQDisplayPublisherTests(unittest.TestCase): diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index a53dfbe2c..b5bcded0e 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -378,7 +378,7 @@ def connect_info(self, arg_s): print ("Paste the above JSON into a file, and connect with:\n" " $> jupyter --existing \n" "or, if you are local, you can connect with just:\n" - " $> jupyter --existing {0}\n" + " $> jupyter --existing {}\n" "or even just:\n" " $> jupyter --existing\n" "if this is the most recent Jupyter kernel you have started.".format( @@ -497,7 +497,7 @@ def init_environment(self): env['GIT_PAGER'] = 'cat' def init_hooks(self): - super(ZMQInteractiveShell, self).init_hooks() + super().init_hooks() self.set_hook('show_in_pager', page.as_hook(payloadpage.page), 99) def init_data_pub(self): @@ -530,7 +530,7 @@ def ask_exit(self): def run_cell(self, *args, **kwargs): self._last_traceback = None - return super(ZMQInteractiveShell, self).run_cell(*args, **kwargs) + return super().run_cell(*args, **kwargs) def _showtraceback(self, etype, evalue, stb): # try to preserve ordering of tracebacks and print statements @@ -592,7 +592,7 @@ def get_parent(self): return self.parent_header def init_magics(self): - super(ZMQInteractiveShell, self).init_magics() + super().init_magics() self.register_magics(KernelMagics) self.magics_manager.register_alias('ed', 'edit') From 7f0e45d5f68bb3e16df9be684f098b172a45526e Mon Sep 17 00:00:00 2001 From: Alexey Stepanov Date: Wed, 22 Dec 2021 17:04:37 +0100 Subject: [PATCH 0688/1195] Update ipykernel/zmqshell.py Co-authored-by: David Brochart --- ipykernel/zmqshell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index b5bcded0e..e0be32d0a 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -378,7 +378,7 @@ def connect_info(self, arg_s): print ("Paste the above JSON into a file, and connect with:\n" " $> jupyter --existing \n" "or, if you are local, you can connect with just:\n" - " $> jupyter --existing {}\n" + f" $> jupyter --existing {connection_file}\n" "or even just:\n" " $> jupyter --existing\n" "if this is the most recent Jupyter kernel you have started.".format( From 9efccb7d053b16215d6ac046bed106f777347461 Mon Sep 17 00:00:00 2001 From: Aleksei Stepanov Date: Wed, 22 Dec 2021 17:22:32 +0100 Subject: [PATCH 0689/1195] replace `.format` by f-strings if applicable f-strings is faster, than `str().format` --- ipykernel/debugger.py | 2 +- ipykernel/iostream.py | 12 ++++++------ ipykernel/tests/test_async.py | 2 +- ipykernel/tests/test_debugger.py | 6 +++--- ipykernel/zmqshell.py | 15 +++++++-------- 5 files changed, 18 insertions(+), 19 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 09b0ef487..44c9d16e9 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -540,7 +540,7 @@ async def richInspectVariables(self, message): else: # The code has stopped on a breakpoint, we use the setExpression # request to get the rich representation of the variable - code = "get_ipython().display_formatter.format(" + var_name + ")" + code = f"get_ipython().display_formatter.format({var_name})" frame_id = message["arguments"]["frameId"] seq = message["seq"] reply = await self._forward_message( diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index f2df4df2b..f4c4ad0f8 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -253,9 +253,9 @@ def __getattr__(self, attr): super().__getattr__(attr) if hasattr(self.io_thread.socket, attr): warnings.warn( - "Accessing zmq Socket attribute {attr} on BackgroundSocket" - " is deprecated since ipykernel 4.3.0" - " use .io_thread.socket.{attr}".format(attr=attr), + f"Accessing zmq Socket attribute {attr} on BackgroundSocket" + f" is deprecated since ipykernel 4.3.0" + f" use .io_thread.socket.{attr}", DeprecationWarning, stacklevel=2, ) @@ -267,9 +267,9 @@ def __setattr__(self, attr, value): super().__setattr__(attr, value) else: warnings.warn( - "Setting zmq Socket attribute {attr} on BackgroundSocket" - " is deprecated since ipykernel 4.3.0" - " use .io_thread.socket.{attr}".format(attr=attr), + f"Setting zmq Socket attribute {attr} on BackgroundSocket" + f" is deprecated since ipykernel 4.3.0" + f" use .io_thread.socket.{attr}", DeprecationWarning, stacklevel=2, ) diff --git a/ipykernel/tests/test_async.py b/ipykernel/tests/test_async.py index d1f327bff..9c0820c5e 100644 --- a/ipykernel/tests/test_async.py +++ b/ipykernel/tests/test_async.py @@ -54,7 +54,7 @@ def test_async_interrupt(asynclib, request): flush_channels(KC) msg_id = KC.execute( - "print('begin'); import {0}; await {0}.sleep(5)".format(asynclib) + f"print('begin'); import {asynclib}; await {asynclib}.sleep(5)" ) busy = KC.get_iopub_msg(timeout=TIMEOUT) validate_message(busy, "status", msg_id) diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index 2a3223e62..d7263ca9b 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -130,9 +130,9 @@ def test_set_breakpoints(kernel_with_debug): def test_rich_inspect_not_at_breakpoint(kernel_with_debug): var_name = "text" value = "Hello the world" - code = """{0}='{1}' -print({0}) -""".format(var_name, value) + code = f"""{var_name}='{value}' +print({var_name}) +""" msg_id = kernel_with_debug.execute(code) get_reply(kernel_with_debug, msg_id) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index e0be32d0a..5f33054f9 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -375,15 +375,14 @@ def connect_info(self, arg_s): print (info + '\n') - print ("Paste the above JSON into a file, and connect with:\n" - " $> jupyter --existing \n" - "or, if you are local, you can connect with just:\n" + print ( + f"Paste the above JSON into a file, and connect with:\n" + f" $> jupyter --existing \n" + f"or, if you are local, you can connect with just:\n" f" $> jupyter --existing {connection_file}\n" - "or even just:\n" - " $> jupyter --existing\n" - "if this is the most recent Jupyter kernel you have started.".format( - connection_file - ) + f"or even just:\n" + f" $> jupyter --existing\n" + f"if this is the most recent Jupyter kernel you have started." ) @line_magic From 22689db56949e68ca2e62ad7003c0d6ac7e5d515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 29 Dec 2021 15:13:32 +0100 Subject: [PATCH 0690/1195] Fix title position in changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb0740e74..3352bbba7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,5 @@ # Changes in IPython kernel -## 6.3 - ## 6.6.0 @@ -130,6 +128,8 @@ [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2021-08-31..2021-09-09&type=Issues) | [@frenzymadness](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afrenzymadness+updated%3A2021-08-31..2021-09-09&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AmartinRenou+updated%3A2021-08-31..2021-09-09&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2021-08-31..2021-09-09&type=Issues) +## 6.3 + ## 6.3.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.3.0...0b4a8eaa080fc11e240ada9c44c95841463da58c)) From c048a7a3a2f569e54a36beb57d2817204f3c8689 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 29 Dec 2021 16:35:01 +0100 Subject: [PATCH 0691/1195] Add unit tests Expected to fail as the fix is not yet pushed --- ipykernel/tests/test_debugger.py | 75 ++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index d7263ca9b..bdfa41468 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -1,6 +1,6 @@ import pytest -from .utils import new_kernel, get_reply +from .utils import TIMEOUT, new_kernel, get_reply seq = 0 @@ -8,7 +8,7 @@ pytest.importorskip("debugpy") -def wait_for_debug_request(kernel, command, arguments=None): +def wait_for_debug_request(kernel, command, arguments=None, full_reply=False): """Carry out a debug request and return the reply content. It does not check if the request was successful. @@ -27,7 +27,7 @@ def wait_for_debug_request(kernel, command, arguments=None): ) kernel.control_channel.send(msg) reply = get_reply(kernel, msg["header"]["msg_id"], channel="control") - return reply["content"] + return reply if full_reply else reply["content"] @pytest.fixture @@ -127,6 +127,75 @@ def test_set_breakpoints(kernel_with_debug): assert r["success"] +def test_stop_on_breakpoint(kernel_with_debug): + code = """def f(a, b): + c = a + b + return c + +f(2, 3)""" + + r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) + source = r["body"]["sourcePath"] + + wait_for_debug_request(kernel_with_debug, "debugInfo") + + wait_for_debug_request( + kernel_with_debug, + "setBreakpoints", + { + "breakpoints": [{"line": 2}], + "source": {"path": source}, + "sourceModified": False, + }, + ) + + wait_for_debug_request(kernel_with_debug, "configurationDone", full_reply=True) + + kernel_with_debug.execute(code) + + # Wait for stop on breakpoint + msg = {"msg_type": "", "content": {}} + while msg.get('msg_type') != 'debug_event' or msg["content"].get("event") != "stopped": + msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) + + assert msg["content"]["body"]["reason"] == "breakpoint" + + +def test_breakpoint_in_cell_with_leading_empty_lines(kernel_with_debug): + code = """ +def f(a, b): + c = a + b + return c + +f(2, 3)""" + + r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) + source = r["body"]["sourcePath"] + + wait_for_debug_request(kernel_with_debug, "debugInfo") + + wait_for_debug_request( + kernel_with_debug, + "setBreakpoints", + { + "breakpoints": [{"line": 6}], + "source": {"path": source}, + "sourceModified": False, + }, + ) + + wait_for_debug_request(kernel_with_debug, "configurationDone", full_reply=True) + + kernel_with_debug.execute(code) + + # Wait for stop on breakpoint + msg = {"msg_type": "", "content": {}} + while msg.get('msg_type') != 'debug_event' or msg["content"].get("event") != "stopped": + msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) + + assert msg["content"]["body"]["reason"] == "breakpoint" + + def test_rich_inspect_not_at_breakpoint(kernel_with_debug): var_name = "text" value = "Hello the world" From bcdc32a6a42cc11f79b1b102735afefcc8d31c40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Wed, 29 Dec 2021 16:39:13 +0100 Subject: [PATCH 0692/1195] Remove `leading_empty_lines` when debugging --- ipykernel/debugger.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 44c9d16e9..62c6a5407 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -8,6 +8,7 @@ from tornado.locks import Event from IPython.core.getipython import get_ipython +from IPython.core.inputtransformer2 import leading_empty_lines try: from jupyter_client.jsonutil import json_default @@ -286,6 +287,7 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): self.stopped_threads = [] self.debugpy_initialized = False + self._removed_cleanup = {} self.debugpy_host = '127.0.0.1' self.debugpy_port = 0 @@ -341,12 +343,25 @@ def start(self): ident, msg = self.session.recv(self.shell_socket, mode=0) self.debugpy_initialized = msg['content']['status'] == 'ok' + + # Don't remove leading empty lines when debugging so the breakpoints are correctly positioned + cleanup_transforms = get_ipython().input_transformer_manager.cleanup_transforms + if leading_empty_lines in cleanup_transforms: + index = cleanup_transforms.index(leading_empty_lines) + self._removed_cleanup[index] = cleanup_transforms.pop(index) + self.debugpy_client.connect_tcp_socket() return self.debugpy_initialized def stop(self): self.debugpy_client.disconnect_tcp_socket() + # Restore remove cleanup transformers + cleanup_transforms = get_ipython().input_transformer_manager.cleanup_transforms + for index in sorted(self._removed_cleanup): + func = self._removed_cleanup.pop(index) + cleanup_transforms.insert(index, func) + async def dumpCell(self, message): code = message['arguments']['code'] file_name = get_file_name(code) From f5d229220c69000f987759ede8295e6f8509a70e Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Mon, 3 Jan 2022 18:58:04 +0100 Subject: [PATCH 0693/1195] do_one_iteration is a coroutine --- ipykernel/eventloops.py | 10 +++++++++- setup.py | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 45a978123..29434bb41 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -253,6 +253,10 @@ def process_stream_events(stream, *a, **kw): app.mainloop() else: + import asyncio + import nest_asyncio + nest_asyncio.apply() + doi = kernel.do_one_iteration # Tk uses milliseconds poll_interval = int(1000 * kernel._poll_interval) @@ -264,7 +268,11 @@ def __init__(self, app, func): self.func = func def on_timer(self): - self.func() + loop = asyncio.get_event_loop() + try: + loop.run_until_complete(self.func()) + except Exception: + kernel.log.exception("Error in message handler") self.app.after(poll_interval, self.on_timer) def start(self): diff --git a/setup.py b/setup.py index fc2828660..f2c0719c7 100644 --- a/setup.py +++ b/setup.py @@ -70,6 +70,7 @@ def run(self): 'tornado>=4.2,<7.0', 'matplotlib-inline>=0.1.0,<0.2.0', 'appnope;platform_system=="Darwin"', + 'nest_asyncio', ], extras_require={ "test": [ From cbb1f439779ea55799cc868470e787a459a49953 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 21:03:49 +0000 Subject: [PATCH 0694/1195] Automated Changelog Entry for 6.6.1 on main --- CHANGELOG.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3352bbba7..2b6a57065 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ +## 6.6.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.6.0...bdce14b32ca8cc8f4b1635ea47200f0828ec1e05)) + +### Bugs fixed + +- PR: do_one_iteration is a coroutine [#830](https://github.com/ipython/ipykernel/pull/830) ([@impact27](https://github.com/impact27)) + +### Maintenance and upkeep improvements + +- Clean python 2 artifacts. Fix #826 [#827](https://github.com/ipython/ipykernel/pull/827) ([@penguinolog](https://github.com/penguinolog)) + +### Documentation improvements + +- Fix title position in changelog [#828](https://github.com/ipython/ipykernel/pull/828) ([@fcollonval](https://github.com/fcollonval)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2021-12-01&to=2022-01-03&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2021-12-01..2022-01-03&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2021-12-01..2022-01-03&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afcollonval+updated%3A2021-12-01..2022-01-03&type=Issues) | [@impact27](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aimpact27+updated%3A2021-12-01..2022-01-03&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2021-12-01..2022-01-03&type=Issues) | [@penguinolog](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apenguinolog+updated%3A2021-12-01..2022-01-03&type=Issues) + + + ## 6.6.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.5.1...9566304175d844c23a1f2b1d70c10df475ed2868)) @@ -27,8 +51,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2021-11-18..2021-12-01&type=Issues) | [@dlukes](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adlukes+updated%3A2021-11-18..2021-12-01&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2021-11-18..2021-12-01&type=Issues) | [@fasiha](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afasiha+updated%3A2021-11-18..2021-12-01&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2021-11-18..2021-12-01&type=Issues) | [@Kojoley](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AKojoley+updated%3A2021-11-18..2021-12-01&type=Issues) - - ## 6.5.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.5.0...1ef2017781435d54348fbb170b8c5d096e3e1351)) From c9e2917ebfd167430e78229668f437219afc978b Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 3 Jan 2022 21:10:01 +0000 Subject: [PATCH 0695/1195] Publish 6.6.1 SHA256 hashes: ipykernel-6.6.1-py3-none-any.whl: de99f6c1caa72578305cc96122ee3a19669e9c1958694a2b564ed1be28240ab9 ipykernel-6.6.1.tar.gz: 91ff0058b45660aad4a68088041059c0d378cd53fc8aff60e5abc91bcc049353 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index ae135677a..49d6301ee 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.6.0' +__version__ = '6.6.1' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index f41667f4c..1d71d84e2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.6.0" +current = "6.6.1" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From ca794bf0dc6be23bae53e53205ce0972baa073a4 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 7 Jan 2022 13:19:35 +0100 Subject: [PATCH 0696/1195] Remove impossible skipif. According to the dependencies in setup.py those will never be skipped. --- ipykernel/tests/test_async.py | 7 ------- ipykernel/tests/test_eventloop.py | 1 - ipykernel/tests/test_kernel.py | 4 ---- 3 files changed, 12 deletions(-) diff --git a/ipykernel/tests/test_async.py b/ipykernel/tests/test_async.py index d1f327bff..9727f8376 100644 --- a/ipykernel/tests/test_async.py +++ b/ipykernel/tests/test_async.py @@ -26,13 +26,7 @@ def teardown_function(): KM.shutdown_kernel(now=True) -skip_without_async = pytest.mark.skipif( - sys.version_info < (3, 5) or V(IPython.__version__) < V("7.0"), - reason="IPython >=7 with async/await required", -) - -@skip_without_async def test_async_await(): flush_channels(KC) msg_id, content = execute("import asyncio; await asyncio.sleep(0.1)", KC) @@ -40,7 +34,6 @@ def test_async_await(): @pytest.mark.parametrize("asynclib", ["asyncio", "trio", "curio"]) -@skip_without_async def test_async_interrupt(asynclib, request): try: __import__(asynclib) diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index 418c7d34b..1f25d97e2 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -28,7 +28,6 @@ def teardown(): """ -@pytest.mark.skipif(sys.version_info < (3, 5), reason="async/await syntax required") @pytest.mark.skipif(tornado.version_info < (5,), reason="only relevant on tornado 5") def test_asyncio_interrupt(): flush_channels(KC) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 5b9b26682..2e9d5cbbc 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -415,10 +415,6 @@ def test_interrupt_with_message(): validate_message(reply, 'execute_reply', msg_id) -@pytest.mark.skipif( - version.parse(IPython.__version__) < version.parse("7.14.0"), - reason="Need new IPython" -) def test_interrupt_during_pdb_set_trace(): """ The kernel exits after being interrupted while waiting in pdb.set_trace(). From 94899897af47145f0ee7e505526d1aec5c38dade Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 7 Jan 2022 13:28:11 +0100 Subject: [PATCH 0697/1195] Remove pipx to fix conflicts. Not sure why it is/was installed. Remove dependencies as well that seem unnecessary and install pipdeptree to make it easier to find those issues later. --- .github/workflows/ci.yml | 4 ++++ setup.py | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f70f151b..778f4e248 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,10 @@ jobs: - name: List installed packages run: | + pip uninstall pipx -y + pip install pipdeptree + pipdeptree + pipdeptree --reverse pip freeze pip check diff --git a/setup.py b/setup.py index f2c0719c7..95dffbc66 100644 --- a/setup.py +++ b/setup.py @@ -61,8 +61,6 @@ def run(self): keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], python_requires='>=3.7', install_requires=[ - 'importlib-metadata<5;python_version<"3.8.0"', - 'argcomplete>=1.12.3;python_version<"3.8.0"', 'debugpy>=1.0.0,<2.0', 'ipython>=7.23.1', 'traitlets>=5.1.0,<6.0', From 22284481b58fa223d59d8e160877e373e5484d14 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 7 Jan 2022 14:19:37 +0100 Subject: [PATCH 0698/1195] Skip on PyPy, seem to fail. --- ipykernel/tests/test_kernel.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 6702b6c15..fdb614439 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -425,6 +425,10 @@ def test_interrupt_with_message(): validate_message(reply, 'execute_reply', msg_id) +@pytest.mark.skipif( + "__pypy__" in sys.builtin_module_names, + reason="fails on pypy", +) @pytest.mark.skipif( version.parse(IPython.__version__) < version.parse("7.14.0"), reason="Need new IPython" From fdd1bac892dc4f943856d0b6991f2444556074c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Collonval?= Date: Fri, 7 Jan 2022 15:09:11 +0100 Subject: [PATCH 0699/1195] Skip test broken by Python 3.10 for now --- ipykernel/tests/test_debugger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index bdfa41468..ab9f13a2c 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -1,3 +1,4 @@ +import sys import pytest from .utils import TIMEOUT, new_kernel, get_reply @@ -161,6 +162,7 @@ def test_stop_on_breakpoint(kernel_with_debug): assert msg["content"]["body"]["reason"] == "breakpoint" +@pytest.mark.skipif(sys.version_info >= (3, 10), reason="TODO Does not work on Python 3.10") def test_breakpoint_in_cell_with_leading_empty_lines(kernel_with_debug): code = """ def f(a, b): From b4586155a796b6c8a15b36ecfc9b395dc91bb9fc Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Wed, 13 Oct 2021 15:54:06 +0200 Subject: [PATCH 0700/1195] add usage_request and usage_reply based on psutil --- ipykernel/kernelbase.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index ceeebc23a..342ccd6fa 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -16,6 +16,7 @@ import time import uuid import warnings +import psutil try: # jupyter_client >= 5, use tz-aware now @@ -204,7 +205,7 @@ def _parent_header(self): 'apply_request', ] # add deprecated ipyparallel control messages - control_msg_types = msg_types + ['clear_request', 'abort_request', 'debug_request'] + control_msg_types = msg_types + ['clear_request', 'abort_request', 'debug_request', 'usage_request'] def __init__(self, **kwargs): super().__init__(**kwargs) @@ -856,10 +857,26 @@ async def debug_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content reply_content = json_clean(reply_content) + reply_content['hello'] = 'hello' reply_msg = self.session.send(stream, 'debug_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) + async def usage_request(self, stream, ident, parent): + content = parent['content'] + reply_content = self.do_debug_request(content) + if inspect.isawaitable(reply_content): + reply_content = await reply_content + reply_content = json_clean(reply_content) + reply_content['cpu_percent'] = psutil.cpu_percent() + reply_content['virtual_memory'] = psutil.virtual_memory() + reply_content['virtual_memory_dict'] = dict(psutil.virtual_memory()._asdict()) + reply_content['virtual_memory_percent'] = psutil.virtual_memory().percent + reply_content['available_memory_percentage'] = psutil.virtual_memory().available * 100 / psutil.virtual_memory().total + reply_msg = self.session.send(stream, 'usage_reply', reply_content, + parent, ident) + self.log.debug("%s", reply_msg) + async def do_debug_request(self, msg): raise NotImplementedError From 5602f4340b2ad0719d2ba19a8bb864b95afb7595 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 22 Nov 2021 07:15:26 +0100 Subject: [PATCH 0701/1195] remove uneeded code --- ipykernel/kernelbase.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 342ccd6fa..471b6a4b3 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -857,7 +857,6 @@ async def debug_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content reply_content = json_clean(reply_content) - reply_content['hello'] = 'hello' reply_msg = self.session.send(stream, 'debug_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) From 95a65d5630e266902e54e2de0dd709331c038c37 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 22 Nov 2021 07:36:31 +0100 Subject: [PATCH 0702/1195] guard on first cpu request returning None or 0.0 --- ipykernel/kernelbase.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 471b6a4b3..baa6a6a9f 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -867,7 +867,11 @@ async def usage_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content reply_content = json_clean(reply_content) - reply_content['cpu_percent'] = psutil.cpu_percent() + cpu_percent = psutil.cpu_percent() + # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent + # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. + if cpu_percent != None and cpu_percent != 0.0: + reply_content['cpu_percent'] = cpu_percent reply_content['virtual_memory'] = psutil.virtual_memory() reply_content['virtual_memory_dict'] = dict(psutil.virtual_memory()._asdict()) reply_content['virtual_memory_percent'] = psutil.virtual_memory().percent From 815e015d99414701f60e951b7fba463c56ef56a3 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 22 Nov 2021 07:41:21 +0100 Subject: [PATCH 0703/1195] only return the memory usage as a dict --- ipykernel/kernelbase.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index baa6a6a9f..f72448f44 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -872,10 +872,7 @@ async def usage_request(self, stream, ident, parent): # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. if cpu_percent != None and cpu_percent != 0.0: reply_content['cpu_percent'] = cpu_percent - reply_content['virtual_memory'] = psutil.virtual_memory() - reply_content['virtual_memory_dict'] = dict(psutil.virtual_memory()._asdict()) - reply_content['virtual_memory_percent'] = psutil.virtual_memory().percent - reply_content['available_memory_percentage'] = psutil.virtual_memory().available * 100 / psutil.virtual_memory().total + reply_content['virtual_memory'] = dict(psutil.virtual_memory()._asdict()) reply_msg = self.session.send(stream, 'usage_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) From 28172a1c0b9994c6af61468fac7665e923d115cb Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Sat, 4 Dec 2021 09:37:42 +0100 Subject: [PATCH 0704/1195] do not call debug_request on usage_request --- ipykernel/kernelbase.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index f72448f44..680a705ec 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -862,11 +862,7 @@ async def debug_request(self, stream, ident, parent): self.log.debug("%s", reply_msg) async def usage_request(self, stream, ident, parent): - content = parent['content'] - reply_content = self.do_debug_request(content) - if inspect.isawaitable(reply_content): - reply_content = await reply_content - reply_content = json_clean(reply_content) + reply_content = {} cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. From 4c471547f71556bd4c84d360dcd2dbeadab03c6d Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 21 Dec 2021 08:32:46 +0100 Subject: [PATCH 0705/1195] Add kernel cpu and memory usage information --- ipykernel/kernelbase.py | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 680a705ec..0e1bbbc4c 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -16,7 +16,10 @@ import time import uuid import warnings -import psutil +try: + import psutil +except ImportError: + psutil = None try: # jupyter_client >= 5, use tz-aware now @@ -861,14 +864,37 @@ async def debug_request(self, stream, ident, parent): parent, ident) self.log.debug("%s", reply_msg) + # Taken from https://github.com/jupyter-server/jupyter-resource-usage/blob/e6ec53fa69fdb6de8e878974bcff006310658408/jupyter_resource_usage/metrics.py#L16 + def get_process_metric_value(self, process, name, attribute=None): + try: + # psutil.Process methods will either return... + metric_value = getattr(process, name)() + if attribute is not None: # ... a named tuple + return getattr(metric_value, attribute) + else: # ... or a number + return metric_value + # Avoid littering logs with stack traces + # complaining about dead processes + except BaseException: + return 0 + async def usage_request(self, stream, ident, parent): reply_content = {} + if psutil is None: + return reply_content + current_process = psutil.Process() + all_processes = [current_process] + current_process.children(recursive=True) + process_metric_value = lambda process, name, attribute: self.get_process_metric_value( + process, name, attribute + ) + reply_content['kernel_cpu'] = sum([process_metric_value(process, 'cpu_percent', None) for process in all_processes]) + reply_content['kernel_memory'] = sum([process_metric_value(process, 'memory_info', 'rss') for process in all_processes]) cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. if cpu_percent != None and cpu_percent != 0.0: - reply_content['cpu_percent'] = cpu_percent - reply_content['virtual_memory'] = dict(psutil.virtual_memory()._asdict()) + reply_content['host_cpu_percent'] = cpu_percent + reply_content['host_virtual_memory'] = dict(psutil.virtual_memory()._asdict()) reply_msg = self.session.send(stream, 'usage_reply', reply_content, parent, ident) self.log.debug("%s", reply_msg) From 4c437da92edce97664b1c1928b9fea51e4d64842 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Fri, 7 Jan 2022 11:34:44 +0100 Subject: [PATCH 0706/1195] return a None value instead of 0 on exception --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 0e1bbbc4c..f162a1b6a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -876,7 +876,7 @@ def get_process_metric_value(self, process, name, attribute=None): # Avoid littering logs with stack traces # complaining about dead processes except BaseException: - return 0 + return None async def usage_request(self, stream, ident, parent): reply_content = {} From 1af59538152723e1795b885f4add63b86542564f Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Fri, 7 Jan 2022 12:12:10 +0100 Subject: [PATCH 0707/1195] remove unneeded lambda to get the metric values --- ipykernel/kernelbase.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index f162a1b6a..34119dc70 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -884,9 +884,7 @@ async def usage_request(self, stream, ident, parent): return reply_content current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) - process_metric_value = lambda process, name, attribute: self.get_process_metric_value( - process, name, attribute - ) + process_metric_value = self.get_process_metric_value reply_content['kernel_cpu'] = sum([process_metric_value(process, 'cpu_percent', None) for process in all_processes]) reply_content['kernel_memory'] = sum([process_metric_value(process, 'memory_info', 'rss') for process in all_processes]) cpu_percent = psutil.cpu_percent() From e01150731b245306027c98241ba2a1b136611882 Mon Sep 17 00:00:00 2001 From: Kyle Cutler Date: Fri, 7 Jan 2022 15:07:04 -0800 Subject: [PATCH 0708/1195] Normalize debugger temp file paths on Windows --- ipykernel/compiler.py | 10 ++++++++-- ipykernel/debugger.py | 2 +- setup.py | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 0b1384021..472116beb 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -39,8 +39,14 @@ def murmur2_x86(data, seed): def get_tmp_directory(): tmp_dir = tempfile.gettempdir() + if os.name == 'nt': + try: + import win32file + tmp_dir = win32file.GetLongPathName(tmp_dir) + except: + pass pid = os.getpid() - return tmp_dir + '/ipykernel_' + str(pid) + return tmp_dir + os.sep + 'ipykernel_' + str(pid) def get_tmp_hash_seed(): @@ -52,7 +58,7 @@ def get_file_name(code): cell_name = os.environ.get("IPYKERNEL_CELL_NAME") if cell_name is None: name = murmur2_x86(code, get_tmp_hash_seed()) - cell_name = get_tmp_directory() + '/' + str(name) + '.py' + cell_name = get_tmp_directory() + os.sep + str(name) + '.py' return cell_name diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 62c6a5407..d1ce0ff8f 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -507,7 +507,7 @@ async def debugInfo(self, message): 'isStarted': self.is_started, 'hashMethod': 'Murmur2', 'hashSeed': get_tmp_hash_seed(), - 'tmpFilePrefix': get_tmp_directory() + '/', + 'tmpFilePrefix': get_tmp_directory() + os.sep, 'tmpFileSuffix': '.py', 'breakpoints': breakpoint_list, 'stoppedThreads': self.stopped_threads, diff --git a/setup.py b/setup.py index 95dffbc66..dfff0d5dc 100644 --- a/setup.py +++ b/setup.py @@ -69,6 +69,7 @@ def run(self): 'matplotlib-inline>=0.1.0,<0.2.0', 'appnope;platform_system=="Darwin"', 'nest_asyncio', + 'pywin32;platform_system=="Windows"' ], extras_require={ "test": [ From 4d873e0ac59cc160859afab2e5de256a62aff9d3 Mon Sep 17 00:00:00 2001 From: Kyle Cutler Date: Mon, 10 Jan 2022 09:08:39 -0800 Subject: [PATCH 0709/1195] Use ctypes instead of pywin32 --- ipykernel/compiler.py | 33 ++++++++++++++++++++++++++------- setup.py | 1 - 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 472116beb..a38e45378 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -1,6 +1,7 @@ from IPython.core.compilerop import CachingCompiler import tempfile import os +import sys def murmur2_x86(data, seed): @@ -36,15 +37,33 @@ def murmur2_x86(data, seed): return h +convert_to_long_pathname = lambda filename:filename + +if sys.platform == 'win32': + try: + import ctypes + from ctypes.wintypes import MAX_PATH, LPCWSTR, LPWSTR, DWORD + + _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW + _GetLongPathName.argtypes = [LPCWSTR, LPWSTR, DWORD] + _GetLongPathName.restype = DWORD + + def _convert_to_long_pathname(filename): + buf = ctypes.create_unicode_buffer(MAX_PATH) + rv = _GetLongPathName(filename, buf, MAX_PATH) + if rv != 0 and rv <= MAX_PATH: + filename = buf.value + return filename + + # test that it works + _convert_to_long_pathname(__file__) + except: + pass + else: + convert_to_long_pathname = _convert_to_long_pathname def get_tmp_directory(): - tmp_dir = tempfile.gettempdir() - if os.name == 'nt': - try: - import win32file - tmp_dir = win32file.GetLongPathName(tmp_dir) - except: - pass + tmp_dir = convert_to_long_pathname(tempfile.gettempdir()) pid = os.getpid() return tmp_dir + os.sep + 'ipykernel_' + str(pid) diff --git a/setup.py b/setup.py index dfff0d5dc..95dffbc66 100644 --- a/setup.py +++ b/setup.py @@ -69,7 +69,6 @@ def run(self): 'matplotlib-inline>=0.1.0,<0.2.0', 'appnope;platform_system=="Darwin"', 'nest_asyncio', - 'pywin32;platform_system=="Windows"' ], extras_require={ "test": [ From 6ffb3d33f4a24d06b09edb85d53e06aa6ae6d73a Mon Sep 17 00:00:00 2001 From: Kyle Cutler Date: Mon, 10 Jan 2022 09:58:25 -0800 Subject: [PATCH 0710/1195] Add test, clarify reason for inline check --- ipykernel/compiler.py | 2 +- ipykernel/tests/test_debugger.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index a38e45378..95770e7be 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -55,7 +55,7 @@ def _convert_to_long_pathname(filename): filename = buf.value return filename - # test that it works + # test that it works so if there are any issues we fail just once here _convert_to_long_pathname(__file__) except: pass diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index ab9f13a2c..65f5711e1 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -271,3 +271,9 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): ) assert reply["body"]["data"] == {"text/plain": locals_[0]["value"]} + + +def test_convert_to_long_pathname(): + if sys.platform == 'win32': + from ipykernel.compiler import _convert_to_long_pathname + _convert_to_long_pathname(__file__) \ No newline at end of file From aa0d637bb8a7094f4c29259c078569788ad9bc12 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 12 Jan 2022 10:33:48 +0100 Subject: [PATCH 0711/1195] Removed DebugStdLib from arguments of attach --- ipykernel/debugger.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index d1ce0ff8f..72fb25983 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -477,8 +477,11 @@ async def attach(self, message): 'port': port } message['arguments']['logToFile'] = True + # Reverts that option for now since it leads to spurious break of the code + # in ipykernel source and resuming the execution leads to several errors + # in the kernel. # Set debugOptions for breakpoints in python standard library source. - message['arguments']['debugOptions'] = [ 'DebugStdLib' ] + # message['arguments']['debugOptions'] = [ 'DebugStdLib' ] return await self._forward_message(message) async def configurationDone(self, message): From 3d4255da1d33361477cca8b75922911dd24cfac9 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Jan 2022 15:08:34 +0000 Subject: [PATCH 0712/1195] Automated Changelog Entry for 6.7.0 on main --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b6a57065..d8269ac9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ +## 6.7.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.6.1...0be80cbc81927f4fb20343840bf5834b48884717)) + +### Enhancements made + +- Add usage_request and usage_reply based on psutil [#805](https://github.com/ipython/ipykernel/pull/805) ([@echarles](https://github.com/echarles)) + +### Bugs fixed + +- Removed DebugStdLib from arguments of attach [#839](https://github.com/ipython/ipykernel/pull/839) ([@JohanMabille](https://github.com/JohanMabille)) +- Normalize debugger temp file paths on Windows [#838](https://github.com/ipython/ipykernel/pull/838) ([@kycutler](https://github.com/kycutler)) +- Breakpoint in cell with leading empty lines may be ignored [#829](https://github.com/ipython/ipykernel/pull/829) ([@fcollonval](https://github.com/fcollonval)) + +### Maintenance and upkeep improvements + +- Skip on PyPy, seem to fail. [#837](https://github.com/ipython/ipykernel/pull/837) ([@Carreau](https://github.com/Carreau)) +- Remove pipx to fix conflicts [#835](https://github.com/ipython/ipykernel/pull/835) ([@Carreau](https://github.com/Carreau)) +- Remove impossible skipif. [#834](https://github.com/ipython/ipykernel/pull/834) ([@Carreau](https://github.com/Carreau)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-01-03&to=2022-01-13&type=c)) + +[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2022-01-03..2022-01-13&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-01-03..2022-01-13&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afcollonval+updated%3A2022-01-03..2022-01-13&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2022-01-03..2022-01-13&type=Issues) | [@kycutler](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akycutler+updated%3A2022-01-03..2022-01-13&type=Issues) + + + ## 6.6.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.6.0...bdce14b32ca8cc8f4b1635ea47200f0828ec1e05)) @@ -24,8 +52,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2021-12-01..2022-01-03&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2021-12-01..2022-01-03&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afcollonval+updated%3A2021-12-01..2022-01-03&type=Issues) | [@impact27](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aimpact27+updated%3A2021-12-01..2022-01-03&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2021-12-01..2022-01-03&type=Issues) | [@penguinolog](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apenguinolog+updated%3A2021-12-01..2022-01-03&type=Issues) - - ## 6.6.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.5.1...9566304175d844c23a1f2b1d70c10df475ed2868)) From 996dd5c0433ea73a10ad113a8886b8766784c3cd Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Jan 2022 15:21:01 +0000 Subject: [PATCH 0713/1195] Publish 6.7.0 SHA256 hashes: ipykernel-6.7.0-py3-none-any.whl: 6203ccd5510ff148e9433fd4a2707c5ce8d688f026427f46e13d7ebf9b3e9787 ipykernel-6.7.0.tar.gz: d82b904fdc2fd8c7b1fbe0fa481c68a11b4cd4c8ef07e6517da1f10cc3114d24 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 49d6301ee..df94fcbab 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.6.1' +__version__ = '6.7.0' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index 1d71d84e2..fd83b539b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.6.1" +current = "6.7.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 34ee173ab2623f7d1dd8e3b789e47e8277bf0fdf Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 14 Jan 2022 22:53:25 +0100 Subject: [PATCH 0714/1195] Handled AllThreadsContinued and workaround for wrong threadId in continued event after a next request --- ipykernel/debugger.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 72fb25983..6a73ea2a4 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,5 +1,6 @@ import os import re +import threading import zmq from zmq.utils import jsonapi @@ -284,7 +285,7 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): self.static_debug_handlers[msg_type] = getattr(self, msg_type) self.breakpoint_list = {} - self.stopped_threads = [] + self.stopped_threads = set() self.debugpy_initialized = False self._removed_cleanup = {} @@ -297,12 +298,21 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): def _handle_event(self, msg): if msg['event'] == 'stopped': - self.stopped_threads.append(msg['body']['threadId']) + self.stopped_threads.add(msg['body']['threadId']) elif msg['event'] == 'continued': try: - self.stopped_threads.remove(msg['body']['threadId']) + if msg['allThreadsContinued']: + self.stopped_threads = set() + else: + self.stopped_threads.remove(msg['body']['threadId']) except Exception: - pass + # Workaround for debugpy/pydev not setting the correct threadId + # after a next request. Does not work if a the code executed on + # the shell spawns additional threads + if len(self.stopped_threads) == 1: + self.stopped_threads = set() + else: + raise Exception('threadId from continued event not in stopped threads set') self.event_callback(msg) async def _forward_message(self, msg): @@ -513,7 +523,7 @@ async def debugInfo(self, message): 'tmpFilePrefix': get_tmp_directory() + os.sep, 'tmpFileSuffix': '.py', 'breakpoints': breakpoint_list, - 'stoppedThreads': self.stopped_threads, + 'stoppedThreads': list(self.stopped_threads), 'richRendering': True, 'exceptionPaths': ['Python Exceptions'] } @@ -613,7 +623,7 @@ async def process_request(self, message): if message['command'] == 'disconnect': self.stop() self.breakpoint_list = {} - self.stopped_threads = [] + self.stopped_threads = set() self.is_started = False self.log.info('The debugger has stopped') From 14f58f362bc901670c443afe491867e6aed76fd5 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Wed, 1 Dec 2021 14:35:40 +0100 Subject: [PATCH 0715/1195] Add support for the debug modules request --- ipykernel/debugger.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 6a73ea2a4..03c808c7e 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,3 +1,4 @@ +import sys import os import re import threading @@ -265,7 +266,8 @@ class Debugger: # Requests that can be handled even if the debugger is not running static_debug_msg_types = [ - 'debugInfo', 'inspectVariables', 'richInspectVariables' + 'debugInfo', 'inspectVariables', + 'richInspectVariables', 'modules' ] def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): @@ -591,6 +593,24 @@ async def richInspectVariables(self, message): reply["success"] = True return reply + async def modules(self, message): + modules = [sys.modules[name] for name in sys.modules] + mods = [] + for module in modules: + m = str(module) + if ".py'" in m: + a = {} + x = re.findall(r"'(.*?)'", m) + a[x[0]] = x[1] + mods.append(a) + reply = { + 'body': { + 'modules': mods, + 'totalModules': len(modules) + } + } + return reply + async def process_request(self, message): reply = {} From 561d435cca2a9fe6dc5583bed1828eb41b3413bd Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Wed, 26 Jan 2022 06:33:59 +0100 Subject: [PATCH 0716/1195] simpler code block to get the modules for the debugger --- ipykernel/debugger.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 03c808c7e..973cbf268 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -593,22 +593,17 @@ async def richInspectVariables(self, message): reply["success"] = True return reply - async def modules(self, message): - modules = [sys.modules[name] for name in sys.modules] + async def modules(self, message): + modules = sys.modules.values() mods = [] for module in modules: - m = str(module) - if ".py'" in m: - a = {} - x = re.findall(r"'(.*?)'", m) - a[x[0]] = x[1] - mods.append(a) - reply = { - 'body': { - 'modules': mods, - 'totalModules': len(modules) - } - } + filename = getattr(getattr(module, "__spec__", None), "origin", None) + if filename and filename.endswith(".py"): + mods.append({module.__name__: filename}) + + reply = {"body": {"modules": mods, "totalModules": len(modules)}} + return reply + reply = {"body": {"modules": mods, "totalModules": len(modules)}} return reply async def process_request(self, message): From 28a9b0f3792cb15be2d5926200fe0921b1b46f40 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Wed, 26 Jan 2022 06:35:36 +0100 Subject: [PATCH 0717/1195] implement startModule and moduleCount as specified by the DAP modules request --- ipykernel/debugger.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 973cbf268..a80d88b27 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -594,17 +594,18 @@ async def richInspectVariables(self, message): return reply async def modules(self, message): - modules = sys.modules.values() + modules = list(sys.modules.values()) + startModule = message.get('startModule', 0) + moduleCount = message.get('moduleCount', len(modules)) mods = [] - for module in modules: + for i in range(startModule, moduleCount): + module = modules[i] filename = getattr(getattr(module, "__spec__", None), "origin", None) if filename and filename.endswith(".py"): mods.append({module.__name__: filename}) reply = {"body": {"modules": mods, "totalModules": len(modules)}} return reply - reply = {"body": {"modules": mods, "totalModules": len(modules)}} - return reply async def process_request(self, message): reply = {} From 431022daf8ea37685142d38ed9a38f67cc4db7bd Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 1 Feb 2022 07:59:29 +0100 Subject: [PATCH 0718/1195] fix the debug modules model --- ipykernel/debugger.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index a80d88b27..daa1b7fe1 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -602,7 +602,10 @@ async def modules(self, message): module = modules[i] filename = getattr(getattr(module, "__spec__", None), "origin", None) if filename and filename.endswith(".py"): - mods.append({module.__name__: filename}) + mods.append({ + 'id': module.__name__, + 'name': filename + }) reply = {"body": {"modules": mods, "totalModules": len(modules)}} return reply From ab7ed4931a8a73bc034754d544ab4c15b952fe59 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 1 Feb 2022 08:41:22 +0100 Subject: [PATCH 0719/1195] use simple quote in the modules method --- ipykernel/debugger.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index daa1b7fe1..fe6b13047 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -600,14 +600,14 @@ async def modules(self, message): mods = [] for i in range(startModule, moduleCount): module = modules[i] - filename = getattr(getattr(module, "__spec__", None), "origin", None) - if filename and filename.endswith(".py"): + filename = getattr(getattr(module, '__spec__', None), 'origin', None) + if filename and filename.endswith('.py'): mods.append({ 'id': module.__name__, 'name': filename }) - reply = {"body": {"modules": mods, "totalModules": len(modules)}} + reply = {'body': {'modules': mods, 'totalModules': len(modules)}} return reply async def process_request(self, message): From 6822ada6648b9b8811ddaa1793f4cb69ed720c28 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 1 Feb 2022 16:03:42 +0100 Subject: [PATCH 0720/1195] use incremental id in the modules response --- ipykernel/debugger.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index fe6b13047..be09a72de 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -603,8 +603,9 @@ async def modules(self, message): filename = getattr(getattr(module, '__spec__', None), 'origin', None) if filename and filename.endswith('.py'): mods.append({ - 'id': module.__name__, - 'name': filename + 'id': i, + 'name': module.__name__, + 'path': filename }) reply = {'body': {'modules': mods, 'totalModules': len(modules)}} From d482dbfa4248f4b66d0692470ef9fc2fa78aa357 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 1 Feb 2022 11:06:23 +0100 Subject: [PATCH 0721/1195] Handle all threads stopped correctly --- ipykernel/control.py | 3 +- ipykernel/debugger.py | 54 +++++++++++++++++++++++--------- ipykernel/heartbeat.py | 4 ++- ipykernel/iostream.py | 4 ++- ipykernel/ipkernel.py | 6 ++++ ipykernel/tests/test_debugger.py | 8 ++++- 6 files changed, 60 insertions(+), 19 deletions(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index 4ba21dff5..71c61764b 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -10,12 +10,13 @@ class ControlThread(Thread): def __init__(self, **kwargs): - Thread.__init__(self, **kwargs) + Thread.__init__(self, name="Control", **kwargs) self.io_loop = IOLoop(make_current=False) self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True def run(self): + self.name="Control" self.io_loop.make_current() try: self.io_loop.start() diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index a80d88b27..94e209ba4 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -277,6 +277,7 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): self.session = session self.is_started = False self.event_callback = event_callback + self.stopped_queue = Queue() self.started_debug_handlers = {} for msg_type in Debugger.started_debug_msg_types: @@ -300,22 +301,19 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): def _handle_event(self, msg): if msg['event'] == 'stopped': - self.stopped_threads.add(msg['body']['threadId']) + if msg['body']['allThreadsStopped']: + self.stopped_queue.put_nowait(msg) + # Do not forward the event now, will be done in the handle_stopped_event + return + else: + self.stopped_threads.add(msg['body']['threadId']) + self.event_callback(msg) elif msg['event'] == 'continued': - try: - if msg['allThreadsContinued']: - self.stopped_threads = set() - else: - self.stopped_threads.remove(msg['body']['threadId']) - except Exception: - # Workaround for debugpy/pydev not setting the correct threadId - # after a next request. Does not work if a the code executed on - # the shell spawns additional threads - if len(self.stopped_threads) == 1: - self.stopped_threads = set() - else: - raise Exception('threadId from continued event not in stopped threads set') - self.event_callback(msg) + if msg['body']['allThreadsContinued']: + self.stopped_threads = set() + else: + self.stopped_threads.remove(msg['body']['threadId']) + self.event_callback(msg) async def _forward_message(self, msg): return await self.debugpy_client.send_dap_request(msg) @@ -334,6 +332,32 @@ def _build_variables_response(self, request, variables): } return reply + def _accept_stopped_thread(self, thread_name): + # TODO: identify Thread-2, Thread-3 and Thread-4. These are NOT + # Control, IOPub or Heartbeat threads + forbid_list = [ + 'IPythonHistorySavingThread', + 'Thread-2', + 'Thread-3', + 'Thread-4' + ] + return thread_name not in forbid_list + + async def handle_stopped_event(self): + # Wait for a stopped event message in the stopped queue + # This message is used for triggering the 'threads' request + event = await self.stopped_queue.get() + req = { + 'seq': event['seq'] + 1, + 'type': 'request', + 'command': 'threads' + } + rep = await self._forward_message(req) + for t in rep['body']['threads']: + if self._accept_stopped_thread(t['name']): + self.stopped_threads.add(t['id']) + self.event_callback(event) + @property def tcp_client(self): return self.debugpy_client diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 6d65a6c7e..bebf21e3f 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -32,7 +32,7 @@ class Heartbeat(Thread): def __init__(self, context, addr=None): if addr is None: addr = ('tcp', localhost(), 0) - Thread.__init__(self) + Thread.__init__(self, name="Heartbeat") self.context = context self.transport, self.ip, self.port = addr self.original_port = self.port @@ -42,6 +42,7 @@ def __init__(self, context, addr=None): self.daemon = True self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True + self.name="Heartbeat" def pick_port(self): if self.transport == 'tcp': @@ -89,6 +90,7 @@ def _bind_socket(self): return def run(self): + self.name="Heartbeat" self.socket = self.context.socket(zmq.ROUTER) self.socket.linger = 1000 try: diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index f4c4ad0f8..f7161daa5 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -70,10 +70,11 @@ def __init__(self, socket, pipe=False): self._events = deque() self._event_pipes = WeakSet() self._setup_event_pipe() - self.thread = threading.Thread(target=self._thread_main) + self.thread = threading.Thread(target=self._thread_main, name="IOPub") self.thread.daemon = True self.thread.pydev_do_not_trace = True self.thread.is_pydev_daemon_thread = True + self.thread.name="IOPub" def _thread_main(self): """The inner loop that's actually run in a thread""" @@ -176,6 +177,7 @@ def _check_mp_mode(self): def start(self): """Start the IOPub thread""" + self.thread.name="IOPub" self.thread.start() # make sure we don't prevent process exit # I'm not sure why setting daemon=True above isn't enough, but it doesn't appear to be. diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index fd2462043..2941ce34c 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -169,6 +169,10 @@ def dispatch_debugpy(self, msg): def banner(self): return self.shell.banner + async def poll_stopped_queue(self): + while True: + await self.debugger.handle_stopped_event() + def start(self): self.shell.exit_now = False if self.debugpy_stream is None: @@ -176,6 +180,8 @@ def start(self): else: self.debugpy_stream.on_recv(self.dispatch_debugpy, copy=False) super().start() + if self.debugpy_stream: + asyncio.run_coroutine_threadsafe(self.poll_stopped_queue(), self.control_thread.io_loop.asyncio_loop) def set_parent(self, ident, parent, channel='shell'): """Overridden from parent to tell the display hook and output streams diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index 65f5711e1..43a96ef22 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -227,6 +227,7 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): f(2, 3)""" + r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) source = r["body"]["sourcePath"] @@ -246,6 +247,11 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): kernel_with_debug.execute(code) + # Wait for stop on breakpoint + msg = {"msg_type": "", "content": {}} + while msg.get('msg_type') != 'debug_event' or msg["content"].get("event") != "stopped": + msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) + stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})[ "body" ]["stackFrames"] @@ -276,4 +282,4 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): def test_convert_to_long_pathname(): if sys.platform == 'win32': from ipykernel.compiler import _convert_to_long_pathname - _convert_to_long_pathname(__file__) \ No newline at end of file + _convert_to_long_pathname(__file__) From 5abb3ade1dd54ec71f485402eddc6f74d8cdbc5a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 1 Feb 2022 16:14:33 -0600 Subject: [PATCH 0722/1195] Update ipykernel/control.py --- ipykernel/control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index 71c61764b..cba8b0994 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -16,7 +16,7 @@ def __init__(self, **kwargs): self.is_pydev_daemon_thread = True def run(self): - self.name="Control" + self.name = "Control" self.io_loop.make_current() try: self.io_loop.start() From 042d555a34d69e0770ddd76aeb528683bd4d3a4d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 1 Feb 2022 16:14:38 -0600 Subject: [PATCH 0723/1195] Update ipykernel/heartbeat.py --- ipykernel/heartbeat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index bebf21e3f..41e0b2739 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -42,7 +42,7 @@ def __init__(self, context, addr=None): self.daemon = True self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True - self.name="Heartbeat" + self.name = "Heartbeat" def pick_port(self): if self.transport == 'tcp': From 3e35b9c86f096ca00e0d108324183bd20222ada0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 1 Feb 2022 16:14:43 -0600 Subject: [PATCH 0724/1195] Update ipykernel/heartbeat.py --- ipykernel/heartbeat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 41e0b2739..77c90b974 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -90,7 +90,7 @@ def _bind_socket(self): return def run(self): - self.name="Heartbeat" + self.name = "Heartbeat" self.socket = self.context.socket(zmq.ROUTER) self.socket.linger = 1000 try: From ded3fc800d527abdfe312ad49664143b26b4c6e6 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 1 Feb 2022 16:14:48 -0600 Subject: [PATCH 0725/1195] Update ipykernel/iostream.py --- ipykernel/iostream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index f7161daa5..41e3ff5aa 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -74,7 +74,7 @@ def __init__(self, socket, pipe=False): self.thread.daemon = True self.thread.pydev_do_not_trace = True self.thread.is_pydev_daemon_thread = True - self.thread.name="IOPub" + self.thread.name = "IOPub" def _thread_main(self): """The inner loop that's actually run in a thread""" From ef8003d01b9902a1c4a4ac4b4e49f8da958d2a60 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 1 Feb 2022 16:14:52 -0600 Subject: [PATCH 0726/1195] Update ipykernel/iostream.py --- ipykernel/iostream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 41e3ff5aa..50f86d243 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -177,7 +177,7 @@ def _check_mp_mode(self): def start(self): """Start the IOPub thread""" - self.thread.name="IOPub" + self.thread.name = "IOPub" self.thread.start() # make sure we don't prevent process exit # I'm not sure why setting daemon=True above isn't enough, but it doesn't appear to be. From 4124840c21bcc148d127bbddf33c4b3736267980 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 1 Feb 2022 16:31:09 -0600 Subject: [PATCH 0727/1195] cancel duplicate runs --- .github/workflows/check-release.yml | 4 ++++ .github/workflows/ci.yml | 4 ++++ .github/workflows/downstream.yml | 4 ++++ .github/workflows/enforce-label.yml | 4 ++++ 4 files changed, 16 insertions(+) diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index 94372cc08..9341bf4e0 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -5,6 +5,10 @@ on: pull_request: branches: ["*"] +concurrency: + group: check-release-${{ github.ref }} + cancel-in-progress: true + jobs: check_release: runs-on: ubuntu-latest diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 778f4e248..5d5a7e773 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: pull_request: branches: "*" +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + jobs: build: runs-on: ${{ matrix.os }}-latest diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 3f1909e02..ccfa015ae 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -6,6 +6,10 @@ on: pull_request: branches: "*" +concurrency: + group: downstream-${{ github.ref }} + cancel-in-progress: true + jobs: downstream1: runs-on: ubuntu-latest diff --git a/.github/workflows/enforce-label.yml b/.github/workflows/enforce-label.yml index 354a0468d..8296e75d4 100644 --- a/.github/workflows/enforce-label.yml +++ b/.github/workflows/enforce-label.yml @@ -1,5 +1,9 @@ name: Enforce PR label +concurrency: + group: label-${{ github.ref }} + cancel-in-progress: true + on: pull_request: types: [labeled, unlabeled, opened, edited, synchronize] From 2d124b1777462830fc1fb1d4593fc19a1253dfd4 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 1 Feb 2022 16:37:56 -0600 Subject: [PATCH 0728/1195] remove fail fast --- .github/workflows/check-release.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index 9341bf4e0..95eced91c 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -15,6 +15,7 @@ jobs: strategy: matrix: group: [check_release, link_check] + fail-fast: false steps: - name: Checkout uses: actions/checkout@v2 From 8e322372d824f8fec38b17ab6ed00cdcf1c2c60e Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Feb 2022 23:36:09 +0000 Subject: [PATCH 0729/1195] Automated Changelog Entry for 6.8.0 on main --- CHANGELOG.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8269ac9b..c2a634d9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ +## 6.8.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.7.0...4e775b70e7e1be7e96fe7c3c747f21f3d93f0181)) + +### Enhancements made + +- Add support for the debug modules request [#816](https://github.com/ipython/ipykernel/pull/816) ([@echarles](https://github.com/echarles)) + +### Bugs fixed + +- Handle all threads stopped correctly [#849](https://github.com/ipython/ipykernel/pull/849) ([@JohanMabille](https://github.com/JohanMabille)) +- Fix the debug modules model [#848](https://github.com/ipython/ipykernel/pull/848) ([@echarles](https://github.com/echarles)) +- Handled AllThreadsContinued and workaround for wrong threadId in cont… [#844](https://github.com/ipython/ipykernel/pull/844) ([@JohanMabille](https://github.com/JohanMabille)) + +### Maintenance and upkeep improvements + +- Cancel duplicate runs [#850](https://github.com/ipython/ipykernel/pull/850) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-01-13&to=2022-02-01&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-01-13..2022-02-01&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-01-13..2022-02-01&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2022-01-13..2022-02-01&type=Issues) + + + ## 6.7.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.6.1...0be80cbc81927f4fb20343840bf5834b48884717)) @@ -28,8 +54,6 @@ [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2022-01-03..2022-01-13&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-01-03..2022-01-13&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afcollonval+updated%3A2022-01-03..2022-01-13&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2022-01-03..2022-01-13&type=Issues) | [@kycutler](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akycutler+updated%3A2022-01-03..2022-01-13&type=Issues) - - ## 6.6.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.6.0...bdce14b32ca8cc8f4b1635ea47200f0828ec1e05)) From ef84cff795f083f13d1892394dc27ca85b18c996 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 1 Feb 2022 23:43:06 +0000 Subject: [PATCH 0730/1195] Publish 6.8.0 SHA256 hashes: ipykernel-6.8.0-py3-none-any.whl: 6c977ead67ec22151993a5f848b97e57a5e771f979b510941e157b2e7fe54184 ipykernel-6.8.0.tar.gz: 67d316d527eca24e3ded45a2b38689615bcda1aa520a11af0accdcea7152c18a --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index df94fcbab..2f97fa64e 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.7.0' +__version__ = '6.8.0' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index fd83b539b..8138b5677 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.7.0" +current = "6.8.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 23c3709573894a2d244fc269ca15e18cb611e18c Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 3 Feb 2022 14:52:33 +0100 Subject: [PATCH 0731/1195] use message queue for abort_queues ensures queued messages are aborted, rather than racing with coroutine processing, which would complete too soon during asynchronous executions --- ipykernel/kernelbase.py | 26 ++++++++++++++++++++++---- ipykernel/tests/test_message_spec.py | 22 +++++++++++++++------- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 34119dc70..622f1ca05 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -671,7 +671,7 @@ async def execute_request(self, stream, ident, parent): self.log.debug("%s", reply_msg) if not silent and reply_msg['content']['status'] == 'error' and stop_on_error: - await self._abort_queues() + self._abort_queues() def do_execute(self, code, silent, store_history=True, user_expressions=None, allow_stdin=False): @@ -974,13 +974,31 @@ def _topic(self, topic): _aborting = Bool(False) - async def _abort_queues(self): - self.shell_stream.flush() + def _abort_queues(self): + # while this flag is true, + # execute requests will be aborted self._aborting = True + self.log.info("Aborting queue") + + # flush streams, so all currently waiting messages + # are added to the queue + self.shell_stream.flush() + + # Callback to signal that we are done aborting def stop_aborting(): self.log.info("Finishing abort") self._aborting = False - asyncio.get_event_loop().call_later(self.stop_on_error_timeout, stop_aborting) + + # put the stop-aborting event on the message queue + # so that all messages already waiting in the queue are aborted + # before we reset the flag + schedule_stop_aborting = partial(self.schedule_dispatch, stop_aborting) + + # if we have a delay, give messages this long to arrive on the queue + # before we stop aborting requests + asyncio.get_event_loop().call_later( + self.stop_on_error_timeout, schedule_stop_aborting + ) def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index b66c124d6..f550b898e 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -341,15 +341,23 @@ def test_execute_stop_on_error(): """execute request should not abort execution queue with stop_on_error False""" flush_channels() - fail = '\n'.join([ - # sleep to ensure subsequent message is waiting in the queue to be aborted - 'import time', - 'time.sleep(0.5)', - 'raise ValueError', - ]) + fail = "\n".join( + [ + # sleep to ensure subsequent message is waiting in the queue to be aborted + # async sleep to ensure coroutines are processing while this happens + "import asyncio", + "await asyncio.sleep(1)", + "raise ValueError()", + ] + ) KC.execute(code=fail) KC.execute(code='print("Hello")') - KC.get_shell_msg(timeout=TIMEOUT) + KC.execute(code='print("world")') + reply = KC.get_shell_msg(timeout=TIMEOUT) + print(reply) + reply = KC.get_shell_msg(timeout=TIMEOUT) + assert reply["content"]["status"] == "aborted" + # second message, too reply = KC.get_shell_msg(timeout=TIMEOUT) assert reply['content']['status'] == 'aborted' From 87d78ae1a0cdb8a25415a9fc2c16c4b50b16c131 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 4 Feb 2022 09:35:23 +0100 Subject: [PATCH 0732/1195] Fixed event forwarding --- ipykernel/debugger.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 076b8d4db..bc26c5189 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -314,6 +314,8 @@ def _handle_event(self, msg): else: self.stopped_threads.remove(msg['body']['threadId']) self.event_callback(msg) + else: + self.event_callback(msg) async def _forward_message(self, msg): return await self.debugpy_client.send_dap_request(msg) From 5dd8d0112ffea3912817961f1fedace76338f7a2 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 17:09:18 +0000 Subject: [PATCH 0733/1195] Automated Changelog Entry for 6.9.0 on main --- CHANGELOG.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2a634d9f..c9c2b63cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ +## 6.9.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.8.0...7a229c6c83d44d315f637ef63159a43c64ec73d6)) + +### Bugs fixed + +- Fixed event forwarding [#855](https://github.com/ipython/ipykernel/pull/855) ([@JohanMabille](https://github.com/JohanMabille)) +- use message queue for abort_queues [#853](https://github.com/ipython/ipykernel/pull/853) ([@minrk](https://github.com/minrk)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-02-01&to=2022-02-07&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-02-01..2022-02-07&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2022-02-01..2022-02-07&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2022-02-01..2022-02-07&type=Issues) + + + ## 6.8.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.7.0...4e775b70e7e1be7e96fe7c3c747f21f3d93f0181)) @@ -26,8 +43,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-01-13..2022-02-01&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-01-13..2022-02-01&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2022-01-13..2022-02-01&type=Issues) - - ## 6.7.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.6.1...0be80cbc81927f4fb20343840bf5834b48884717)) From 6ea9018f9f7e6554181c52ef9610787c2ca05548 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 7 Feb 2022 17:20:49 +0000 Subject: [PATCH 0734/1195] Publish 6.9.0 SHA256 hashes: ipykernel-6.9.0-py3-none-any.whl: 1626b91c50e4605555ac6e5b29f1e5206d299a4a4a21483770a181be97f0f0e0 ipykernel-6.9.0.tar.gz: b556e292dc6fa223f24328b1c936b9c921fafcc2f420bb0d6cfdfc42eaa90225 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 2f97fa64e..24e5e5984 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.8.0' +__version__ = '6.9.0' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index 8138b5677..482986ff9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.8.0" +current = "6.9.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From bdf3bc154d1aa370d6d6f42b29c11c5cc7e2c8d5 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 11 Feb 2022 20:32:55 +0100 Subject: [PATCH 0735/1195] dispatch only accepts coroutines --- ipykernel/kernelbase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 622f1ca05..79b27ce93 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -985,7 +985,8 @@ def _abort_queues(self): self.shell_stream.flush() # Callback to signal that we are done aborting - def stop_aborting(): + # dispatch functions _must_ be async + async def stop_aborting(): self.log.info("Finishing abort") self._aborting = False From 88e842f17a0ee12ba4c7930eec4401bd954c64d3 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 14 Feb 2022 09:13:25 +0100 Subject: [PATCH 0736/1195] enable standard library debugging via config --- ipykernel/debugger.py | 12 +++++++----- ipykernel/ipkernel.py | 3 ++- ipykernel/kernelbase.py | 9 +++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index bc26c5189..e5df66591 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -270,13 +270,14 @@ class Debugger: 'richInspectVariables', 'modules' ] - def __init__(self, log, debugpy_stream, event_callback, shell_socket, session): + def __init__(self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code): self.log = log self.debugpy_client = DebugpyClient(log, debugpy_stream, self._handle_event) self.shell_socket = shell_socket self.session = session self.is_started = False self.event_callback = event_callback + self.just_my_code = just_my_code self.stopped_queue = Queue() self.started_debug_handlers = {} @@ -515,11 +516,12 @@ async def attach(self, message): 'port': port } message['arguments']['logToFile'] = True - # Reverts that option for now since it leads to spurious break of the code - # in ipykernel source and resuming the execution leads to several errors - # in the kernel. + # Experimental option to break in non-user code. + # The ipykernel source is in the call stack, so the user + # has to manipulate the step-over and step-into in a wize way. # Set debugOptions for breakpoints in python standard library source. - # message['arguments']['debugOptions'] = [ 'DebugStdLib' ] + if not self.just_my_code: + message['arguments']['debugOptions'] = [ 'DebugStdLib' ] return await self._forward_message(message) async def configurationDone(self, message): diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 2941ce34c..de4f47069 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -83,7 +83,8 @@ def __init__(self, **kwargs): self.debugpy_stream, self._publish_debug_event, self.debug_shell_socket, - self.session) + self.session, + self.debug_just_my_code) # Initialize the InteractiveShell subclass self.shell = self.shell_class.instance(parent=self, diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 622f1ca05..e7145df4e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -127,6 +127,15 @@ def _default_ident(self): # any links that should go in the help menu help_links = List() + # Experimental option to break in non-user code. + # The ipykernel source is in the call stack, so the user + # has to manipulate the step-over and step-into in a wize way. + debug_just_my_code = Bool(True, + help="""Set to False if you want to debug python standard and dependent libraries. + """ + ).tag(config=True) + + # track associations with current request # Private interface _darwin_app_nap = Bool(True, From f3bb6b509fad79eff02c76203ab75544a55528d0 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 14 Feb 2022 10:14:02 +0100 Subject: [PATCH 0737/1195] add hostname to the usage reply --- ipykernel/kernelbase.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 622f1ca05..f82b34968 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -13,6 +13,7 @@ import os from signal import signal, default_int_handler, SIGINT import sys +import socket import time import uuid import warnings @@ -879,7 +880,9 @@ def get_process_metric_value(self, process, name, attribute=None): return None async def usage_request(self, stream, ident, parent): - reply_content = {} + reply_content = { + 'hostname': socket.gethostname() + } if psutil is None: return reply_content current_process = psutil.Process() From bd0f200dcf2cdcd48ba9fdf4d8a43187a67ff48f Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 15 Feb 2022 07:42:56 +0100 Subject: [PATCH 0738/1195] just_my_code as optional arg in Debugger constructor --- ipykernel/debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index e5df66591..dd145d802 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -270,7 +270,7 @@ class Debugger: 'richInspectVariables', 'modules' ] - def __init__(self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code): + def __init__(self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code = True): self.log = log self.debugpy_client = DebugpyClient(log, debugpy_stream, self._handle_event) self.shell_socket = shell_socket From b953706da810f51eff0d539b920d5552f86e5ee1 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 15:43:35 +0000 Subject: [PATCH 0739/1195] Automated Changelog Entry for 6.9.1 on main --- CHANGELOG.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9c2b63cc..910f81f84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ +## 6.9.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.9.0...c27e5b95c3d104d9fb6cae3375aec0e98974dcff)) + +### Bugs fixed + +- Add hostname to the usage reply [#865](https://github.com/ipython/ipykernel/pull/865) ([@echarles](https://github.com/echarles)) +- Enable standard library debugging via config [#863](https://github.com/ipython/ipykernel/pull/863) ([@echarles](https://github.com/echarles)) +- process_one only accepts coroutines for dispatch [#861](https://github.com/ipython/ipykernel/pull/861) ([@minrk](https://github.com/minrk)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-02-07&to=2022-02-15&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-02-07..2022-02-15&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-02-07..2022-02-15&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2022-02-07..2022-02-15&type=Issues) + + + ## 6.9.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.8.0...7a229c6c83d44d315f637ef63159a43c64ec73d6)) @@ -17,8 +35,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-02-01..2022-02-07&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2022-02-01..2022-02-07&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2022-02-01..2022-02-07&type=Issues) - - ## 6.8.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.7.0...4e775b70e7e1be7e96fe7c3c747f21f3d93f0181)) From 221dca63d7b2e2b8fea32bf8a101d07645fc9d3c Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 15 Feb 2022 15:54:09 +0000 Subject: [PATCH 0740/1195] Publish 6.9.1 SHA256 hashes: ipykernel-6.9.1-py3-none-any.whl: 4fae9df6e192837552b2406a6052d707046dd2e153860be73c68484bacba18ed ipykernel-6.9.1.tar.gz: f95070a2dfd3147f8ab19f18ee46733310813758593745e07ec18fb08b409f1d --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 24e5e5984..40c5f6f3d 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.9.0' +__version__ = '6.9.1' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index 482986ff9..3455adadb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.9.0" +current = "6.9.1" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From adbdb77daac02c6d4f28820801ea3ebdb8b7be3a Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 16 Feb 2022 17:53:21 +0100 Subject: [PATCH 0741/1195] BUG: Kill subprocesses on shutdown. Fixes #jupyter/jupyter_client#104 This should make sure we properly cull all subprocesses at shutdown, it does change one of the private method from sync to async in order to no user time.sleep or thread so this may affect subclasses, though I doubt it. It's also not completely clear to me whether this works on windows as SIGINT I belove is not a thing. Regardless as this affects things like dask, and others that are mostly on unix, it should be an improvement. It does the following, stopping as soon as it does not find any more children to current process. - Send sigint to everything - Immediately send sigterm in look with an exponential backoff from 0.01 to 1 second roughtly multiplying the delay until next send by 3 each time. - Switch to sending sigkill with same backoff. There is no delay after sigint, as this is just a courtesy. The delays backoff are not configurable. I can imagine that on slow systems it may make sens --- ipykernel/kernelbase.py | 89 ++++++++++++++++++++++++++++++++--------- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index c1494ffbb..497c6a2f7 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -5,23 +5,26 @@ import asyncio import concurrent.futures -from datetime import datetime -from functools import partial +import inspect import itertools import logging -import inspect import os -from signal import signal, default_int_handler, SIGINT -import sys import socket +import sys import time import uuid import warnings +from datetime import datetime +from functools import partial +from signal import (SIGINT, SIGKILL, SIGTERM, Signals, default_int_handler, + signal) + try: import psutil except ImportError: psutil = None + try: # jupyter_client >= 5, use tz-aware now from jupyter_client.session import utcnow as now @@ -29,20 +32,17 @@ # jupyter_client < 5, use local now() now = datetime.now +import zmq +from IPython.core.error import StdinNotImplementedError +from jupyter_client.session import Session from tornado import ioloop from tornado.queues import Queue, QueueEmpty -import zmq +from traitlets import (Any, Bool, Dict, Float, Instance, Integer, List, Set, + Unicode, default, observe) +from traitlets.config.configurable import SingletonConfigurable from zmq.eventloop.zmqstream import ZMQStream -from traitlets.config.configurable import SingletonConfigurable -from IPython.core.error import StdinNotImplementedError from ipykernel.jsonutil import json_clean -from traitlets import ( - Any, Instance, Float, Dict, List, Set, Integer, Unicode, Bool, - observe, default -) - -from jupyter_client.session import Session from ._version import kernel_protocol_version @@ -796,13 +796,13 @@ async def comm_info_request(self, stream, ident, parent): reply_content, parent, ident) self.log.debug("%s", msg) - async def interrupt_request(self, stream, ident, parent): + def _send_interupt_children(self): + pid = os.getpid() pgid = os.getpgid(pid) if os.name == "nt": self.log.error("Interrupt message not supported on Windows") - else: # Prefer process-group over process if pgid and hasattr(os, "killpg"): @@ -816,6 +816,8 @@ async def interrupt_request(self, stream, ident, parent): except OSError: pass + async def interrupt_request(self, stream, ident, parent): + self._send_interupt_children() content = parent['content'] self.session.send(stream, 'interrupt_reply', content, parent, ident=ident) return @@ -830,7 +832,7 @@ async def shutdown_request(self, stream, ident, parent): content, parent ) - self._at_shutdown() + await self._at_shutdown() self.log.debug('Stopping control ioloop') control_io_loop = self.control_stream.io_loop @@ -1131,9 +1133,60 @@ def _input_request(self, prompt, ident, parent, password=False): raise EOFError return value - def _at_shutdown(self): + async def _progressively_terminate_all_children(self): + + pgid = os.getpgid(os.getpid()) + if not pgid: + self.log.warning(f"No Pgid ({pgid=}), not trying to stop subprocesses.") + return + if psutil is None: + # blindly send quickly sigterm/sigkill to processes if psutil not there. + self.log.warning( + f"Please install psutil for a cleaner subprocess shutdown." + ) + self._send_interupt_children() + try: + await asyncio.sleep(0.05) + self.log.debug("Sending SIGTERM to {pgid=}") + os.killpg(pgid, SIGTERM) + await asyncio.sleep(0.05) + self.log.debug("Sending SIGKILL to {pgid=}") + os.killpg(pgid, SIGKILL) + except Exception: + self.log.exception("Exception during subprocesses termination") + return + + sleeps = (0.01, 0.03, 0.1, 0.3, 1) + children = psutil.Process().children(recursive=True) + if not children: + self.log.debug("Kernel has no children.") + return + self.log.debug(f"Trying to interrupt then kill subprocesses : {children=}") + self._send_interupt_children() + for signum in (SIGTERM, SIGKILL): + self.log.debug( + f"Will try to send {signum} ({Signals(signum)}) to subprocesses :{children}" + ) + for delay in sleeps: + children = psutil.Process().children(recursive=True) + if not children: + self.log.debug("No more children, continuing shutdown routine.") + return + if pgid and hasattr(os, "killpg"): + try: + os.killpg(pgid, signum) + except OSError: + self.log.warning("OSError running killpg, not killing children") + return + self.log.debug( + f"Will sleep {delay}s before checking for children and retrying." + ) + await ascynio.sleep(delay) + + async def _at_shutdown(self): """Actions taken at shutdown by the kernel, called by python's atexit. """ + await self._progressively_terminate_all_children() if self._shutdown_message is not None: self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown')) self.log.debug("%s", self._shutdown_message) From 106b57a924d139bcdff186c3d0ee21e546b323d2 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 17 Feb 2022 12:59:13 +0100 Subject: [PATCH 0742/1195] Fix windows --- ipykernel/kernelbase.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 497c6a2f7..88b926c90 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -16,8 +16,13 @@ import warnings from datetime import datetime from functools import partial -from signal import (SIGINT, SIGKILL, SIGTERM, Signals, default_int_handler, - signal) +from signal import SIGINT, SIGTERM, Signals, default_int_handler, signal + +if sys.platform != "win32": + from signal import SIGKILL +else: + SIGKILL = None + try: import psutil @@ -1149,9 +1154,10 @@ async def _progressively_terminate_all_children(self): await asyncio.sleep(0.05) self.log.debug("Sending SIGTERM to {pgid=}") os.killpg(pgid, SIGTERM) - await asyncio.sleep(0.05) - self.log.debug("Sending SIGKILL to {pgid=}") - os.killpg(pgid, SIGKILL) + if sys.platform != "win32": + await asyncio.sleep(0.05) + self.log.debug("Sending SIGKILL to {pgid=}") + os.killpg(pgid, SIGKILL) except Exception: self.log.exception("Exception during subprocesses termination") return @@ -1163,7 +1169,12 @@ async def _progressively_terminate_all_children(self): return self.log.debug(f"Trying to interrupt then kill subprocesses : {children=}") self._send_interupt_children() - for signum in (SIGTERM, SIGKILL): + if sys.platform != "win32": + sigs = (SIGTERM, SIGKILL) + else: + sigs = SIGTERM + + for signum in sigs: self.log.debug( f"Will try to send {signum} ({Signals(signum)}) to subprocesses :{children}" ) From f24a98958eccba742b2d9a69190fe586bd33000b Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 17 Feb 2022 13:00:13 +0100 Subject: [PATCH 0743/1195] fix 37 --- ipykernel/kernelbase.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 88b926c90..51ebef516 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1142,21 +1142,19 @@ async def _progressively_terminate_all_children(self): pgid = os.getpgid(os.getpid()) if not pgid: - self.log.warning(f"No Pgid ({pgid=}), not trying to stop subprocesses.") + self.log.warning(f"No Pgid ({pgid}), not trying to stop subprocesses.") return if psutil is None: # blindly send quickly sigterm/sigkill to processes if psutil not there. - self.log.warning( - f"Please install psutil for a cleaner subprocess shutdown." - ) + self.log.debug("Please install psutil for a cleaner subprocess shutdown.") self._send_interupt_children() try: await asyncio.sleep(0.05) - self.log.debug("Sending SIGTERM to {pgid=}") + self.log.debug("Sending SIGTERM to {pgid}") os.killpg(pgid, SIGTERM) if sys.platform != "win32": await asyncio.sleep(0.05) - self.log.debug("Sending SIGKILL to {pgid=}") + self.log.debug("Sending SIGKILL to {pgid}") os.killpg(pgid, SIGKILL) except Exception: self.log.exception("Exception during subprocesses termination") @@ -1167,7 +1165,7 @@ async def _progressively_terminate_all_children(self): if not children: self.log.debug("Kernel has no children.") return - self.log.debug(f"Trying to interrupt then kill subprocesses : {children=}") + self.log.debug(f"Trying to interrupt then kill subprocesses : {children}") self._send_interupt_children() if sys.platform != "win32": sigs = (SIGTERM, SIGKILL) From 7d7961e037ffb77c230f30ed037f1c1bb9e1439f Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 17 Feb 2022 19:59:52 +0100 Subject: [PATCH 0744/1195] need to be even stricter on windows --- ipykernel/kernelbase.py | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 51ebef516..49c205014 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -802,13 +802,11 @@ async def comm_info_request(self, stream, ident, parent): self.log.debug("%s", msg) def _send_interupt_children(self): - - pid = os.getpid() - pgid = os.getpgid(pid) - if os.name == "nt": self.log.error("Interrupt message not supported on Windows") else: + pid = os.getpid() + pgid = os.getpgid(pid) # Prefer process-group over process if pgid and hasattr(os, "killpg"): try: @@ -1139,6 +1137,9 @@ def _input_request(self, prompt, ident, parent, password=False): return value async def _progressively_terminate_all_children(self): + if sys.platform != "win32": + self.log.info(f"Terminating subprocesses not yet supported on windows.") + return pgid = os.getpgid(os.getpid()) if not pgid: @@ -1152,10 +1153,9 @@ async def _progressively_terminate_all_children(self): await asyncio.sleep(0.05) self.log.debug("Sending SIGTERM to {pgid}") os.killpg(pgid, SIGTERM) - if sys.platform != "win32": - await asyncio.sleep(0.05) - self.log.debug("Sending SIGKILL to {pgid}") - os.killpg(pgid, SIGKILL) + await asyncio.sleep(0.05) + self.log.debug("Sending SIGKILL to {pgid}") + os.killpg(pgid, SIGKILL) except Exception: self.log.exception("Exception during subprocesses termination") return @@ -1167,12 +1167,8 @@ async def _progressively_terminate_all_children(self): return self.log.debug(f"Trying to interrupt then kill subprocesses : {children}") self._send_interupt_children() - if sys.platform != "win32": - sigs = (SIGTERM, SIGKILL) - else: - sigs = SIGTERM - for signum in sigs: + for signum in (SIGTERM, SIGKILL): self.log.debug( f"Will try to send {signum} ({Signals(signum)}) to subprocesses :{children}" ) From 0ccc5914847b63145df8343662fff00d3685d077 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Thu, 17 Feb 2022 20:42:51 +0100 Subject: [PATCH 0745/1195] Update ipykernel/kernelbase.py Co-authored-by: Steven Silvester --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 49c205014..e888ae26a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1137,7 +1137,7 @@ def _input_request(self, prompt, ident, parent, password=False): return value async def _progressively_terminate_all_children(self): - if sys.platform != "win32": + if sys.platform == "win32": self.log.info(f"Terminating subprocesses not yet supported on windows.") return From 8518d6f0ea6ddc2edd6259f77affe066d9ff3cee Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 18 Feb 2022 14:56:00 +0100 Subject: [PATCH 0746/1195] add psutils on windows and try to kill children --- ipykernel/kernelbase.py | 38 +++++++++++++++++++++++++++++--------- setup.py | 1 + 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e888ae26a..275a2078e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -21,7 +21,7 @@ if sys.platform != "win32": from signal import SIGKILL else: - SIGKILL = None + SIGKILL = "windown-SIGKILL-sentinel" try: @@ -1136,6 +1136,31 @@ def _input_request(self, prompt, ident, parent, password=False): raise EOFError return value + + async def _killpg(self, *, signal): + """ + similar to killpg but use psutil if it can on windows + or if pgid is none + + """ + pgid = os.getpgid(os.getpid()) + if pgid and hasattr(os, "killpg"): + try: + os.killpg(pgid, signal) + except OSError: + self.log.warning("OSError running killpg, not killing children") + return + elif psutil is not None: + children = parent.children(recursive=True) + for p in children: + try: + if signal == SIGTERM: + p.terminate() + elif signal == SIGKILL: + p.kill() + except psutil.NoSuchProcess: + pass + async def _progressively_terminate_all_children(self): if sys.platform == "win32": self.log.info(f"Terminating subprocesses not yet supported on windows.") @@ -1152,10 +1177,10 @@ async def _progressively_terminate_all_children(self): try: await asyncio.sleep(0.05) self.log.debug("Sending SIGTERM to {pgid}") - os.killpg(pgid, SIGTERM) + self._killpg(SIGTERM) await asyncio.sleep(0.05) self.log.debug("Sending SIGKILL to {pgid}") - os.killpg(pgid, SIGKILL) + self._killpg(pgid, SIGKILL) except Exception: self.log.exception("Exception during subprocesses termination") return @@ -1177,12 +1202,7 @@ async def _progressively_terminate_all_children(self): if not children: self.log.debug("No more children, continuing shutdown routine.") return - if pgid and hasattr(os, "killpg"): - try: - os.killpg(pgid, signum) - except OSError: - self.log.warning("OSError running killpg, not killing children") - return + self._killpg(signum) self.log.debug( f"Will sleep {delay}s before checking for children and retrying." ) diff --git a/setup.py b/setup.py index 95dffbc66..e7af2a86c 100644 --- a/setup.py +++ b/setup.py @@ -68,6 +68,7 @@ def run(self): 'tornado>=4.2,<7.0', 'matplotlib-inline>=0.1.0,<0.2.0', 'appnope;platform_system=="Darwin"', + 'psutil;platform_system=="Windows"', 'nest_asyncio', ], extras_require={ From aaad57563197dd36456506dc642f0ee4db3ae0d8 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Fri, 18 Feb 2022 15:35:22 +0100 Subject: [PATCH 0747/1195] handle windows --- ipykernel/kernelbase.py | 73 +++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 275a2078e..7a7fcde45 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1136,8 +1136,7 @@ def _input_request(self, prompt, ident, parent, password=False): raise EOFError return value - - async def _killpg(self, *, signal): + def _killpg(self, signal): """ similar to killpg but use psutil if it can on windows or if pgid is none @@ -1147,8 +1146,8 @@ async def _killpg(self, *, signal): if pgid and hasattr(os, "killpg"): try: os.killpg(pgid, signal) - except OSError: - self.log.warning("OSError running killpg, not killing children") + except (OSError) as e: + self.log.exception(f"OSError running killpg, not killing children.") return elif psutil is not None: children = parent.children(recursive=True) @@ -1162,30 +1161,20 @@ async def _killpg(self, *, signal): pass async def _progressively_terminate_all_children(self): - if sys.platform == "win32": - self.log.info(f"Terminating subprocesses not yet supported on windows.") - return pgid = os.getpgid(os.getpid()) - if not pgid: - self.log.warning(f"No Pgid ({pgid}), not trying to stop subprocesses.") - return if psutil is None: # blindly send quickly sigterm/sigkill to processes if psutil not there. - self.log.debug("Please install psutil for a cleaner subprocess shutdown.") + self.log.info("Please install psutil for a cleaner subprocess shutdown.") self._send_interupt_children() - try: - await asyncio.sleep(0.05) - self.log.debug("Sending SIGTERM to {pgid}") - self._killpg(SIGTERM) - await asyncio.sleep(0.05) - self.log.debug("Sending SIGKILL to {pgid}") - self._killpg(pgid, SIGKILL) - except Exception: - self.log.exception("Exception during subprocesses termination") - return - - sleeps = (0.01, 0.03, 0.1, 0.3, 1) + await asyncio.sleep(0.05) + self.log.debug("Sending SIGTERM to {pgid}") + self._killpg(SIGTERM) + await asyncio.sleep(0.05) + self.log.debug("Sending SIGKILL to {pgid}") + self._killpg(pgid, SIGKILL) + + sleeps = (0.01, 0.03, 0.1, 0.3, 1, 3, 10) children = psutil.Process().children(recursive=True) if not children: self.log.debug("Kernel has no children.") @@ -1195,24 +1184,38 @@ async def _progressively_terminate_all_children(self): for signum in (SIGTERM, SIGKILL): self.log.debug( - f"Will try to send {signum} ({Signals(signum)}) to subprocesses :{children}" + f"Will try to send {signum} ({Signals(signum)!r}) to subprocesses :{children}" ) for delay in sleeps: children = psutil.Process().children(recursive=True) - if not children: - self.log.debug("No more children, continuing shutdown routine.") - return - self._killpg(signum) + try: + if not children: + self.log.warning( + "No more children, continuing shutdown routine." + ) + return + except psutil.NoSuchProcess: + pass + self._killpg(15) self.log.debug( - f"Will sleep {delay}s before checking for children and retrying." + f"Will sleep {delay}s before checking for children and retrying. {children}" ) - await ascynio.sleep(delay) + await asyncio.sleep(delay) async def _at_shutdown(self): """Actions taken at shutdown by the kernel, called by python's atexit. """ - await self._progressively_terminate_all_children() - if self._shutdown_message is not None: - self.session.send(self.iopub_socket, self._shutdown_message, ident=self._topic('shutdown')) - self.log.debug("%s", self._shutdown_message) - self.control_stream.flush(zmq.POLLOUT) + try: + await self._progressively_terminate_all_children() + except Exception as e: + self.log.exception("Exception during subprocesses termination %s", e) + + finally: + if self._shutdown_message is not None: + self.session.send( + self.iopub_socket, + self._shutdown_message, + ident=self._topic("shutdown"), + ) + self.log.debug("%s", self._shutdown_message) + self.control_stream.flush(zmq.POLLOUT) From d46c4b510f525f7f0a8a1de738b168ac4e57f403 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 18 Feb 2022 14:49:55 -0600 Subject: [PATCH 0748/1195] clean up ci config --- .github/workflows/check-release.yml | 3 +-- .github/workflows/ci.yml | 3 +-- .github/workflows/downstream.yml | 29 +++++++++++++++++++++-------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index 95eced91c..cc11b5cee 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -1,9 +1,8 @@ name: Check Release on: push: - branches: ["master"] + branches: ["main"] pull_request: - branches: ["*"] concurrency: group: check-release-${{ github.ref }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d5a7e773..4fe89e6f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,8 @@ name: ipykernel tests on: push: - branches: "master" + branches: ["main"] pull_request: - branches: "*" concurrency: group: ci-${{ github.ref }} diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index ccfa015ae..594c2701f 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -2,16 +2,15 @@ name: Test downstream projects on: push: - branches: "master" + branches: ["main"] pull_request: - branches: "*" concurrency: group: downstream-${{ github.ref }} cancel-in-progress: true jobs: - downstream1: + nbclient: runs-on: ubuntu-latest steps: @@ -21,18 +20,25 @@ jobs: - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Test nbclient + - name: Run Test uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: nbclient env_values: IPYKERNEL_CELL_NAME=\ - - name: Test jupyter_client + jupyter_client: + - name: Checkout + uses: actions/checkout@v2 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Run Test uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: jupyter_client - downstream2: + ipyparallel: runs-on: ubuntu-latest steps: @@ -42,12 +48,19 @@ jobs: - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Test ipyparallel + - name: Run Test uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: ipyparallel - - name: Test jupyter_kernel_test + jupyter_kernel_test: + - name: Checkout + uses: actions/checkout@v2 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Run Test run: | git clone https://github.com/jupyter/jupyter_kernel_test.git cd jupyter_kernel_test From 2955fa88fb6102264c244ff4639b47c0af62fe8c Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 18 Feb 2022 14:56:35 -0600 Subject: [PATCH 0749/1195] fix workflow syntax --- .github/workflows/downstream.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 594c2701f..d67c532d2 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -12,7 +12,6 @@ concurrency: jobs: nbclient: runs-on: ubuntu-latest - steps: - name: Checkout uses: actions/checkout@v2 @@ -27,6 +26,8 @@ jobs: env_values: IPYKERNEL_CELL_NAME=\ jupyter_client: + runs-on: ubuntu-latest + steps: - name: Checkout uses: actions/checkout@v2 @@ -40,7 +41,6 @@ jobs: ipyparallel: runs-on: ubuntu-latest - steps: - name: Checkout uses: actions/checkout@v2 @@ -54,6 +54,8 @@ jobs: package_name: ipyparallel jupyter_kernel_test: + runs-on: ubuntu-latest + steps: - name: Checkout uses: actions/checkout@v2 From 511e8e088b43b38d7beaee7f3a91f7af5c733d58 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 21 Feb 2022 11:23:58 +0100 Subject: [PATCH 0750/1195] Only kill children in process group at shutdown - not the whole process group, which includes self and possibly parents - not children which have started their own process groups --- ipykernel/kernelbase.py | 101 ++++++++++++++++++--------------- ipykernel/tests/test_kernel.py | 84 ++++++++++++++++++++++++++- 2 files changed, 137 insertions(+), 48 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 7a7fcde45..74858d652 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -808,7 +808,8 @@ def _send_interupt_children(self): pid = os.getpid() pgid = os.getpgid(pid) # Prefer process-group over process - if pgid and hasattr(os, "killpg"): + # but only if the kernel is the leader of the process group + if pgid and pgid == pid and hasattr(os, "killpg"): try: os.killpg(pgid, SIGINT) return @@ -1136,67 +1137,73 @@ def _input_request(self, prompt, ident, parent, password=False): raise EOFError return value - def _killpg(self, signal): + def _signal_children(self, signum): """ - similar to killpg but use psutil if it can on windows - or if pgid is none + Send a signal to all our children + Like `killpg`, but does not include the current process + (or possible parents). """ - pgid = os.getpgid(os.getpid()) - if pgid and hasattr(os, "killpg"): - try: - os.killpg(pgid, signal) - except (OSError) as e: - self.log.exception(f"OSError running killpg, not killing children.") + if psutil is None: + self.log.info("Need psutil to signal children") return - elif psutil is not None: - children = parent.children(recursive=True) - for p in children: - try: - if signal == SIGTERM: - p.terminate() - elif signal == SIGKILL: - p.kill() - except psutil.NoSuchProcess: - pass - async def _progressively_terminate_all_children(self): + for p in self._process_children(): + self.log.debug(f"Sending {Signals(signum)!r} to subprocess {p}") + try: + if signum == SIGTERM: + p.terminate() + elif signum == SIGKILL: + p.kill() + else: + p.send_signal(signum) + except psutil.NoSuchProcess: + pass + + def _process_children(self): + """Retrieve child processes in the kernel's process group + + Avoids: + - including parents and self with killpg + - including all children that may have forked-off a new group + """ + if psutil is None: + return [] + kernel_process = psutil.Process() + all_children = kernel_process.children(recursive=True) + if os.name == "nt": + return all_children + kernel_pgid = os.getpgrp() + process_group_children = [] + for child in all_children: + try: + child_pgid = os.getpgid(child.pid) + except OSError: + pass + else: + if child_pgid == kernel_pgid: + process_group_children.append(child) + return process_group_children - pgid = os.getpgid(os.getpid()) + async def _progressively_terminate_all_children(self): if psutil is None: - # blindly send quickly sigterm/sigkill to processes if psutil not there. + # we need psutil to safely clean up children self.log.info("Please install psutil for a cleaner subprocess shutdown.") - self._send_interupt_children() - await asyncio.sleep(0.05) - self.log.debug("Sending SIGTERM to {pgid}") - self._killpg(SIGTERM) - await asyncio.sleep(0.05) - self.log.debug("Sending SIGKILL to {pgid}") - self._killpg(pgid, SIGKILL) + return sleeps = (0.01, 0.03, 0.1, 0.3, 1, 3, 10) - children = psutil.Process().children(recursive=True) - if not children: + if not self._process_children(): self.log.debug("Kernel has no children.") return - self.log.debug(f"Trying to interrupt then kill subprocesses : {children}") - self._send_interupt_children() for signum in (SIGTERM, SIGKILL): - self.log.debug( - f"Will try to send {signum} ({Signals(signum)!r}) to subprocesses :{children}" - ) for delay in sleeps: - children = psutil.Process().children(recursive=True) - try: - if not children: - self.log.warning( - "No more children, continuing shutdown routine." - ) - return - except psutil.NoSuchProcess: - pass - self._killpg(15) + children = self._process_children() + if not children: + self.log.debug("No more children, continuing shutdown routine.") + return + # signals only children, not current process + self._signal_children(signum) self.log.debug( f"Will sleep {delay}s before checking for children and retrying. {children}" ) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 77fc1b8e5..56077ca5f 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -6,14 +6,20 @@ import ast import os.path import platform +import signal import subprocess import sys import time +from subprocess import Popen from tempfile import TemporaryDirectory from flaky import flaky import pytest -from packaging import version + +try: + import psutil +except ImportError: + psutil = None import IPython from IPython.paths import locate_profile @@ -496,3 +502,79 @@ def test_control_thread_priority(): # comparing first to last ought to be enough, since queues preserve order # use <= in case of very-fast handling and/or low resolution timers assert control_dates[-1] <= shell_dates[0] + + +def _child(): + print("in child", os.getpid()) + + def _print_and_exit(sig, frame): + print(f"Received signal {sig}") + # take some time so retries are triggered + time.sleep(0.5) + sys.exit(-sig) + + signal.signal(signal.SIGTERM, _print_and_exit) + time.sleep(30) + + +def _start_children(): + ip = IPython.get_ipython() + ns = ip.user_ns + + cmd = [sys.executable, "-c", f"from {__name__} import _child; _child()"] + child_pg = Popen(cmd, start_new_session=False) + child_newpg = Popen(cmd, start_new_session=True) + ns["pid"] = os.getpid() + ns["child_pg"] = child_pg.pid + ns["child_newpg"] = child_newpg.pid + # give them time to start up and register signal handlers + time.sleep(1) + + +@pytest.mark.skipif( + platform.python_implementation() == "PyPy", + reason="does not work on PyPy", +) +@pytest.mark.skipif( + psutil is None, + reason="requires psutil", +) +def test_shutdown_subprocesses(): + """Kernel exits after polite shutdown_request""" + with new_kernel() as kc: + km = kc.parent + msg_id, reply = execute( + f"from {__name__} import _start_children\n_start_children()", + kc=kc, + user_expressions={ + "pid": "pid", + "child_pg": "child_pg", + "child_newpg": "child_newpg", + }, + ) + print(reply) + expressions = reply["user_expressions"] + kernel_process = psutil.Process(int(expressions["pid"]["data"]["text/plain"])) + child_pg = psutil.Process(int(expressions["child_pg"]["data"]["text/plain"])) + child_newpg = psutil.Process( + int(expressions["child_newpg"]["data"]["text/plain"]) + ) + wait_for_idle(kc) + + kc.shutdown() + for i in range(300): # 30s timeout + if km.is_alive(): + time.sleep(0.1) + else: + break + assert not km.is_alive() + assert not kernel_process.is_running() + # child in the process group shut down + assert not child_pg.is_running() + # child outside the process group was not shut down (unix only) + if os.name != 'nt': + assert child_newpg.is_running() + try: + child_newpg.terminate() + except psutil.NoSuchProcess: + pass From 16807bf708b5d2847921427e0741ab75b53591fc Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 21 Feb 2022 13:24:10 +0100 Subject: [PATCH 0751/1195] require psutil on all platforms now needed for cleanup on all platforms, as well as usage_requests --- ipykernel/kernelbase.py | 18 +----------------- ipykernel/tests/test_kernel.py | 10 +--------- setup.py | 2 +- 3 files changed, 3 insertions(+), 27 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 74858d652..c775398fc 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -24,10 +24,6 @@ SIGKILL = "windown-SIGKILL-sentinel" -try: - import psutil -except ImportError: - psutil = None try: @@ -37,6 +33,7 @@ # jupyter_client < 5, use local now() now = datetime.now +import psutil import zmq from IPython.core.error import StdinNotImplementedError from jupyter_client.session import Session @@ -898,8 +895,6 @@ async def usage_request(self, stream, ident, parent): reply_content = { 'hostname': socket.gethostname() } - if psutil is None: - return reply_content current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) process_metric_value = self.get_process_metric_value @@ -1144,10 +1139,6 @@ def _signal_children(self, signum): Like `killpg`, but does not include the current process (or possible parents). """ - if psutil is None: - self.log.info("Need psutil to signal children") - return - for p in self._process_children(): self.log.debug(f"Sending {Signals(signum)!r} to subprocess {p}") try: @@ -1167,8 +1158,6 @@ def _process_children(self): - including parents and self with killpg - including all children that may have forked-off a new group """ - if psutil is None: - return [] kernel_process = psutil.Process() all_children = kernel_process.children(recursive=True) if os.name == "nt": @@ -1186,11 +1175,6 @@ def _process_children(self): return process_group_children async def _progressively_terminate_all_children(self): - if psutil is None: - # we need psutil to safely clean up children - self.log.info("Please install psutil for a cleaner subprocess shutdown.") - return - sleeps = (0.01, 0.03, 0.1, 0.3, 1, 3, 10) if not self._process_children(): self.log.debug("Kernel has no children.") diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 56077ca5f..896ca52ca 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -14,13 +14,9 @@ from tempfile import TemporaryDirectory from flaky import flaky +import psutil import pytest -try: - import psutil -except ImportError: - psutil = None - import IPython from IPython.paths import locate_profile @@ -535,10 +531,6 @@ def _start_children(): platform.python_implementation() == "PyPy", reason="does not work on PyPy", ) -@pytest.mark.skipif( - psutil is None, - reason="requires psutil", -) def test_shutdown_subprocesses(): """Kernel exits after polite shutdown_request""" with new_kernel() as kc: diff --git a/setup.py b/setup.py index e7af2a86c..71289881e 100644 --- a/setup.py +++ b/setup.py @@ -68,7 +68,7 @@ def run(self): 'tornado>=4.2,<7.0', 'matplotlib-inline>=0.1.0,<0.2.0', 'appnope;platform_system=="Darwin"', - 'psutil;platform_system=="Windows"', + 'psutil', 'nest_asyncio', ], extras_require={ From 5c16fde1ce3dd5dd4489bf1969688f78c8a6ec3d Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 21 Feb 2022 14:31:56 +0100 Subject: [PATCH 0752/1195] Decrease imports reach on wheel build. Moving _is_debugpy_available to .debugger to avoid having import that much of the package on wheel building. In particular this was forcing `psutil` to be importable to build this package --- ipykernel/debugger.py | 12 ++++++++---- ipykernel/ipkernel.py | 7 +------ ipykernel/kernelspec.py | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index dd145d802..dfe5d0a9b 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -19,10 +19,14 @@ from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed) -# This import is required to have the next ones working... -from debugpy.server import api # noqa -from _pydevd_bundle import pydevd_frame_utils -from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager, _FramesTracker +try: + # This import is required to have the next ones working... + from debugpy.server import api # noqa + from _pydevd_bundle import pydevd_frame_utils + from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager, _FramesTracker + _is_debugpy_available = True +except ImportError: + _is_debugpy_available = False # Required for backwards compatiblity ROUTING_ID = getattr(zmq, 'ROUTING_ID', None) or zmq.IDENTITY diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index de4f47069..26276ca91 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -18,6 +18,7 @@ from .zmqshell import ZMQInteractiveShell from .eventloops import _use_appnope from .compiler import XCachingCompiler +from .debugger import Debugger, _is_debugpy_available try: from IPython.core.interactiveshell import _asyncio_runner @@ -33,12 +34,6 @@ except ImportError: _use_experimental_60_completion = False -try: - import debugpy - from .debugger import Debugger - _is_debugpy_available = True -except ImportError: - _is_debugpy_available = False _EXPERIMENTAL_KEY_NAME = '_jupyter_types_experimental' diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index c7514084a..6a0bc639f 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -13,7 +13,7 @@ from jupyter_client.kernelspec import KernelSpecManager -from .ipkernel import _is_debugpy_available +from .debugger import _is_debugpy_available pjoin = os.path.join From 35f1e76d53e196de4b0ca529919a5e959dec0702 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Sun, 27 Feb 2022 13:34:50 -0500 Subject: [PATCH 0753/1195] Catch error when shutting down kernel from the control channel --- ipykernel/eventloops.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 29434bb41..4911c87ee 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -285,7 +285,10 @@ def start(self): @loop_tk.exit def loop_tk_exit(kernel): - kernel.app_wrapper.app.destroy() + try: + kernel.app_wrapper.app.destroy() + except RuntimeError: + pass @register_integration('gtk') From 96f3ae51f83fad042e321694577ee315a0cc77b0 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 09:30:50 +0000 Subject: [PATCH 0754/1195] Automated Changelog Entry for 6.9.2 on main --- CHANGELOG.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 910f81f84..0c15f5a35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ +## 6.9.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.9.1...d6744f9e423dacc6b317b1d31805304e89cbec5d)) + +### Bugs fixed + +- Catch error when shutting down kernel from the control channel [#877](https://github.com/ipython/ipykernel/pull/877) ([@ccordoba12](https://github.com/ccordoba12)) +- Only kill children in process group at shutdown [#874](https://github.com/ipython/ipykernel/pull/874) ([@minrk](https://github.com/minrk)) +- BUG: Kill subprocesses on shutdown. [#869](https://github.com/ipython/ipykernel/pull/869) ([@Carreau](https://github.com/Carreau)) + +### Maintenance and upkeep improvements + +- Clean up CI [#871](https://github.com/ipython/ipykernel/pull/871) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-02-15&to=2022-03-14&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-02-15..2022-03-14&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2022-02-15..2022-03-14&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2022-02-15..2022-03-14&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-02-15..2022-03-14&type=Issues) | [@fabioz](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afabioz+updated%3A2022-02-15..2022-03-14&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2022-02-15..2022-03-14&type=Issues) | [@vidartf](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Avidartf+updated%3A2022-02-15..2022-03-14&type=Issues) + + + ## 6.9.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.9.0...c27e5b95c3d104d9fb6cae3375aec0e98974dcff)) @@ -18,8 +40,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-02-07..2022-02-15&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-02-07..2022-02-15&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2022-02-07..2022-02-15&type=Issues) - - ## 6.9.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.8.0...7a229c6c83d44d315f637ef63159a43c64ec73d6)) From 7283ccbfee3d5530a9220e916f41ed366ce1be04 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 14 Mar 2022 11:23:20 +0000 Subject: [PATCH 0755/1195] Publish 6.9.2 SHA256 hashes: ipykernel-6.9.2-py3-none-any.whl: c977cff576b8425a68d3a6916510903833f0f25ed8d5c282a0c51c35de27bd47 ipykernel-6.9.2.tar.gz: 4c3cc8cb359f2ead70c30f5504971c0d285e2c1c699d2ce9af0216fe9c9fb17c --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 40c5f6f3d..1c9db6510 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.9.1' +__version__ = '6.9.2' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index 3455adadb..ec9b08e49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.9.1" +current = "6.9.2" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From ee68b2e144a05a89ffc01e6dd02d8d6ef4e2409d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Est=C3=A8ve?= Date: Thu, 17 Mar 2022 14:06:03 +0100 Subject: [PATCH 0756/1195] Add precision about subprocess stdout/stderr capturing --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c15f5a35..90c46b372 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -377,11 +377,12 @@ following non-exhaustive changes. - We now have a new dependency: `matplotlib-inline`, this helps to separate the circular dependency between IPython/IPykernel and matplotlib. - - All outputs to stdout/stderr should now be captured, including subprocesses - and output of compiled libraries (blas, lapack....). In notebook - server, some outputs that would previously go to the notebooks logs will now - both head to notebook logs and in notebooks outputs. In terminal frontend - like Jupyter Console, Emacs or other, this may ends up as duplicated outputs. + - On POSIX systems, all outputs to stdout/stderr should now be captured, + including subprocesses and output of compiled libraries (blas, lapack....). + In notebook server, some outputs that would previously go to the notebooks + logs will now both head to notebook logs and in notebooks outputs. In + terminal frontend like Jupyter Console, Emacs or other, this may ends up as + duplicated outputs. - coroutines are now native (async-def) , instead of using tornado's `@gen.coroutine` From 730a812c5228cadf9e4e5b84d4396544065146cd Mon Sep 17 00:00:00 2001 From: Jack Amadeo Date: Mon, 21 Mar 2022 22:17:23 -0400 Subject: [PATCH 0757/1195] Check if the current thread is the io thread --- ipykernel/iostream.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 50f86d243..a1a54a84e 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -455,7 +455,12 @@ def flush(self): send will happen in the background thread """ - if self.pub_thread and self.pub_thread.thread is not None and self.pub_thread.thread.is_alive(): + if ( + self.pub_thread + and self.pub_thread.thread is not None + and self.pub_thread.thread.is_alive() + and self.pub_thread.thread.ident != threading.current_thread().ident + ): # request flush on the background thread self.pub_thread.schedule(self._flush) # wait for flush to actually get through, if we can. From f423992695000a2423c34e2e005238ca40a97796 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 22 Mar 2022 04:55:37 -0500 Subject: [PATCH 0758/1195] ci cleanup --- .github/workflows/ci.yml | 111 +++++++++++++++++++++++++++---- .github/workflows/downstream.yml | 1 + 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4fe89e6f2..9d3bf9be3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: Install the Python dependencies run: | - pip install --pre --upgrade --upgrade-strategy=eager .[test] codecov + pip install .[test] codecov - name: Install matplotlib if: ${{ matrix.os != 'macos' && matrix.python-version != 'pypy3' }} @@ -55,26 +55,22 @@ jobs: timeout-minutes: 10 if: ${{ !startsWith( matrix.python-version, 'pypy' ) }} run: | - pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 + args="-vv -raXxs --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 --color=yes" + pytest $args|| pytest $args --lf" + pytest $args || pytest $args --lf - name: Run the tests on pypy timeout-minutes: 15 if: ${{ startsWith( matrix.python-version, 'pypy' ) }} run: | - pytest -vv ipykernel - - - name: Build the docs - if: ${{ matrix.os == 'ubuntu' && matrix.python-version == '3.9'}} - run: | - cd docs - pip install -r requirements.txt - make html + args="-vv -raXxs --durations 10 --color=yes" + pytest $args || pytest $args --lf - name: Coverage run: | codecov - check_docstrings: + test_docs: runs-on: ${{ matrix.os }}-latest strategy: fail-fast: false @@ -91,9 +87,16 @@ jobs: - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Build the docs + if: ${{ matrix.os == 'ubuntu' && matrix.python-version == '3.9'}} + run: | + cd docs + pip install -r requirements.txt + make html SPHINXOPTS="-W" + - name: Install the Python dependencies run: | - pip install --pre --upgrade --upgrade-strategy=eager . + pip install . pip install velin - name: Check Docstrings @@ -116,7 +119,7 @@ jobs: - name: Install the Python dependencies without debugpy run: | - pip install --pre --upgrade --upgrade-strategy=eager .[test] + pip install .[test] pip uninstall --yes debugpy - name: List installed packages @@ -126,4 +129,84 @@ jobs: - name: Run the tests timeout-minutes: 10 run: | - pytest ipykernel -vv -s --durations 10 + args="-vv -raXxs --durations 10 --color=yes" + pytest $args || pytest $args --lf + + test_miniumum_versions: + name: Test Minimum Versions + timeout-minutes: 20 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + with: + python_version: "3.7" + - name: Install miniumum versions + uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1 + - name: Run the unit tests + run: | + args="-vv -raXxs --durations 10 --color=yes" + pytest $args|| pytest $args --lf + + test_prereleases: + name: Test Prereleases + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install the Python dependencies + run: | + pip install --pre -e ".[test]" + - name: List installed packages + run: | + pip freeze + pip check + - name: Run the tests + run: | + args="-vv -raXxs --durations 10 --color=yes" + pytest $args|| pytest $args --lf + + make_sdist: + name: Make SDist + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v2 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Build SDist + run: | + pip install build + python -m build --sdist + - uses: actions/upload-artifact@v2 + with: + name: "sdist" + path: dist/*.tar.gz + + test_sdist: + runs-on: ubuntu-latest + needs: [make_sdist] + name: Install from SDist and Test + timeout-minutes: 20 + steps: + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Download sdist + uses: actions/download-artifact@v2 + - name: Install From SDist + run: | + set -ex + cd sdist + mkdir test + tar --strip-components=1 -zxvf *.tar.gz -C ./test + cd test + pip install .[test] + - name: Run Test + run: | + cd sdist/test + args="-vv -raXxs --durations 10 --color=yes" + pytest $args|| pytest $args --lf diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index d67c532d2..26e003a0b 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -52,6 +52,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: ipyparallel + package_spec: "-e \".[test]\"" jupyter_kernel_test: runs-on: ubuntu-latest From 139f62bd10026b4f6f262e1211db9c2ca6231a79 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 22 Mar 2022 04:57:50 -0500 Subject: [PATCH 0759/1195] add missing spaces --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d3bf9be3..93f348174 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -147,7 +147,7 @@ jobs: - name: Run the unit tests run: | args="-vv -raXxs --durations 10 --color=yes" - pytest $args|| pytest $args --lf + pytest $args || pytest $args --lf test_prereleases: name: Test Prereleases @@ -168,7 +168,7 @@ jobs: - name: Run the tests run: | args="-vv -raXxs --durations 10 --color=yes" - pytest $args|| pytest $args --lf + pytest $args || pytest $args --lf make_sdist: name: Make SDist @@ -209,4 +209,4 @@ jobs: run: | cd sdist/test args="-vv -raXxs --durations 10 --color=yes" - pytest $args|| pytest $args --lf + pytest $args || pytest $args --lf From 11ba1adc411751121f9d5b03417992a324a6fd5b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 22 Mar 2022 04:59:26 -0500 Subject: [PATCH 0760/1195] syntax --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 93f348174..6efdd40b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -149,7 +149,7 @@ jobs: args="-vv -raXxs --durations 10 --color=yes" pytest $args || pytest $args --lf - test_prereleases: + test_prereleases: name: Test Prereleases runs-on: ubuntu-latest timeout-minutes: 20 From 6525576f79fb648971f680a8fa4f78306f6feb56 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 22 Mar 2022 05:13:45 -0500 Subject: [PATCH 0761/1195] fixup --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6efdd40b7..6c14af068 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,6 @@ jobs: if: ${{ !startsWith( matrix.python-version, 'pypy' ) }} run: | args="-vv -raXxs --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 --color=yes" - pytest $args|| pytest $args --lf" pytest $args || pytest $args --lf - name: Run the tests on pypy From 6de06bdddf41249fd2b0faf94586cb20b73c2991 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 22 Mar 2022 05:21:18 -0500 Subject: [PATCH 0762/1195] cleanup --- .github/workflows/ci.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c14af068..c8167cc4e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,10 @@ concurrency: group: ci-${{ github.ref }} cancel-in-progress: true +defaults: + run: + shell: bash + jobs: build: runs-on: ${{ matrix.os }}-latest @@ -16,7 +20,7 @@ jobs: fail-fast: false matrix: os: [ubuntu, macos, windows] - python-version: [ '3.7', '3.8', '3.9', '3.10', 'pypy-3.7' ] + python-version: [ '3.7', '3.8', '3.10', 'pypy-3.7' ] exclude: - os: windows python-version: pypy-3.7 @@ -53,14 +57,14 @@ jobs: - name: Run the tests timeout-minutes: 10 - if: ${{ !startsWith( matrix.python-version, 'pypy' ) }} + if: ${{ !startsWith( matrix.python-version, 'pypy' ) && matrix.os != 'windows' }} run: | args="-vv -raXxs --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 --color=yes" pytest $args || pytest $args --lf - - name: Run the tests on pypy + - name: Run the tests on pypy and windows timeout-minutes: 15 - if: ${{ startsWith( matrix.python-version, 'pypy' ) }} + if: ${{ startsWith( matrix.python-version, 'pypy' ) || matrix.os == 'windows' }} run: | args="-vv -raXxs --durations 10 --color=yes" pytest $args || pytest $args --lf From a0a3e7d1d1989e766955d43b8aa6d87ee9da9e2a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 22 Mar 2022 05:36:47 -0500 Subject: [PATCH 0763/1195] bump required tornado --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 71289881e..513871ffa 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ def run(self): 'ipython>=7.23.1', 'traitlets>=5.1.0,<6.0', 'jupyter_client<8.0', - 'tornado>=4.2,<7.0', + 'tornado>=5.0,<7.0', 'matplotlib-inline>=0.1.0,<0.2.0', 'appnope;platform_system=="Darwin"', 'psutil', From d692220b6ebe1db3b4f20824f11030e61bababdf Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 22 Mar 2022 05:39:05 -0500 Subject: [PATCH 0764/1195] increase timeout --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8167cc4e..faa690242 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: pip check - name: Run the tests - timeout-minutes: 10 + timeout-minutes: 15 if: ${{ !startsWith( matrix.python-version, 'pypy' ) && matrix.os != 'windows' }} run: | args="-vv -raXxs --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 --color=yes" From 169ffc6b9253881ca3e611d0bcdb3a1f7a0111c2 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 22 Mar 2022 09:47:23 -0500 Subject: [PATCH 0765/1195] more ci cleanup --- .github/workflows/ci.yml | 57 +++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index faa690242..12e2e1788 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,15 +15,19 @@ defaults: jobs: build: - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu, macos, windows] - python-version: [ '3.7', '3.8', '3.10', 'pypy-3.7' ] - exclude: - - os: windows - python-version: pypy-3.7 + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.7", "3.10"] + include: + - os: windows-latest + python-version: "3.9" + - os: ubuntu-latest + python-version: "pypy-3.7" + - os: macos-latest + python-version: "3.8" steps: - name: Checkout uses: actions/checkout@v2 @@ -36,12 +40,12 @@ jobs: pip install .[test] codecov - name: Install matplotlib - if: ${{ matrix.os != 'macos' && matrix.python-version != 'pypy3' }} + if: ${{ !startsWith(matrix.os, 'macos') && !startsWith(matrix.python-version, 'pypy') }} run: | pip install matplotlib || echo 'failed to install matplotlib' - name: Install alternate event loops - if: ${{ matrix.os != 'windows' }} + if: ${{ !startsWith(matrix.os, 'windows') }} run: | pip install curio || echo 'ignoring curio install failure' pip install trio || echo 'ignoring trio install failure' @@ -57,32 +61,32 @@ jobs: - name: Run the tests timeout-minutes: 15 - if: ${{ !startsWith( matrix.python-version, 'pypy' ) && matrix.os != 'windows' }} + if: ${{ !startsWith( matrix.python-version, 'pypy' ) && !startsWith(matrix.os, 'windows') }} run: | - args="-vv -raXxs --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 --color=yes" - pytest $args || pytest $args --lf + cmd="pythom -m pytest -vv -raXs --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 --color=yes" + $cmd || $cmd --lf - name: Run the tests on pypy and windows timeout-minutes: 15 - if: ${{ startsWith( matrix.python-version, 'pypy' ) || matrix.os == 'windows' }} + if: ${{ startsWith( matrix.python-version, 'pypy' ) || startsWith(matrix.os, 'windows') }} run: | - args="-vv -raXxs --durations 10 --color=yes" - pytest $args || pytest $args --lf + cmd="python -m pytest -vv -raXs --durations 10 --color=yes" + $cmd || $cmd --lf - name: Coverage run: | codecov test_docs: - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu] + os: [ubuntu-latest] python-version: [ '3.9' ] exclude: - - os: windows - python-version: pypy3 + - os: windows-latest + python-version: pypy-3.7 steps: - name: Checkout uses: actions/checkout@v2 @@ -91,7 +95,6 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Build the docs - if: ${{ matrix.os == 'ubuntu' && matrix.python-version == '3.9'}} run: | cd docs pip install -r requirements.txt @@ -132,8 +135,8 @@ jobs: - name: Run the tests timeout-minutes: 10 run: | - args="-vv -raXxs --durations 10 --color=yes" - pytest $args || pytest $args --lf + cmd="python -m pytest -vv -raXxs --durations 10 --color=yes" + $cmd || $cmd --lf test_miniumum_versions: name: Test Minimum Versions @@ -149,8 +152,8 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1 - name: Run the unit tests run: | - args="-vv -raXxs --durations 10 --color=yes" - pytest $args || pytest $args --lf + cmd="python -m pytest -vv -raXxs --durations 10 --color=yes" + $cmd || $cmd --lf test_prereleases: name: Test Prereleases @@ -170,8 +173,8 @@ jobs: pip check - name: Run the tests run: | - args="-vv -raXxs --durations 10 --color=yes" - pytest $args || pytest $args --lf + cmd="python -m pytest -vv -raXs --durations 10 --color=yes" + $cmd || $cmd --lf make_sdist: name: Make SDist @@ -211,5 +214,5 @@ jobs: - name: Run Test run: | cd sdist/test - args="-vv -raXxs --durations 10 --color=yes" - pytest $args || pytest $args --lf + cmd="python -m pytest -vv -raXs --durations 10 --color=yes" + $cmd || $cmd --lf From f4f80b1d454d2c560bf37a6bfcd5d017b4caa18a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 22 Mar 2022 09:51:16 -0500 Subject: [PATCH 0766/1195] spelling --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12e2e1788..5c6a54738 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: timeout-minutes: 15 if: ${{ !startsWith( matrix.python-version, 'pypy' ) && !startsWith(matrix.os, 'windows') }} run: | - cmd="pythom -m pytest -vv -raXs --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 --color=yes" + cmd="python -m pytest -vv -raXs --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 --color=yes" $cmd || $cmd --lf - name: Run the tests on pypy and windows From 2de496a825d2f50026c1f6909a1fcb02227f60c4 Mon Sep 17 00:00:00 2001 From: Bago Amirbekian Date: Fri, 25 Mar 2022 10:24:40 -0700 Subject: [PATCH 0767/1195] Make OutStream._buffer thread safe --- ipykernel/iostream.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 50f86d243..87536f801 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -376,7 +376,8 @@ def __init__( self._flush_pending = False self._subprocess_flush_pending = False self._io_loop = pub_thread.io_loop - self._new_buffer() + self._buffer_lock = threading.RLock() + self._buffer = StringIO() self.echo = None self._isatty = bool(isatty) @@ -528,7 +529,8 @@ def write(self, string: str) -> int: is_child = (not self._is_master_process()) # only touch the buffer in the IO thread to avoid races - self.pub_thread.schedule(lambda: self._buffer.write(string)) + with self._buffer_lock: + self._buffer.write(string) if is_child: # mp.Pool cannot be trusted to flush promptly (or ever), # and this helps. @@ -553,17 +555,15 @@ def writable(self): return True def _flush_buffer(self): - """clear the current buffer and return the current buffer data. - - This should only be called in the IO thread. - """ - data = '' - if self._buffer is not None: - buf = self._buffer - self._new_buffer() - data = buf.getvalue() - buf.close() + """clear the current buffer and return the current buffer data.""" + buf = self._rotate_buffer() + data = buf.getvalue() + buf.close() return data - def _new_buffer(self): - self._buffer = StringIO() + def _rotate_buffer(self): + """Returns the current buffer and replaces it with an empty buffer.""" + with self._buffer_lock: + old_buffer = self._buffer + self._buffer = StringIO() + return old_buffer From af3be06660a2b0c7731aacb53ce310668aa2235e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 27 Mar 2022 05:52:47 -0500 Subject: [PATCH 0768/1195] add pytest opts and pre-commit --- .github/workflows/ci.yml | 36 ++++++++++++++++++++++++++++++------ CONTRIBUTING.md | 25 +++++++++++++++++++++++++ pyproject.toml | 9 +++++++++ setup.py | 1 + 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c6a54738..56a8aba91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,6 +14,30 @@ defaults: shell: bash jobs: + # Run "pre-commit run --all-files" + pre-commit: + runs-on: ubuntu-20.04 + timeout-minutes: 2 + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: 3.8 + + # ref: https://github.com/pre-commit/action + - uses: pre-commit/action@v2.0.0 + - name: Help message if pre-commit fail + if: ${{ failure() }} + run: | + echo "You can install pre-commit hooks to automatically run formatting" + echo "on each commit with:" + echo " pre-commit install" + echo "or you can run by hand on staged files with" + echo " pre-commit run" + echo "or after-the-fact on already committed files with" + echo " pre-commit run --all-files" + build: runs-on: ${{ matrix.os }} strategy: @@ -63,14 +87,14 @@ jobs: timeout-minutes: 15 if: ${{ !startsWith( matrix.python-version, 'pypy' ) && !startsWith(matrix.os, 'windows') }} run: | - cmd="python -m pytest -vv -raXs --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 --color=yes" + cmd="python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered" $cmd || $cmd --lf - name: Run the tests on pypy and windows timeout-minutes: 15 if: ${{ startsWith( matrix.python-version, 'pypy' ) || startsWith(matrix.os, 'windows') }} run: | - cmd="python -m pytest -vv -raXs --durations 10 --color=yes" + cmd="python -m pytest -vv" $cmd || $cmd --lf - name: Coverage @@ -135,7 +159,7 @@ jobs: - name: Run the tests timeout-minutes: 10 run: | - cmd="python -m pytest -vv -raXxs --durations 10 --color=yes" + cmd="python -m pytest -vv -raXxs" $cmd || $cmd --lf test_miniumum_versions: @@ -152,7 +176,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1 - name: Run the unit tests run: | - cmd="python -m pytest -vv -raXxs --durations 10 --color=yes" + cmd="python -m pytest -vv -raXxs" $cmd || $cmd --lf test_prereleases: @@ -173,7 +197,7 @@ jobs: pip check - name: Run the tests run: | - cmd="python -m pytest -vv -raXs --durations 10 --color=yes" + cmd="python -m pytest -vv" $cmd || $cmd --lf make_sdist: @@ -214,5 +238,5 @@ jobs: - name: Run Test run: | cd sdist/test - cmd="python -m pytest -vv -raXs --durations 10 --color=yes" + cmd="python -m pytest -vv" $cmd || $cmd --lf diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 98696576f..28e0160da 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,31 @@ cd ipykernel pip install -e . ``` +## Code Styling +`ipykernel` has adopted automatic code formatting so you shouldn't +need to worry too much about your code style. +As long as your code is valid, +the pre-commit hook should take care of how it should look. +To install `pre-commit`, run the following:: + + pip install pre-commit + pre-commit install + + +You can invoke the pre-commit hook by hand at any time with:: + + pre-commit run + +which should run any autoformatting on your code +and tell you about any errors it couldn't fix automatically. +You may also install [black integration](https://github.com/psf/black#editor-integration) +into your text editor to format code automatically. + +If you have already committed files before setting up the pre-commit +hook with ``pre-commit install``, you can fix everything up using +``pre-commit run --all-files``. You need to make the fixing commit +yourself after that. + ## Releasing ipykernel Releasing ipykernel is *almost* standard for a Python package: diff --git a/pyproject.toml b/pyproject.toml index ec9b08e49..6dcaf2645 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,3 +28,12 @@ tag_template = "v{new_version}" [[tool.tbump.file]] src = "ipykernel/_version.py" + +[tool.pytest.ini_options] +addopts = "-raXs --durations 10 --color=yes --doctest-modules" +testpaths = [ + "tests/" +] +timeout = 300 +# Restore this setting to debug failures +# timeout_method = "thread" diff --git a/setup.py b/setup.py index 513871ffa..deac49d71 100644 --- a/setup.py +++ b/setup.py @@ -77,6 +77,7 @@ def run(self): "pytest-cov", "flaky", "ipyparallel", + "pre-commit" ], }, classifiers=[ From d207e4cc97bb75b871e19be25f9b9859c4f72fd2 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 27 Mar 2022 05:57:53 -0500 Subject: [PATCH 0769/1195] add pre-commit --- .github/workflows/ci.yml | 178 ++++----- .github/workflows/downstream.yml | 2 +- .mailmap | 2 +- .pre-commit-config.yaml | 60 +++ CHANGELOG.md | 485 ++++++++++++------------ CONTRIBUTING.md | 8 +- COPYING.md | 6 +- RELEASE.md | 2 +- docs/index.rst | 1 - docs/requirements.txt | 2 +- examples/embedding/internal_ipkernel.py | 2 +- examples/embedding/ipkernel_qtapp.py | 2 +- ipykernel/__init__.py | 2 +- ipykernel/comm/manager.py | 2 +- ipykernel/debugger.py | 4 +- ipykernel/kernelspec.py | 8 +- ipykernel/pickleutil.py | 44 +-- ipykernel/tests/__init__.py | 2 +- ipykernel/tests/test_debugger.py | 4 +- ipykernel/tests/test_kernelspec.py | 8 +- ipykernel/tests/test_pickleutil.py | 8 +- setup.cfg | 46 +-- 22 files changed, 465 insertions(+), 413 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56a8aba91..728482ffa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,53 +53,53 @@ jobs: - os: macos-latest python-version: "3.8" steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - - name: Install the Python dependencies - run: | - pip install .[test] codecov - - - name: Install matplotlib - if: ${{ !startsWith(matrix.os, 'macos') && !startsWith(matrix.python-version, 'pypy') }} - run: | - pip install matplotlib || echo 'failed to install matplotlib' - - - name: Install alternate event loops - if: ${{ !startsWith(matrix.os, 'windows') }} - run: | - pip install curio || echo 'ignoring curio install failure' - pip install trio || echo 'ignoring trio install failure' - - - name: List installed packages - run: | - pip uninstall pipx -y - pip install pipdeptree - pipdeptree - pipdeptree --reverse - pip freeze - pip check - - - name: Run the tests - timeout-minutes: 15 - if: ${{ !startsWith( matrix.python-version, 'pypy' ) && !startsWith(matrix.os, 'windows') }} - run: | - cmd="python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered" - $cmd || $cmd --lf - - - name: Run the tests on pypy and windows - timeout-minutes: 15 - if: ${{ startsWith( matrix.python-version, 'pypy' ) || startsWith(matrix.os, 'windows') }} - run: | - cmd="python -m pytest -vv" - $cmd || $cmd --lf - - - name: Coverage - run: | - codecov + - name: Checkout + uses: actions/checkout@v2 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Install the Python dependencies + run: | + pip install .[test] codecov + + - name: Install matplotlib + if: ${{ !startsWith(matrix.os, 'macos') && !startsWith(matrix.python-version, 'pypy') }} + run: | + pip install matplotlib || echo 'failed to install matplotlib' + + - name: Install alternate event loops + if: ${{ !startsWith(matrix.os, 'windows') }} + run: | + pip install curio || echo 'ignoring curio install failure' + pip install trio || echo 'ignoring trio install failure' + + - name: List installed packages + run: | + pip uninstall pipx -y + pip install pipdeptree + pipdeptree + pipdeptree --reverse + pip freeze + pip check + + - name: Run the tests + timeout-minutes: 15 + if: ${{ !startsWith( matrix.python-version, 'pypy' ) && !startsWith(matrix.os, 'windows') }} + run: | + cmd="python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered" + $cmd || $cmd --lf + + - name: Run the tests on pypy and windows + timeout-minutes: 15 + if: ${{ startsWith( matrix.python-version, 'pypy' ) || startsWith(matrix.os, 'windows') }} + run: | + cmd="python -m pytest -vv" + $cmd || $cmd --lf + + - name: Coverage + run: | + codecov test_docs: runs-on: ${{ matrix.os }} @@ -107,60 +107,60 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: [ '3.9' ] + python-version: ["3.9"] exclude: - - os: windows-latest - python-version: pypy-3.7 + - os: windows-latest + python-version: pypy-3.7 steps: - - name: Checkout - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v2 - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Build the docs - run: | - cd docs - pip install -r requirements.txt - make html SPHINXOPTS="-W" + - name: Build the docs + run: | + cd docs + pip install -r requirements.txt + make html SPHINXOPTS="-W" - - name: Install the Python dependencies - run: | - pip install . - pip install velin + - name: Install the Python dependencies + run: | + pip install . + pip install velin - - name: Check Docstrings - run: | - velin . --check --compact + - name: Check Docstrings + run: | + velin . --check --compact test_without_debugpy: - runs-on: ${{ matrix.os }}-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - os: [ubuntu] - python-version: [ '3.9' ] + os: [ubuntu-latest] + python-version: ["3.9"] steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - - name: Install the Python dependencies without debugpy - run: | - pip install .[test] - pip uninstall --yes debugpy - - - name: List installed packages - run: | - pip freeze - - - name: Run the tests - timeout-minutes: 10 - run: | - cmd="python -m pytest -vv -raXxs" - $cmd || $cmd --lf + - name: Checkout + uses: actions/checkout@v2 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Install the Python dependencies without debugpy + run: | + pip install .[test] + pip uninstall --yes debugpy + + - name: List installed packages + run: | + pip freeze + + - name: Run the tests + timeout-minutes: 10 + run: | + cmd="python -m pytest -vv -raXxs" + $cmd || $cmd --lf test_miniumum_versions: name: Test Minimum Versions diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 26e003a0b..061299943 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -52,7 +52,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: ipyparallel - package_spec: "-e \".[test]\"" + package_spec: '-e ".[test]"' jupyter_kernel_test: runs-on: ubuntu-latest diff --git a/.mailmap b/.mailmap index bd6544e25..e39230250 100644 --- a/.mailmap +++ b/.mailmap @@ -26,7 +26,7 @@ David Hirschfeld dhirschfeld David P. Sanders David Warde-Farley David Warde-Farley <> Doug Blank Doug Blank -Eugene Van den Bulke Eugene Van den Bulke +Eugene Van den Bulke Eugene Van den Bulke Evan Patterson Evan Patterson Evan Patterson diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..25b0bcb3c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,60 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: end-of-file-fixer + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: requirements-txt-fixer + - id: check-added-large-files + - id: check-case-conflict + - id: check-toml + - id: check-yaml + - id: debug-statements + exclude: ipykernel/kernelapp.py + - id: forbid-new-submodules + - id: check-builtin-literals + - id: trailing-whitespace + + # - repo: https://github.com/psf/black + # rev: 22.1.0 + # hooks: + # - id: black + # args: ["--line-length", "100"] + + # - repo: https://github.com/PyCQA/isort + # rev: 5.10.1 + # hooks: + # - id: isort + # files: \.py$ + # args: [--profile=black] + + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v2.5.1 + hooks: + - id: prettier + + # - repo: https://github.com/pycqa/flake8 + # rev: 4.0.1 + # hooks: + # - id: flake8 + # additional_dependencies: + # [ + # "flake8-bugbear==20.1.4", + # "flake8-logging-format==0.6.0", + # "flake8-implicit-str-concat==0.2.0", + # ] + + - repo: https://github.com/pre-commit/mirrors-eslint + rev: v8.8.0 + hooks: + - id: eslint + + - repo: https://github.com/sirosen/check-jsonschema + rev: 0.10.2 + hooks: + - id: check-jsonschema + name: "Check GitHub Workflows" + files: ^\.github/workflows/ + types: [yaml] + args: ["--schemafile", "https://json.schemastore.org/github-workflow"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 90c46b372..e20fec75f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -119,7 +119,7 @@ ### Documentation improvements -- Fix title position in changelog [#828](https://github.com/ipython/ipykernel/pull/828) ([@fcollonval](https://github.com/fcollonval)) +- Fix title position in changelog [#828](https://github.com/ipython/ipykernel/pull/828) ([@fcollonval](https://github.com/fcollonval)) ### Contributors to this release @@ -366,30 +366,30 @@ release and welcome any feedback (~50 Pull-requests). IPykernel 6 should contain all changes of the 5.x series, in addition to the following non-exhaustive changes. - - Support for the debugger protocol, when using `JupyterLab`, `RetroLab` or any - frontend supporting the debugger protocol you should have access to the - debugger functionalities. +- Support for the debugger protocol, when using `JupyterLab`, `RetroLab` or any + frontend supporting the debugger protocol you should have access to the + debugger functionalities. - - The control channel on IPykernel 6.0 is run in a separate thread, this may - change the order in which messages are processed, though this change was necessary - to accommodate the debugger. +- The control channel on IPykernel 6.0 is run in a separate thread, this may + change the order in which messages are processed, though this change was necessary + to accommodate the debugger. - - We now have a new dependency: `matplotlib-inline`, this helps to separate the - circular dependency between IPython/IPykernel and matplotlib. +- We now have a new dependency: `matplotlib-inline`, this helps to separate the + circular dependency between IPython/IPykernel and matplotlib. - - On POSIX systems, all outputs to stdout/stderr should now be captured, - including subprocesses and output of compiled libraries (blas, lapack....). - In notebook server, some outputs that would previously go to the notebooks - logs will now both head to notebook logs and in notebooks outputs. In - terminal frontend like Jupyter Console, Emacs or other, this may ends up as - duplicated outputs. +- On POSIX systems, all outputs to stdout/stderr should now be captured, + including subprocesses and output of compiled libraries (blas, lapack....). + In notebook server, some outputs that would previously go to the notebooks + logs will now both head to notebook logs and in notebooks outputs. In + terminal frontend like Jupyter Console, Emacs or other, this may ends up as + duplicated outputs. - - coroutines are now native (async-def) , instead of using tornado's - `@gen.coroutine` +- coroutines are now native (async-def) , instead of using tornado's + `@gen.coroutine` - - OutStreams can now be configured to report `istty() == True`, while this - should make some output nicer (for example colored), it is likely to break - others. Use with care. +- OutStreams can now be configured to report `istty() == True`, while this + should make some output nicer (for example colored), it is likely to break + others. Use with care. ### New features added @@ -471,13 +471,13 @@ following non-exhaustive changes. ### Deprecations in 6.0 - `Kernel`s now support only a single shell stream, multiple streams will now be ignored. The attribute - `Kernel.shell_streams` (plural) is deprecated in ipykernel 6.0. Use `Kernel.shell_stream` (singular) + `Kernel.shell_streams` (plural) is deprecated in ipykernel 6.0. Use `Kernel.shell_stream` (singular) - `Kernel._parent_header` is deprecated, even though it was private. Use `.get_parent()` now. ### Removal in 6.0 - `ipykernel.codeutils` was deprecated since 4.x series (2016) and has been removed, please import similar - functionalities from `ipyparallel` + functionalities from `ipyparallel` - remove `find_connection_file` and `profile` argument of `connect_qtconsole` and `get_connection_info`, deprecated since IPykernel 4.2.2 (2016). ### Contributors to this release @@ -489,116 +489,122 @@ following non-exhaustive changes. ## 5.5 ### 5.5.5 -* Keep preferring SelectorEventLoop on Windows. [#669](https://github.com/ipython/ipykernel/pull/669) + +- Keep preferring SelectorEventLoop on Windows. [#669](https://github.com/ipython/ipykernel/pull/669) ### 5.5.4 -* Import ``configure_inline_support`` from ``matplotlib_inline`` if available [#654](https://github.com/ipython/ipykernel/pull/654) - + +- Import `configure_inline_support` from `matplotlib_inline` if available [#654](https://github.com/ipython/ipykernel/pull/654) + ### 5.5.3 -* Revert Backport of #605: Fix Handling of ``shell.should_run_async`` [#622](https://github.com/ipython/ipykernel/pull/622) + +- Revert Backport of #605: Fix Handling of `shell.should_run_async` [#622](https://github.com/ipython/ipykernel/pull/622) ### 5.5.2 + **Note:** This release was deleted from PyPI since it had breaking changes. -* Changed default timeout to 0.0 seconds for stop_on_error_timeout. [#618](https://github.com/ipython/ipykernel/pull/618) +- Changed default timeout to 0.0 seconds for stop_on_error_timeout. [#618](https://github.com/ipython/ipykernel/pull/618) ### 5.5.1 + **Note:** This release was deleted from PyPI since it had breaking changes. -* Fix Handling of ``shell.should_run_async``. [#605](https://github.com/ipython/ipykernel/pull/605) +- Fix Handling of `shell.should_run_async`. [#605](https://github.com/ipython/ipykernel/pull/605) ### 5.5.0 -* kernelspec: ensure path is writable before writing `kernel.json`. [#593](https://github.com/ipython/ipykernel/pull/593) -* Add `configure_inline_support` and call it in the shell. [#590](https://github.com/ipython/ipykernel/pull/590) -* Fix `stop_on_error_timeout` to now properly abort `execute_request`'s that fall within the timeout after an error. [#572](https://github.com/ipython/ipykernel/pull/572) + +- kernelspec: ensure path is writable before writing `kernel.json`. [#593](https://github.com/ipython/ipykernel/pull/593) +- Add `configure_inline_support` and call it in the shell. [#590](https://github.com/ipython/ipykernel/pull/590) +- Fix `stop_on_error_timeout` to now properly abort `execute_request`'s that fall within the timeout after an error. [#572](https://github.com/ipython/ipykernel/pull/572) ## 5.4 ### 5.4.3 -* Rework `wait_for_ready` logic. [#578](https://github.com/ipython/ipykernel/pull/578) +- Rework `wait_for_ready` logic. [#578](https://github.com/ipython/ipykernel/pull/578) ### 5.4.2 -* Revert \"Fix stop_on_error_timeout blocking other messages in - queue\". [#570](https://github.com/ipython/ipykernel/pull/570) +- Revert \"Fix stop_on_error_timeout blocking other messages in + queue\". [#570](https://github.com/ipython/ipykernel/pull/570) ### 5.4.1 -* Invalid syntax in `ipykernel/log.py`. [#567](https://github.com/ipython/ipykernel/pull/567) +- Invalid syntax in `ipykernel/log.py`. [#567](https://github.com/ipython/ipykernel/pull/567) ### 5.4.0 5.4.0 is generally focused on code quality improvements and tornado asyncio compatibility. -* Add github actions, bail on asyncio patch for tornado 6.1. - [#564](https://github.com/ipython/ipykernel/pull/564) -* Start testing on Python 3.9. [#551](https://github.com/ipython/ipykernel/pull/551) -* Fix stack levels for ipykernel\'s deprecation warnings and stop - using some deprecated APIs. [#547](https://github.com/ipython/ipykernel/pull/547) -* Add env parameter to kernel installation [#541](https://github.com/ipython/ipykernel/pull/541) -* Fix stop_on_error_timeout blocking other messages in queue. - [#539](https://github.com/ipython/ipykernel/pull/539) -* Remove most of the python 2 compat code. [#537](https://github.com/ipython/ipykernel/pull/537) -* Remove u-prefix from strings. [#538](https://github.com/ipython/ipykernel/pull/538) +- Add github actions, bail on asyncio patch for tornado 6.1. + [#564](https://github.com/ipython/ipykernel/pull/564) +- Start testing on Python 3.9. [#551](https://github.com/ipython/ipykernel/pull/551) +- Fix stack levels for ipykernel\'s deprecation warnings and stop + using some deprecated APIs. [#547](https://github.com/ipython/ipykernel/pull/547) +- Add env parameter to kernel installation [#541](https://github.com/ipython/ipykernel/pull/541) +- Fix stop_on_error_timeout blocking other messages in queue. + [#539](https://github.com/ipython/ipykernel/pull/539) +- Remove most of the python 2 compat code. [#537](https://github.com/ipython/ipykernel/pull/537) +- Remove u-prefix from strings. [#538](https://github.com/ipython/ipykernel/pull/538) ## 5.3 ### 5.3.4 -* Only run Qt eventloop in the shell stream. [#531](https://github.com/ipython/ipykernel/pull/531) +- Only run Qt eventloop in the shell stream. [#531](https://github.com/ipython/ipykernel/pull/531) ### 5.3.3 -* Fix QSocketNotifier in the Qt event loop not being disabled for the - control channel. [#525](https://github.com/ipython/ipykernel/pull/525) +- Fix QSocketNotifier in the Qt event loop not being disabled for the + control channel. [#525](https://github.com/ipython/ipykernel/pull/525) ### 5.3.2 -* Restore timer based event loop as a Windows-compatible fallback. - [#523](https://github.com/ipython/ipykernel/pull/523) +- Restore timer based event loop as a Windows-compatible fallback. + [#523](https://github.com/ipython/ipykernel/pull/523) ### 5.3.1 -* Fix \#520: run post_execute and post_run_cell on async cells - [#521](https://github.com/ipython/ipykernel/pull/521) -* Fix exception causes in zmqshell.py [#516](https://github.com/ipython/ipykernel/pull/516) -* Make pdb on Windows interruptible [#490](https://github.com/ipython/ipykernel/pull/490) +- Fix \#520: run post_execute and post_run_cell on async cells + [#521](https://github.com/ipython/ipykernel/pull/521) +- Fix exception causes in zmqshell.py [#516](https://github.com/ipython/ipykernel/pull/516) +- Make pdb on Windows interruptible [#490](https://github.com/ipython/ipykernel/pull/490) ### 5.3.0 5.3.0 Adds support for Trio event loops and has some bug fixes. -* Fix ipython display imports [#509](https://github.com/ipython/ipykernel/pull/509) -* Skip test_unc_paths if OS is not Windows [#507](https://github.com/ipython/ipykernel/pull/507) -* Allow interrupting input() on Windows, as part of effort to make pdb - interruptible [#498](https://github.com/ipython/ipykernel/pull/498) -* Add Trio Loop [#479](https://github.com/ipython/ipykernel/pull/479) -* Flush from process even without newline [#478](https://github.com/ipython/ipykernel/pull/478) +- Fix ipython display imports [#509](https://github.com/ipython/ipykernel/pull/509) +- Skip test_unc_paths if OS is not Windows [#507](https://github.com/ipython/ipykernel/pull/507) +- Allow interrupting input() on Windows, as part of effort to make pdb + interruptible [#498](https://github.com/ipython/ipykernel/pull/498) +- Add Trio Loop [#479](https://github.com/ipython/ipykernel/pull/479) +- Flush from process even without newline [#478](https://github.com/ipython/ipykernel/pull/478) ## 5.2 ### 5.2.1 -* Handle system commands that use UNC paths on Windows - [#500](https://github.com/ipython/ipykernel/pull/500) -* Add offset argument to seek in io test [#496](https://github.com/ipython/ipykernel/pull/496) +- Handle system commands that use UNC paths on Windows + [#500](https://github.com/ipython/ipykernel/pull/500) +- Add offset argument to seek in io test [#496](https://github.com/ipython/ipykernel/pull/496) ### 5.2.0 5.2.0 Includes several bugfixes and internal logic improvements. -* Produce better traceback when kernel is interrupted - [#491](https://github.com/ipython/ipykernel/pull/491) -* Add `InProcessKernelClient.control_channel` for compatibility with - jupyter-client v6.0.0 [#489](https://github.com/ipython/ipykernel/pull/489) -* Drop support for Python 3.4 [#483](https://github.com/ipython/ipykernel/pull/483) -* Work around issue related to Tornado with python3.8 on Windows - ([#480](https://github.com/ipython/ipykernel/pull/480), [#481](https://github.com/ipython/ipykernel/pull/481)) -* Prevent entering event loop if it is None [#464](https://github.com/ipython/ipykernel/pull/464) -* Use `shell.input_transformer_manager` when available - [#411](https://github.com/ipython/ipykernel/pull/411) +- Produce better traceback when kernel is interrupted + [#491](https://github.com/ipython/ipykernel/pull/491) +- Add `InProcessKernelClient.control_channel` for compatibility with + jupyter-client v6.0.0 [#489](https://github.com/ipython/ipykernel/pull/489) +- Drop support for Python 3.4 [#483](https://github.com/ipython/ipykernel/pull/483) +- Work around issue related to Tornado with python3.8 on Windows + ([#480](https://github.com/ipython/ipykernel/pull/480), [#481](https://github.com/ipython/ipykernel/pull/481)) +- Prevent entering event loop if it is None [#464](https://github.com/ipython/ipykernel/pull/464) +- Use `shell.input_transformer_manager` when available + [#411](https://github.com/ipython/ipykernel/pull/411) ## 5.1 @@ -607,48 +613,48 @@ asyncio compatibility. 5.1.4 Includes a few bugfixes, especially for compatibility with Python 3.8 on Windows. -* Fix pickle issues when using inline matplotlib backend - [#476](https://github.com/ipython/ipykernel/pull/476) -* Fix an error during kernel shutdown [#463](https://github.com/ipython/ipykernel/pull/463) -* Fix compatibility issues with Python 3.8 ([#456](https://github.com/ipython/ipykernel/pull/456), [#461](https://github.com/ipython/ipykernel/pull/461)) -* Remove some dead code ([#474](https://github.com/ipython/ipykernel/pull/474), - [#467](https://github.com/ipython/ipykernel/pull/467)) +- Fix pickle issues when using inline matplotlib backend + [#476](https://github.com/ipython/ipykernel/pull/476) +- Fix an error during kernel shutdown [#463](https://github.com/ipython/ipykernel/pull/463) +- Fix compatibility issues with Python 3.8 ([#456](https://github.com/ipython/ipykernel/pull/456), [#461](https://github.com/ipython/ipykernel/pull/461)) +- Remove some dead code ([#474](https://github.com/ipython/ipykernel/pull/474), + [#467](https://github.com/ipython/ipykernel/pull/467)) ### 5.1.3 5.1.3 Includes several bugfixes and internal logic improvements. -* Fix comm shutdown behavior by adding a `deleting` option to `close` - which can be set to prevent registering new comm channels during - shutdown ([#433](https://github.com/ipython/ipykernel/pull/433), [#435](https://github.com/ipython/ipykernel/pull/435)) -* Fix `Heartbeat._bind_socket` to return on the first bind ([#431](https://github.com/ipython/ipykernel/pull/431)) -* Moved `InProcessKernelClient.flush` to `DummySocket` ([#437](https://github.com/ipython/ipykernel/pull/437)) -* Don\'t redirect stdout if nose machinery is not present ([#427](https://github.com/ipython/ipykernel/pull/427)) -* Rename `_asyncio.py` to - `_asyncio_utils.py` to avoid name conflicts on Python - 3.6+ ([#426](https://github.com/ipython/ipykernel/pull/426)) -* Only generate kernelspec when installing or building wheel ([#425](https://github.com/ipython/ipykernel/pull/425)) -* Fix priority ordering of control-channel messages in some cases - [#443](https://github.com/ipython/ipykernel/pull/443) +- Fix comm shutdown behavior by adding a `deleting` option to `close` + which can be set to prevent registering new comm channels during + shutdown ([#433](https://github.com/ipython/ipykernel/pull/433), [#435](https://github.com/ipython/ipykernel/pull/435)) +- Fix `Heartbeat._bind_socket` to return on the first bind ([#431](https://github.com/ipython/ipykernel/pull/431)) +- Moved `InProcessKernelClient.flush` to `DummySocket` ([#437](https://github.com/ipython/ipykernel/pull/437)) +- Don\'t redirect stdout if nose machinery is not present ([#427](https://github.com/ipython/ipykernel/pull/427)) +- Rename `_asyncio.py` to + `_asyncio_utils.py` to avoid name conflicts on Python + 3.6+ ([#426](https://github.com/ipython/ipykernel/pull/426)) +- Only generate kernelspec when installing or building wheel ([#425](https://github.com/ipython/ipykernel/pull/425)) +- Fix priority ordering of control-channel messages in some cases + [#443](https://github.com/ipython/ipykernel/pull/443) ### 5.1.2 5.1.2 fixes some socket-binding race conditions that caused testing failures in nbconvert. -* Fix socket-binding race conditions ([#412](https://github.com/ipython/ipykernel/pull/412), - [#419](https://github.com/ipython/ipykernel/pull/419)) -* Add a no-op `flush` method to `DummySocket` and comply with stream - API ([#405](https://github.com/ipython/ipykernel/pull/405)) -* Update kernel version to indicate kernel v5.3 support ([#394](https://github.com/ipython/ipykernel/pull/394)) -* Add testing for upcoming Python 3.8 and PEP 570 positional - parameters ([#396](https://github.com/ipython/ipykernel/pull/396), [#408](https://github.com/ipython/ipykernel/pull/408)) +- Fix socket-binding race conditions ([#412](https://github.com/ipython/ipykernel/pull/412), + [#419](https://github.com/ipython/ipykernel/pull/419)) +- Add a no-op `flush` method to `DummySocket` and comply with stream + API ([#405](https://github.com/ipython/ipykernel/pull/405)) +- Update kernel version to indicate kernel v5.3 support ([#394](https://github.com/ipython/ipykernel/pull/394)) +- Add testing for upcoming Python 3.8 and PEP 570 positional + parameters ([#396](https://github.com/ipython/ipykernel/pull/396), [#408](https://github.com/ipython/ipykernel/pull/408)) ### 5.1.1 5.1.1 fixes a bug that caused cells to get stuck in a busy state. -* Flush after sending replies [#390](https://github.com/ipython/ipykernel/pull/390) +- Flush after sending replies [#390](https://github.com/ipython/ipykernel/pull/390) ### 5.1.0 @@ -656,13 +662,13 @@ failures in nbconvert. [5.1.0 on GitHub](https://github.com/ipython/ipykernel/milestones/5.1) -* Fix message-ordering bug that could result in out-of-order - executions, especially on Windows [#356](https://github.com/ipython/ipykernel/pull/356) -* Fix classifiers to indicate dropped Python 2 support - [#354](https://github.com/ipython/ipykernel/pull/354) -* Remove some dead code [#355](https://github.com/ipython/ipykernel/pull/355) -* Support rich-media responses in `inspect_requests` (tooltips) - [#361](https://github.com/ipython/ipykernel/pull/361) +- Fix message-ordering bug that could result in out-of-order + executions, especially on Windows [#356](https://github.com/ipython/ipykernel/pull/356) +- Fix classifiers to indicate dropped Python 2 support + [#354](https://github.com/ipython/ipykernel/pull/354) +- Remove some dead code [#355](https://github.com/ipython/ipykernel/pull/355) +- Support rich-media responses in `inspect_requests` (tooltips) + [#361](https://github.com/ipython/ipykernel/pull/361) ## 5.0 @@ -670,18 +676,18 @@ failures in nbconvert. [5.0.0 on GitHub](https://github.com/ipython/ipykernel/milestones/5.0) -* Drop support for Python 2. `ipykernel` 5.0 requires Python \>= 3.4 -* Add support for IPython\'s asynchronous code execution - [#323](https://github.com/ipython/ipykernel/pull/323) -* Update release process in `CONTRIBUTING.md` [#339](https://github.com/ipython/ipykernel/pull/339) +- Drop support for Python 2. `ipykernel` 5.0 requires Python \>= 3.4 +- Add support for IPython\'s asynchronous code execution + [#323](https://github.com/ipython/ipykernel/pull/323) +- Update release process in `CONTRIBUTING.md` [#339](https://github.com/ipython/ipykernel/pull/339) ## 4.10 [4.10 on GitHub](https://github.com/ipython/ipykernel/milestones/4.10) -* Fix compatibility with IPython 7.0 [#348](https://github.com/ipython/ipykernel/pull/348) -* Fix compatibility in cases where sys.stdout can be None - [#344](https://github.com/ipython/ipykernel/pull/344) +- Fix compatibility with IPython 7.0 [#348](https://github.com/ipython/ipykernel/pull/348) +- Fix compatibility in cases where sys.stdout can be None + [#344](https://github.com/ipython/ipykernel/pull/344) ## 4.9 @@ -689,14 +695,14 @@ failures in nbconvert. [4.9.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.9) -* Python 3.3 is no longer supported [#336](https://github.com/ipython/ipykernel/pull/336) -* Flush stdout/stderr in KernelApp before replacing - [#314](https://github.com/ipython/ipykernel/pull/314) -* Allow preserving stdout and stderr in KernelApp - [#315](https://github.com/ipython/ipykernel/pull/315) -* Override writable method on OutStream [#316](https://github.com/ipython/ipykernel/pull/316) -* Add metadata to help display matplotlib figures legibly - [#336](https://github.com/ipython/ipykernel/pull/336) +- Python 3.3 is no longer supported [#336](https://github.com/ipython/ipykernel/pull/336) +- Flush stdout/stderr in KernelApp before replacing + [#314](https://github.com/ipython/ipykernel/pull/314) +- Allow preserving stdout and stderr in KernelApp + [#315](https://github.com/ipython/ipykernel/pull/315) +- Override writable method on OutStream [#316](https://github.com/ipython/ipykernel/pull/316) +- Add metadata to help display matplotlib figures legibly + [#336](https://github.com/ipython/ipykernel/pull/336) ## 4.8 @@ -704,29 +710,29 @@ failures in nbconvert. [4.8.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8.2) -* Fix compatibility issue with qt eventloop and pyzmq 17 - [#307](https://github.com/ipython/ipykernel/pull/307). +- Fix compatibility issue with qt eventloop and pyzmq 17 + [#307](https://github.com/ipython/ipykernel/pull/307). ### 4.8.1 [4.8.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8.1) -* set zmq.ROUTER_HANDOVER socket option when available to workaround - libzmq reconnect bug [#300](https://github.com/ipython/ipykernel/pull/300). -* Fix sdists including absolute paths for kernelspec files, which - prevented installation from sdist on Windows - [#306](https://github.com/ipython/ipykernel/pull/306). +- set zmq.ROUTER_HANDOVER socket option when available to workaround + libzmq reconnect bug [#300](https://github.com/ipython/ipykernel/pull/300). +- Fix sdists including absolute paths for kernelspec files, which + prevented installation from sdist on Windows + [#306](https://github.com/ipython/ipykernel/pull/306). ### 4.8.0 [4.8.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8) -* Cleanly shutdown integrated event loops when shutting down the - kernel. [#290](https://github.com/ipython/ipykernel/pull/290) -* `%gui qt` now uses Qt 5 by default rather than Qt 4, following a - similar change in terminal IPython. [#293](https://github.com/ipython/ipykernel/pull/293) -* Fix event loop integration for `asyncio` when run with Tornado 5, which uses asyncio where - available. [#296](https://github.com/ipython/ipykernel/pull/296) +- Cleanly shutdown integrated event loops when shutting down the + kernel. [#290](https://github.com/ipython/ipykernel/pull/290) +- `%gui qt` now uses Qt 5 by default rather than Qt 4, following a + similar change in terminal IPython. [#293](https://github.com/ipython/ipykernel/pull/293) +- Fix event loop integration for `asyncio` when run with Tornado 5, which uses asyncio where + available. [#296](https://github.com/ipython/ipykernel/pull/296) ## 4.7 @@ -734,14 +740,14 @@ failures in nbconvert. [4.7.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.7) -* Add event loop integration for `asyncio`. -* Use the new IPython completer API. -* Add support for displaying GIF images (mimetype `image/gif`). -* Allow the kernel to be interrupted without killing the Qt console. -* Fix `is_complete` response with cell magics. -* Clean up encoding of bytes objects. -* Clean up help links to use `https` and improve display titles. -* Clean up ioloop handling in preparation for tornado 5. +- Add event loop integration for `asyncio`. +- Use the new IPython completer API. +- Add support for displaying GIF images (mimetype `image/gif`). +- Allow the kernel to be interrupted without killing the Qt console. +- Fix `is_complete` response with cell magics. +- Clean up encoding of bytes objects. +- Clean up help links to use `https` and improve display titles. +- Clean up ioloop handling in preparation for tornado 5. ## 4.6 @@ -749,52 +755,52 @@ failures in nbconvert. [4.6.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.6.1) -* Fix eventloop-integration bug preventing Qt windows/widgets from - displaying with ipykernel 4.6.0 and IPython ≥ 5.2. -* Avoid deprecation warnings about naive datetimes when working with - jupyter_client ≥ 5.0. +- Fix eventloop-integration bug preventing Qt windows/widgets from + displaying with ipykernel 4.6.0 and IPython ≥ 5.2. +- Avoid deprecation warnings about naive datetimes when working with + jupyter_client ≥ 5.0. ### 4.6.0 [4.6.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.6) -* Add to API `DisplayPublisher.publish` two new fully - backward-compatible keyword-args: +- Add to API `DisplayPublisher.publish` two new fully + backward-compatible keyword-args: - > * `update: bool` - > * `transient: dict` + > - `update: bool` + > - `transient: dict` -* Support new `transient` key in - `display_data` messages spec for `publish`. - For a display data message, `transient` contains data - that shouldn\'t be persisted to files or documents. Add a - `display_id` to this `transient` dict by - `display(obj, display_id=\...)` +- Support new `transient` key in + `display_data` messages spec for `publish`. + For a display data message, `transient` contains data + that shouldn\'t be persisted to files or documents. Add a + `display_id` to this `transient` dict by + `display(obj, display_id=\...)` -* Add `ipykernel_launcher` module which removes the - current working directory from `sys.path` before - launching the kernel. This helps to reduce the cases where the - kernel won\'t start because there\'s a `random.py` (or - similar) module in the current working directory. +- Add `ipykernel_launcher` module which removes the + current working directory from `sys.path` before + launching the kernel. This helps to reduce the cases where the + kernel won\'t start because there\'s a `random.py` (or + similar) module in the current working directory. -* Add busy/idle messages on IOPub during processing of aborted - requests +- Add busy/idle messages on IOPub during processing of aborted + requests -* Add active event loop setting to GUI, which enables the correct - response to IPython\'s `is_event_loop_running_xxx` +- Add active event loop setting to GUI, which enables the correct + response to IPython\'s `is_event_loop_running_xxx` -* Include IPython kernelspec in wheels to reduce reliance on \"native - kernel spec\" in jupyter_client +- Include IPython kernelspec in wheels to reduce reliance on \"native + kernel spec\" in jupyter_client -* Modify `OutStream` to inherit from - `TextIOBase` instead of object to improve API support - and error reporting +- Modify `OutStream` to inherit from + `TextIOBase` instead of object to improve API support + and error reporting -* Fix IPython kernel death messages at start, such as \"Kernel - Restarting\...\" and \"Kernel appears to have died\", when - parent-poller handles PID 1 +- Fix IPython kernel death messages at start, such as \"Kernel + Restarting\...\" and \"Kernel appears to have died\", when + parent-poller handles PID 1 -* Various bugfixes +- Various bugfixes ## 4.5 @@ -802,31 +808,31 @@ failures in nbconvert. [4.5.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5.2) -* Fix bug when instantiating Comms outside of the IPython kernel - (introduced in 4.5.1). +- Fix bug when instantiating Comms outside of the IPython kernel + (introduced in 4.5.1). ### 4.5.1 [4.5.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5.1) -* Add missing `stream` parameter to overridden - `getpass` -* Remove locks from iopub thread, which could cause deadlocks during - debugging -* Fix regression where KeyboardInterrupt was treated as an aborted - request, rather than an error -* Allow instantiating Comms outside of the IPython kernel +- Add missing `stream` parameter to overridden + `getpass` +- Remove locks from iopub thread, which could cause deadlocks during + debugging +- Fix regression where KeyboardInterrupt was treated as an aborted + request, rather than an error +- Allow instantiating Comms outside of the IPython kernel ### 4.5.0 [4.5 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5) -* Use figure.dpi instead of savefig.dpi to set DPI for inline figures -* Support ipympl matplotlib backend (requires IPython update as well - to fully work) -* Various bugfixes, including fixes for output coming from threads, - and `input` when called with - non-string prompts, which stdlib allows. +- Use figure.dpi instead of savefig.dpi to set DPI for inline figures +- Support ipympl matplotlib backend (requires IPython update as well + to fully work) +- Various bugfixes, including fixes for output coming from threads, + and `input` when called with + non-string prompts, which stdlib allows. ## 4.4 @@ -834,61 +840,62 @@ failures in nbconvert. [4.4.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.4.1) -* Fix circular import of matplotlib on Python 2 caused by the inline - backend changes in 4.4.0. +- Fix circular import of matplotlib on Python 2 caused by the inline + backend changes in 4.4.0. ### 4.4.0 [4.4.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.4) -* Use - [MPLBACKEND](http://matplotlib.org/devel/coding_guide.html?highlight=mplbackend#developing-a-new-backend) - environment variable to tell matplotlib \>= 1.5 use use the inline - backend by default. This is only done if MPLBACKEND is not already - set and no backend has been explicitly loaded, so setting - `MPLBACKEND=Qt4Agg` or calling `%matplotlib notebook` or - `matplotlib.use('Agg')` will take precedence. -* Fixes for logging problems caused by 4.3, where logging could go to - the terminal instead of the notebook. -* Add `--sys-prefix` and `--profile` arguments to - `ipython kernel install`. -* Allow Comm (Widget) messages to be sent from background threads. -* Select inline matplotlib backend by default if `%matplotlib` magic - or `matplotlib.use()` are not called explicitly (for matplotlib \>= - 1.5). -* Fix some longstanding minor deviations from the message protocol - (missing status: ok in a few replies, connect_reply format). -* Remove calls to NoOpContext from IPython, deprecated in 5.0. +- Use + [MPLBACKEND](http://matplotlib.org/devel/coding_guide.html?highlight=mplbackend#developing-a-new-backend) + environment variable to tell matplotlib \>= 1.5 use use the inline + backend by default. This is only done if MPLBACKEND is not already + set and no backend has been explicitly loaded, so setting + `MPLBACKEND=Qt4Agg` or calling `%matplotlib notebook` or + `matplotlib.use('Agg')` will take precedence. +- Fixes for logging problems caused by 4.3, where logging could go to + the terminal instead of the notebook. +- Add `--sys-prefix` and `--profile` arguments to + `ipython kernel install`. +- Allow Comm (Widget) messages to be sent from background threads. +- Select inline matplotlib backend by default if `%matplotlib` magic + or `matplotlib.use()` are not called explicitly (for matplotlib \>= + 1.5). +- Fix some longstanding minor deviations from the message protocol + (missing status: ok in a few replies, connect_reply format). +- Remove calls to NoOpContext from IPython, deprecated in 5.0. ## 4.3 ### 4.3.2 -* Use a nonempty dummy session key for inprocess kernels to avoid - security warnings. +- Use a nonempty dummy session key for inprocess kernels to avoid + security warnings. ### 4.3.1 -* Fix Windows Python 3.5 incompatibility caused by faulthandler patch - in 4.3 +- Fix Windows Python 3.5 incompatibility caused by faulthandler patch + in 4.3 ### 4.3.0 [4.3.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.3) -* Publish all IO in a thread, via `IOPubThread`. This solves the problem of requiring - `sys.stdout.flush` to be called in - the notebook to produce output promptly during long-running cells. -* Remove references to outdated IPython guiref in kernel banner. -* Patch faulthandler to use `sys.__stderr__` instead of forwarded - `sys.stderr`, which has no fileno when forwarded. -* Deprecate some vestiges of the Big Split: - * `ipykernel.find_connection_file` - is deprecated. Use - `jupyter_client.find_connection_file` instead. +- Publish all IO in a thread, via `IOPubThread`. This solves the problem of requiring + `sys.stdout.flush` to be called in + the notebook to produce output promptly during long-running cells. +- Remove references to outdated IPython guiref in kernel banner. +- Patch faulthandler to use `sys.__stderr__` instead of forwarded + `sys.stderr`, which has no fileno when forwarded. +- Deprecate some vestiges of the Big Split: + + - `ipykernel.find_connection_file` + is deprecated. Use + `jupyter_client.find_connection_file` instead. - \- Various pieces of code specific to IPython parallel are - deprecated in ipykernel and moved to ipyparallel. + \- Various pieces of code specific to IPython parallel are + deprecated in ipykernel and moved to ipyparallel. ## 4.2 @@ -896,24 +903,24 @@ failures in nbconvert. [4.2.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2.2) -* Don\'t show interactive debugging info when kernel crashes -* Fix handling of numerical types in json_clean -* Testing fixes for output capturing +- Don\'t show interactive debugging info when kernel crashes +- Fix handling of numerical types in json_clean +- Testing fixes for output capturing ### 4.2.1 [4.2.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2.1) -* Fix default display name back to \"Python X\" instead of \"pythonX\" +- Fix default display name back to \"Python X\" instead of \"pythonX\" ### 4.2.0 [4.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2) -* Support sending a full message in initial opening of comms - (metadata, buffers were not previously allowed) -* When using `ipython kernel install --name` to install the IPython - kernelspec, default display-name to the same value as `--name`. +- Support sending a full message in initial opening of comms + (metadata, buffers were not previously allowed) +- When using `ipython kernel install --name` to install the IPython + kernelspec, default display-name to the same value as `--name`. ## 4.1 @@ -921,17 +928,17 @@ failures in nbconvert. [4.1.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.1.1) -* Fix missing `ipykernel.__version__` on Python 2. -* Fix missing `target_name` when opening comms from the frontend. +- Fix missing `ipykernel.__version__` on Python 2. +- Fix missing `target_name` when opening comms from the frontend. ### 4.1.0 [4.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.1) -* add `ipython kernel install` entrypoint for installing the IPython - kernelspec -* provisional implementation of `comm_info` request/reply for msgspec - v5.1 +- add `ipython kernel install` entrypoint for installing the IPython + kernelspec +- provisional implementation of `comm_info` request/reply for msgspec + v5.1 ## 4.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 28e0160da..e54fe77f5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,7 @@ pip install -e . ``` ## Code Styling + `ipykernel` has adopted automatic code formatting so you shouldn't need to worry too much about your code style. As long as your code is valid, @@ -27,7 +28,6 @@ To install `pre-commit`, run the following:: pip install pre-commit pre-commit install - You can invoke the pre-commit hook by hand at any time with:: pre-commit run @@ -38,13 +38,13 @@ You may also install [black integration](https://github.com/psf/black#editor-int into your text editor to format code automatically. If you have already committed files before setting up the pre-commit -hook with ``pre-commit install``, you can fix everything up using -``pre-commit run --all-files``. You need to make the fixing commit +hook with `pre-commit install`, you can fix everything up using +`pre-commit run --all-files`. You need to make the fixing commit yourself after that. ## Releasing ipykernel -Releasing ipykernel is *almost* standard for a Python package: +Releasing ipykernel is _almost_ standard for a Python package: - set version for release - make and publish tag diff --git a/COPYING.md b/COPYING.md index 93f45a894..0ccda3eb1 100644 --- a/COPYING.md +++ b/COPYING.md @@ -24,7 +24,7 @@ software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER @@ -46,8 +46,8 @@ IPython uses a shared copyright model. Each contributor maintains copyright over their contributions to IPython. But, it is important to note that these contributions are typically only changes to the repositories. Thus, the IPython source code, in its entirety is not the copyright of any single person or -institution. Instead, it is the collective copyright of the entire IPython -Development Team. If individual contributors want to maintain a record of what +institution. Instead, it is the collective copyright of the entire IPython +Development Team. If individual contributors want to maintain a record of what changes/contributions they have specific copyright on, they should indicate their copyright in the commit message of the change, when they commit the change to one of the IPython repositories. diff --git a/RELEASE.md b/RELEASE.md index 21c93a25d..2f3954582 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -20,6 +20,6 @@ git push --all git push --tags rm -rf dist build python -m build . -twine check dist/* +twine check dist/* twine upload dist/* ``` diff --git a/docs/index.rst b/docs/index.rst index c83dff2be..f58fc62fc 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,4 +20,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/docs/requirements.txt b/docs/requirements.txt index 3026b0bb2..b3608d506 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,2 +1,2 @@ -sphinxcontrib_github_alt myst_parser +sphinxcontrib_github_alt diff --git a/examples/embedding/internal_ipkernel.py b/examples/embedding/internal_ipkernel.py index f5a1b3577..a0e89f5cc 100644 --- a/examples/embedding/internal_ipkernel.py +++ b/examples/embedding/internal_ipkernel.py @@ -27,7 +27,7 @@ def init_ipkernel(self, backend): self.ipkernel = mpl_kernel(backend) # To create and track active qt consoles self.consoles = [] - + # This application will also act on the shell user namespace self.namespace = self.ipkernel.shell.user_ns diff --git a/examples/embedding/ipkernel_qtapp.py b/examples/embedding/ipkernel_qtapp.py index 601c74884..4f9789580 100755 --- a/examples/embedding/ipkernel_qtapp.py +++ b/examples/embedding/ipkernel_qtapp.py @@ -69,7 +69,7 @@ def add_widgets(self): # Create our window win = SimpleWindow(app) win.show() - + # Very important, IPython-specific step: this gets GUI event loop # integration going, and it replaces calling app.exec_() win.ipkernel.start() diff --git a/ipykernel/__init__.py b/ipykernel/__init__.py index 6c7e5bc07..3f753e3ee 100644 --- a/ipykernel/__init__.py +++ b/ipykernel/__init__.py @@ -1,2 +1,2 @@ from ._version import version_info, __version__, kernel_protocol_version_info, kernel_protocol_version -from .connect import * \ No newline at end of file +from .connect import * diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index c70840369..631b67b13 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -117,7 +117,7 @@ def comm_close(self, stream, ident, msg): comm = self.get_comm(comm_id) if comm is None: return - + self.comms[comm_id]._closed = True del self.comms[comm_id] diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index dfe5d0a9b..f229c9402 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -270,7 +270,7 @@ class Debugger: # Requests that can be handled even if the debugger is not running static_debug_msg_types = [ - 'debugInfo', 'inspectVariables', + 'debugInfo', 'inspectVariables', 'richInspectVariables', 'modules' ] @@ -349,7 +349,7 @@ def _accept_stopped_thread(self, thread_name): 'Thread-4' ] return thread_name not in forbid_list - + async def handle_stopped_event(self): # Wait for a stopped event message in the stopped queue # This message is used for triggering the 'threads' request diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 6a0bc639f..34e4960b0 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -68,7 +68,7 @@ def write_kernel_spec(path=None, overrides=None, extra_arguments=None): """ if path is None: path = os.path.join(tempfile.mkdtemp(suffix='_kernels'), KERNEL_NAME) - + # stage resources shutil.copytree(RESOURCES, path) @@ -84,7 +84,7 @@ def write_kernel_spec(path=None, overrides=None, extra_arguments=None): kernel_dict.update(overrides) with open(pjoin(path, 'kernel.json'), 'w') as f: json.dump(kernel_dict, f, indent=1) - + return path @@ -152,12 +152,12 @@ def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, displ class InstallIPythonKernelSpecApp(Application): """Dummy app wrapping argparse""" name = 'ipython-kernel-install' - + def initialize(self, argv=None): if argv is None: argv = sys.argv[1:] self.argv = argv - + def start(self): import argparse parser = argparse.ArgumentParser(prog=self.name, diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index fc8e4f2e3..834abea4d 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -47,7 +47,7 @@ def interactive(f): This results in the function being linked to the user_ns as globals() instead of the module globals(). """ - + # build new FunctionType, so it can have the right globals # interactive functions never have closures, that's kind of the point if isinstance(f, FunctionType): @@ -67,10 +67,10 @@ def use_dill(): """ # import dill causes most of the magic import dill - + # dill doesn't work with cPickle, # tell the two relevant modules to use plain pickle - + global pickle pickle = dill @@ -80,7 +80,7 @@ def use_dill(): pass else: serialize.pickle = dill - + # disable special function handling, let dill take care of it can_map.pop(FunctionType, None) @@ -90,7 +90,7 @@ def use_cloudpickle(): adds support for object methods and closures to serialization. """ import cloudpickle - + global pickle pickle = cloudpickle @@ -100,7 +100,7 @@ def use_cloudpickle(): pass else: serialize.pickle = cloudpickle - + # disable special function handling, let cloudpickle take care of it can_map.pop(FunctionType, None) @@ -134,7 +134,7 @@ def __init__(self, obj, keys=[], hook=None): self.hook = can(hook) for key in keys: setattr(self.obj, key, can(getattr(obj, key))) - + self.buffers = [] def get_object(self, g=None): @@ -143,12 +143,12 @@ def get_object(self, g=None): obj = self.obj for key in self.keys: setattr(obj, key, uncan(getattr(obj, key), g)) - + if self.hook: self.hook = uncan(self.hook, g) self.hook(obj, g) return self.obj - + class Reference(CannedObject): """object for wrapping a remote reference by name.""" @@ -164,7 +164,7 @@ def __repr__(self): def get_object(self, g=None): if g is None: g = {} - + return eval(self.name, g) @@ -172,7 +172,7 @@ class CannedCell(CannedObject): """Can a closure cell""" def __init__(self, cell): self.cell_contents = can(cell.cell_contents) - + def get_object(self, g=None): cell_contents = uncan(self.cell_contents, g) def inner(): @@ -189,13 +189,13 @@ def __init__(self, f): self.defaults = [ can(fd) for fd in f.__defaults__ ] else: self.defaults = None - + closure = f.__closure__ if closure: self.closure = tuple( can(cell) for cell in closure ) else: self.closure = None - + self.module = f.__module__ or '__main__' self.__name__ = f.__name__ self.buffers = [] @@ -236,7 +236,7 @@ def __init__(self, cls): mro = [] else: mro = cls.mro() - + self.parents = [ can(c) for c in mro[1:] ] self.buffers = [] @@ -267,7 +267,7 @@ def __init__(self, obj): # ensure contiguous obj = ascontiguousarray(obj, dtype=None) self.buffers = [buffer(obj)] - + def get_object(self, g=None): from numpy import frombuffer data = self.buffers[0] @@ -338,22 +338,22 @@ def istype(obj, check): def can(obj): """prepare an object for pickling""" - + import_needed = False - + for cls,canner in can_map.items(): if isinstance(cls, str): import_needed = True break elif istype(obj, cls): return canner(obj) - + if import_needed: # perform can_map imports, then try again # this will usually only happen once _import_mapping(can_map, _original_can_map) return can(obj) - + return obj def can_class(obj): @@ -384,7 +384,7 @@ def can_sequence(obj): def uncan(obj, g=None): """invert canning""" - + import_needed = False for cls,uncanner in uncan_map.items(): if isinstance(cls, str): @@ -392,13 +392,13 @@ def uncan(obj, g=None): break elif isinstance(obj, cls): return uncanner(obj, g) - + if import_needed: # perform uncan_map imports, then try again # this will usually only happen once _import_mapping(uncan_map, _original_uncan_map) return uncan(obj, g) - + return obj def uncan_dict(obj, g=None): diff --git a/ipykernel/tests/__init__.py b/ipykernel/tests/__init__.py index 58b12bfa6..399311940 100644 --- a/ipykernel/tests/__init__.py +++ b/ipykernel/tests/__init__.py @@ -29,7 +29,7 @@ def setup(): ] for p in patchers: p.start() - + # install IPython in the temp home: install(user=True) diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index 43a96ef22..d670df7fd 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -153,7 +153,7 @@ def test_stop_on_breakpoint(kernel_with_debug): wait_for_debug_request(kernel_with_debug, "configurationDone", full_reply=True) kernel_with_debug.execute(code) - + # Wait for stop on breakpoint msg = {"msg_type": "", "content": {}} while msg.get('msg_type') != 'debug_event' or msg["content"].get("event") != "stopped": @@ -189,7 +189,7 @@ def f(a, b): wait_for_debug_request(kernel_with_debug, "configurationDone", full_reply=True) kernel_with_debug.execute(code) - + # Wait for stop on breakpoint msg = {"msg_type": "", "content": {}} while msg.get('msg_type') != 'debug_event' or msg["content"].get("event") != "stopped": diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index c736a5833..94321a4d3 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -94,21 +94,21 @@ def test_install_kernelspec(): def test_install_user(): tmp = tempfile.mkdtemp() - + with mock.patch.dict(os.environ, {'HOME': tmp}): install(user=True) data_dir = jupyter_data_dir() - + assert_is_spec(os.path.join(data_dir, 'kernels', KERNEL_NAME)) def test_install(): system_jupyter_dir = tempfile.mkdtemp() - + with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', [system_jupyter_dir]): install() - + assert_is_spec(os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME)) diff --git a/ipykernel/tests/test_pickleutil.py b/ipykernel/tests/test_pickleutil.py index 0bc6e457a..d81342ae0 100644 --- a/ipykernel/tests/test_pickleutil.py +++ b/ipykernel/tests/test_pickleutil.py @@ -17,7 +17,7 @@ def test_no_closure(): def foo(): a = 5 return a - + pfoo = dumps(foo) bar = loads(pfoo) assert foo() == bar() @@ -29,7 +29,7 @@ def foo(): i = 'i' r = [ i for j in (1,2) ] return r - + pfoo = dumps(foo) bar = loads(pfoo) assert foo() == bar() @@ -41,7 +41,7 @@ def foo(): def g(): return i return g() - + pfoo = dumps(foo) bar = loads(pfoo) assert foo() == bar() @@ -51,7 +51,7 @@ def test_closure(): @interactive def foo(): return i - + pfoo = dumps(foo) bar = loads(pfoo) assert foo() == bar() diff --git a/setup.cfg b/setup.cfg index 065b8a89c..2ee9451f5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,33 +7,19 @@ license_file = COPYING.md version = attr: ipykernel._version.__version__ [flake8] -# References: -# https://flake8.readthedocs.io/en/latest/user/configuration.html -# https://flake8.readthedocs.io/en/latest/user/error-codes.html -# https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes -exclude = __init__.py,versioneer.py -ignore = - E20, # Extra space in brackets - E122, # continuation line missing indentation or outdented - E124, # closing bracket does not match visual indentation - E128,E127,E126 # continuation line over/under-indented for visual indent - E121,E125, # continuation line with same indent as next logical line - E226, # missing whitespace around arithmetic operator - E231,E241, # Multiple spaces around "," - E211, # whitespace before '(' - E221,E225,E228 # missing whitespace around operator - E271, # multiple spaces after keyword - E301,E303,E305,E306 # expected X blank lines - E26, # Comments - E251 # unexpected spaces around keyword / parameter equals - E302 # expected 2 blank lines, found 1 - E4, # Import formatting - E721, # Comparing types instead of isinstance - E731, # Assigning lambda expression - E741, # Ambiguous variable names - W293, # blank line contains whitespace - W503, # line break before binary operator - W504, # line break after binary operator - F811, # redefinition of unused 'loop' from line 10 -max-line-length = 120 - +ignore = E501, W503, E402 +builtins = c, get_config +exclude = + .cache, + .github, + docs, + setup.py +enable-extensions = G +extend-ignore = + G001, G002, G004, G200, G201, G202, + # black adds spaces around ':' + E203, +per-file-ignores = + # B011: Do not call assert False since python -O removes these calls + # F841 local variable 'foo' is assigned to but never used + ipykernel/tests/*: B011, F841 From ee6f479b797c584966374997f9a8a3988d9ba20c Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 27 Mar 2022 05:59:41 -0500 Subject: [PATCH 0770/1195] add pytest-timeout --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index deac49d71..1106d77be 100644 --- a/setup.py +++ b/setup.py @@ -77,7 +77,8 @@ def run(self): "pytest-cov", "flaky", "ipyparallel", - "pre-commit" + "pre-commit", + "pytest-timeout" ], }, classifiers=[ From d8b90bdb2b4d02569b2c9f77223f367469fcb31f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 27 Mar 2022 06:04:11 -0500 Subject: [PATCH 0771/1195] fix pytest path --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6dcaf2645..9cc4eabfa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,7 @@ src = "ipykernel/_version.py" [tool.pytest.ini_options] addopts = "-raXs --durations 10 --color=yes --doctest-modules" testpaths = [ - "tests/" + "ipykernel/tests/" ] timeout = 300 # Restore this setting to debug failures From d0cb08db1f964c3677f9e3518b87e46b4bf7a9eb Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 27 Mar 2022 06:14:27 -0500 Subject: [PATCH 0772/1195] update manifest --- MANIFEST.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 155a5e4c4..e75739460 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -23,5 +23,6 @@ global-exclude .ipynb_checkpoints prune data_kernelspec exclude .mailmap exclude readthedocs.yml - exclude .coveragerc +exclude .pre-commit-config.yaml +exclude .git-blame-ignore-revs From 77e3c80e2fb5fb3a723a9cf9a0b44b736b0f8e41 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 13:46:45 +0000 Subject: [PATCH 0773/1195] Automated Changelog Entry for 6.10.0 on main --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90c46b372..62f8e35cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ +## 6.10.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.9.2...3059fd97b7ccbd72e778f123bfb0ad92e7d9e9c8)) + +### Enhancements made + +- Improve performance of stderr and stdout stream buffer [#888](https://github.com/ipython/ipykernel/pull/888) ([@MrBago](https://github.com/MrBago)) + +### Bugs fixed + +- Check if the current thread is the io thread [#884](https://github.com/ipython/ipykernel/pull/884) ([@jamadeo](https://github.com/jamadeo)) + +### Maintenance and upkeep improvements + +- More CI cleanup [#887](https://github.com/ipython/ipykernel/pull/887) ([@blink1073](https://github.com/blink1073)) +- CI cleanup [#885](https://github.com/ipython/ipykernel/pull/885) ([@blink1073](https://github.com/blink1073)) + +### Documentation improvements + +- Add precision about subprocess stdout/stderr capturing [#883](https://github.com/ipython/ipykernel/pull/883) ([@lesteve](https://github.com/lesteve)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-03-14&to=2022-03-28&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-03-14..2022-03-28&type=Issues) | [@jamadeo](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajamadeo+updated%3A2022-03-14..2022-03-28&type=Issues) | [@lesteve](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Alesteve+updated%3A2022-03-14..2022-03-28&type=Issues) | [@MrBago](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AMrBago+updated%3A2022-03-14..2022-03-28&type=Issues) | [@SylvainCorlay](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2022-03-14..2022-03-28&type=Issues) + + + ## 6.9.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.9.1...d6744f9e423dacc6b317b1d31805304e89cbec5d)) @@ -22,8 +51,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-02-15..2022-03-14&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2022-02-15..2022-03-14&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2022-02-15..2022-03-14&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-02-15..2022-03-14&type=Issues) | [@fabioz](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afabioz+updated%3A2022-02-15..2022-03-14&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2022-02-15..2022-03-14&type=Issues) | [@vidartf](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Avidartf+updated%3A2022-02-15..2022-03-14&type=Issues) - - ## 6.9.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.9.0...c27e5b95c3d104d9fb6cae3375aec0e98974dcff)) From ed5f5eb2bb69e3771acb18ee513b7dece2e077b2 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 28 Mar 2022 13:52:14 +0000 Subject: [PATCH 0774/1195] Publish 6.10.0 SHA256 hashes: ipykernel-6.10.0-py3-none-any.whl: 86ebe63f58cb68f299e06a2add4957df0eaebc7b0864de5711accd9c532d7810 ipykernel-6.10.0.tar.gz: d1c7d92daea5d9b55a33e523d4d17c09ad38e0df17a4e0ed2fa5c97f07f200ba --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 1c9db6510..0dabd3201 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.9.2' +__version__ = '6.10.0' # Build up version_info tuple for backwards compatibility pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' diff --git a/pyproject.toml b/pyproject.toml index ec9b08e49..37f5e7a69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.9.2" +current = "6.10.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From c5bca730f82bbdfb005ab93969ff5a1d028c2341 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 29 Mar 2022 05:29:53 -0500 Subject: [PATCH 0775/1195] autoformat with black --- .pre-commit-config.yaml | 28 +- docs/conf.py | 175 ++++---- examples/embedding/inprocess_qtconsole.py | 21 +- examples/embedding/inprocess_terminal.py | 17 +- examples/embedding/internal_ipkernel.py | 34 +- examples/embedding/ipkernel_qtapp.py | 39 +- examples/embedding/ipkernel_wxapp.py | 22 +- ipykernel/__init__.py | 7 +- ipykernel/__main__.py | 3 +- ipykernel/_eventloop_macos.py | 71 +-- ipykernel/_version.py | 12 +- ipykernel/comm/__init__.py | 2 +- ipykernel/comm/comm.py | 74 ++-- ipykernel/comm/manager.py | 40 +- ipykernel/compiler.py | 48 ++- ipykernel/connect.py | 29 +- ipykernel/control.py | 3 +- ipykernel/datapub.py | 27 +- ipykernel/debugger.py | 382 ++++++++--------- ipykernel/displayhook.py | 44 +- ipykernel/embed.py | 9 +- ipykernel/eventloops.py | 76 ++-- ipykernel/gui/__init__.py | 4 +- ipykernel/gui/gtk3embed.py | 30 +- ipykernel/gui/gtkembed.py | 25 +- ipykernel/heartbeat.py | 25 +- ipykernel/inprocess/__init__.py | 8 +- ipykernel/inprocess/blocking.py | 36 +- ipykernel/inprocess/channels.py | 16 +- ipykernel/inprocess/client.py | 110 ++--- ipykernel/inprocess/constants.py | 2 +- ipykernel/inprocess/ipkernel.py | 96 ++--- ipykernel/inprocess/manager.py | 25 +- ipykernel/inprocess/socket.py | 15 +- ipykernel/inprocess/tests/test_kernel.py | 51 +-- .../inprocess/tests/test_kernelmanager.py | 60 ++- ipykernel/iostream.py | 95 ++-- ipykernel/ipkernel.py | 400 +++++++++-------- ipykernel/jsonutil.py | 46 +- ipykernel/kernelapp.py | 294 +++++++------ ipykernel/kernelbase.py | 404 ++++++++++-------- ipykernel/kernelspec.py | 114 +++-- ipykernel/log.py | 18 +- ipykernel/parentpoller.py | 35 +- ipykernel/pickleutil.py | 121 +++--- ipykernel/pylab/backend_inline.py | 3 +- ipykernel/pylab/config.py | 6 +- ipykernel/serialize.py | 65 ++- ipykernel/tests/__init__.py | 17 +- ipykernel/tests/test_async.py | 13 +- ipykernel/tests/test_connect.py | 46 +- ipykernel/tests/test_debugger.py | 29 +- ipykernel/tests/test_embed_kernel.py | 128 +++--- ipykernel/tests/test_eventloop.py | 10 +- ipykernel/tests/test_heartbeat.py | 10 +- ipykernel/tests/test_io.py | 12 +- ipykernel/tests/test_jsonutil.py | 80 ++-- ipykernel/tests/test_kernel.py | 243 +++++------ ipykernel/tests/test_kernelspec.py | 67 ++- ipykernel/tests/test_message_spec.py | 320 +++++++------- ipykernel/tests/test_pickleutil.py | 23 +- ipykernel/tests/test_start_kernel.py | 30 +- ipykernel/tests/test_zmq_shell.py | 28 +- ipykernel/tests/utils.py | 82 ++-- ipykernel/trio_runner.py | 19 +- ipykernel/zmqshell.py | 173 ++++---- ipykernel_launcher.py | 5 +- setup.py | 91 ++-- 68 files changed, 2502 insertions(+), 2191 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 25b0bcb3c..6f1922568 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,21 +16,21 @@ repos: - id: check-builtin-literals - id: trailing-whitespace - # - repo: https://github.com/psf/black - # rev: 22.1.0 - # hooks: - # - id: black - # args: ["--line-length", "100"] + - repo: https://github.com/psf/black + rev: 22.3.0 + hooks: + - id: black + args: ["--line-length", "100"] - # - repo: https://github.com/PyCQA/isort - # rev: 5.10.1 - # hooks: - # - id: isort - # files: \.py$ - # args: [--profile=black] + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + files: \.py$ + args: [--profile=black] - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.5.1 + rev: v2.6.1 hooks: - id: prettier @@ -46,12 +46,12 @@ repos: # ] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.8.0 + rev: v8.12.0 hooks: - id: eslint - repo: https://github.com/sirosen/check-jsonschema - rev: 0.10.2 + rev: 0.14.1 hooks: - id: check-jsonschema name: "Check GitHub Workflows" diff --git a/docs/conf.py b/docs/conf.py index 086fd7e83..e331f558a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,43 +18,43 @@ # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) +# sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -#needs_sphinx = '1.0' +# needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'myst_parser', - 'sphinx.ext.autodoc', - 'sphinx.ext.intersphinx', - 'sphinxcontrib_github_alt', + "myst_parser", + "sphinx.ext.autodoc", + "sphinx.ext.intersphinx", + "sphinxcontrib_github_alt", ] github_project_url = "https://github.com/ipython/ipykernel" # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The encoding of source files. -#source_encoding = 'utf-8-sig' +# source_encoding = 'utf-8-sig' # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'IPython Kernel' -copyright = '2015, IPython Development Team' -author = 'IPython Development Team' +project = "IPython Kernel" +copyright = "2015, IPython Development Team" +author = "IPython Development Team" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -63,14 +63,14 @@ version_ns = {} here = os.path.dirname(__file__) -version_py = os.path.join(here, os.pardir, 'ipykernel', '_version.py') +version_py = os.path.join(here, os.pardir, "ipykernel", "_version.py") with open(version_py) as f: - exec(compile(f.read(), version_py, 'exec'), version_ns) + exec(compile(f.read(), version_py, "exec"), version_ns) # The short X.Y version. -version = '%i.%i' % version_ns['version_info'][:2] +version = "%i.%i" % version_ns["version_info"][:2] # The full version, including alpha/beta/rc tags. -release = version_ns['__version__'] +release = version_ns["__version__"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -81,37 +81,37 @@ # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: -#today = '' +# today = '' # Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' +# today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. -exclude_patterns = ['_build'] +exclude_patterns = ["_build"] # The reST default role (used for this markup: `text`) to use for all # documents. -default_role = 'literal' +default_role = "literal" # If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True +# add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). -#add_module_names = True +# add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. -#show_authors = False +# show_authors = False # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] +# modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. -#keep_warnings = False +# keep_warnings = False # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False @@ -126,26 +126,26 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -#html_theme_options = {} +# html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] +# html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -#html_title = None +# html_title = None # A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None +# html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. -#html_logo = None +# html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. -#html_favicon = None +# html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, @@ -155,122 +155,121 @@ # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. -#html_extra_path = [] +# html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' +# html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -#html_use_smartypants = True +# html_use_smartypants = True # Custom sidebar templates, maps document names to template names. -#html_sidebars = {} +# html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. -#html_additional_pages = {} +# html_additional_pages = {} # If false, no module index is generated. -#html_domain_indices = True +# html_domain_indices = True # If false, no index is generated. -#html_use_index = True +# html_use_index = True # If true, the index is split into individual pages for each letter. -#html_split_index = False +# html_split_index = False # If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True +# html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True +# html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True +# html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. -#html_use_opensearch = '' +# html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None +# html_file_suffix = None # Language to be used for generating the HTML full-text search index. # Sphinx supports the following languages: # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' -#html_search_language = 'en' +# html_search_language = 'en' # A dictionary with options for the search language support, empty by default. # Now only 'ja' uses this config value -#html_search_options = {'type': 'default'} +# html_search_options = {'type': 'default'} # The name of a javascript file (relative to the configuration directory) that # implements a search results scorer. If empty, the default will be used. -#html_search_scorer = 'scorer.js' +# html_search_scorer = 'scorer.js' # Output file base name for HTML help builder. -htmlhelp_basename = 'ipykerneldoc' +htmlhelp_basename = "ipykerneldoc" # -- Options for LaTeX output --------------------------------------------- latex_elements = { -# The paper size ('letterpaper' or 'a4paper'). -#'papersize': 'letterpaper', - -# The font size ('10pt', '11pt' or '12pt'). -#'pointsize': '10pt', - -# Additional stuff for the LaTeX preamble. -#'preamble': '', - -# Latex figure (float) alignment -#'figure_align': 'htbp', + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + #'preamble': '', + # Latex figure (float) alignment + #'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'ipykernel.tex', 'IPython Kernel Documentation', - 'IPython Development Team', 'manual'), + ( + master_doc, + "ipykernel.tex", + "IPython Kernel Documentation", + "IPython Development Team", + "manual", + ), ] # The name of an image file (relative to this directory) to place at the top of # the title page. -#latex_logo = None +# latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. -#latex_use_parts = False +# latex_use_parts = False # If true, show page references after internal links. -#latex_show_pagerefs = False +# latex_show_pagerefs = False # If true, show URL addresses after external links. -#latex_show_urls = False +# latex_show_urls = False # Documents to append as an appendix to all manuals. -#latex_appendices = [] +# latex_appendices = [] # If false, no module index is generated. -#latex_domain_indices = True +# latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'ipykernel', 'IPython Kernel Documentation', - [author], 1) -] +man_pages = [(master_doc, "ipykernel", "IPython Kernel Documentation", [author], 1)] # If true, show URL addresses after external links. -#man_show_urls = False +# man_show_urls = False # -- Options for Texinfo output ------------------------------------------- @@ -279,32 +278,38 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'ipykernel', 'IPython Kernel Documentation', - author, 'ipykernel', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "ipykernel", + "IPython Kernel Documentation", + author, + "ipykernel", + "One line description of project.", + "Miscellaneous", + ), ] # Documents to append as an appendix to all manuals. -#texinfo_appendices = [] +# texinfo_appendices = [] # If false, no module index is generated. -#texinfo_domain_indices = True +# texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. -#texinfo_show_urls = 'footnote' +# texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. -#texinfo_no_detailmenu = False +# texinfo_no_detailmenu = False # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = { - 'python': ('https://docs.python.org/3/', None), - 'ipython': ('https://ipython.readthedocs.io/en/latest', None), - 'jupyter': ('https://jupyter.readthedocs.io/en/latest', None), + "python": ("https://docs.python.org/3/", None), + "ipython": ("https://ipython.readthedocs.io/en/latest", None), + "jupyter": ("https://jupyter.readthedocs.io/en/latest", None), } def setup(app): here = os.path.dirname(os.path.abspath(__file__)) - shutil.copy(os.path.join(here, '..', 'CHANGELOG.md'), 'changelog.md') + shutil.copy(os.path.join(here, "..", "CHANGELOG.md"), "changelog.md") diff --git a/examples/embedding/inprocess_qtconsole.py b/examples/embedding/inprocess_qtconsole.py index 5d9998bff..55e514343 100644 --- a/examples/embedding/inprocess_qtconsole.py +++ b/examples/embedding/inprocess_qtconsole.py @@ -2,14 +2,13 @@ import sys import tornado - -from qtconsole.rich_ipython_widget import RichIPythonWidget -from qtconsole.inprocess import QtInProcessKernelManager from IPython.lib import guisupport +from qtconsole.inprocess import QtInProcessKernelManager +from qtconsole.rich_ipython_widget import RichIPythonWidget def print_process_id(): - print('Process ID is:', os.getpid()) + print("Process ID is:", os.getpid()) def init_asyncio_patch(): @@ -23,8 +22,13 @@ def init_asyncio_patch(): FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8) and tornado.version_info < (6, 1): + if ( + sys.platform.startswith("win") + and sys.version_info >= (3, 8) + and tornado.version_info < (6, 1) + ): import asyncio + try: from asyncio import ( WindowsProactorEventLoopPolicy, @@ -39,6 +43,7 @@ def init_asyncio_patch(): # fallback to the pre-3.8 default of Selector asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) + def main(): # Print the ID of the main process print_process_id() @@ -52,8 +57,8 @@ def main(): kernel_manager = QtInProcessKernelManager() kernel_manager.start_kernel() kernel = kernel_manager.kernel - kernel.gui = 'qt4' - kernel.shell.push({'foo': 43, 'print_process_id': print_process_id}) + kernel.gui = "qt4" + kernel.shell.push({"foo": 43, "print_process_id": print_process_id}) kernel_client = kernel_manager.client() kernel_client.start_channels() @@ -72,5 +77,5 @@ def stop(): guisupport.start_event_loop_qt4(app) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index 5ccab1654..543adba64 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -2,13 +2,13 @@ import sys import tornado +from jupyter_console.ptshell import ZMQTerminalInteractiveShell from ipykernel.inprocess import InProcessKernelManager -from jupyter_console.ptshell import ZMQTerminalInteractiveShell def print_process_id(): - print('Process ID is:', os.getpid()) + print("Process ID is:", os.getpid()) def init_asyncio_patch(): @@ -22,8 +22,13 @@ def init_asyncio_patch(): FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8) and tornado.version_info < (6, 1): + if ( + sys.platform.startswith("win") + and sys.version_info >= (3, 8) + and tornado.version_info < (6, 1) + ): import asyncio + try: from asyncio import ( WindowsProactorEventLoopPolicy, @@ -49,8 +54,8 @@ def main(): kernel_manager = InProcessKernelManager() kernel_manager.start_kernel() kernel = kernel_manager.kernel - kernel.gui = 'qt4' - kernel.shell.push({'foo': 43, 'print_process_id': print_process_id}) + kernel.gui = "qt4" + kernel.shell.push({"foo": 43, "print_process_id": print_process_id}) client = kernel_manager.client() client.start_channels() @@ -58,5 +63,5 @@ def main(): shell.mainloop() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/examples/embedding/internal_ipkernel.py b/examples/embedding/internal_ipkernel.py index a0e89f5cc..9538aacc9 100644 --- a/examples/embedding/internal_ipkernel.py +++ b/examples/embedding/internal_ipkernel.py @@ -1,27 +1,31 @@ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Imports -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- import sys from IPython.lib.kernel import connect_qtconsole + from ipykernel.kernelapp import IPKernelApp -#----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- # Functions and classes -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- def mpl_kernel(gui): - """Launch and return an IPython kernel with matplotlib support for the desired gui - """ + """Launch and return an IPython kernel with matplotlib support for the desired gui""" kernel = IPKernelApp.instance() - kernel.initialize(['python', '--matplotlib=%s' % gui, - #'--log-level=10' - ]) + kernel.initialize( + [ + "python", + "--matplotlib=%s" % gui, + #'--log-level=10' + ] + ) return kernel class InternalIPKernel: - def init_ipkernel(self, backend): # Start IPython kernel with GUI event loop and mpl support self.ipkernel = mpl_kernel(backend) @@ -33,14 +37,14 @@ def init_ipkernel(self, backend): # Example: a variable that will be seen by the user in the shell, and # that the GUI modifies (the 'Counter++' button increments it): - self.namespace['app_counter'] = 0 - #self.namespace['ipkernel'] = self.ipkernel # dbg + self.namespace["app_counter"] = 0 + # self.namespace['ipkernel'] = self.ipkernel # dbg def print_namespace(self, evt=None): print("\n***Variables in User namespace***") for k, v in self.namespace.items(): - if not k.startswith('_'): - print('%s -> %r' % (k, v)) + if not k.startswith("_"): + print("%s -> %r" % (k, v)) sys.stdout.flush() def new_qt_console(self, evt=None): @@ -48,7 +52,7 @@ def new_qt_console(self, evt=None): return connect_qtconsole(self.ipkernel.abs_connection_file, profile=self.ipkernel.profile) def count(self, evt=None): - self.namespace['app_counter'] += 1 + self.namespace["app_counter"] += 1 def cleanup_consoles(self, evt=None): for c in self.consoles: diff --git a/examples/embedding/ipkernel_qtapp.py b/examples/embedding/ipkernel_qtapp.py index 4f9789580..7d6e61cf0 100755 --- a/examples/embedding/ipkernel_qtapp.py +++ b/examples/embedding/ipkernel_qtapp.py @@ -14,55 +14,54 @@ Consoles attached separately from a terminal will not be terminated, though they will notice that their kernel died. """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Imports -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- +from internal_ipkernel import InternalIPKernel from PyQt4 import Qt -from internal_ipkernel import InternalIPKernel -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Functions and classes -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- class SimpleWindow(Qt.QWidget, InternalIPKernel): - def __init__(self, app): Qt.QWidget.__init__(self) self.app = app self.add_widgets() - self.init_ipkernel('qt') + self.init_ipkernel("qt") def add_widgets(self): self.setGeometry(300, 300, 400, 70) - self.setWindowTitle('IPython in your app') + self.setWindowTitle("IPython in your app") # Add simple buttons: - console = Qt.QPushButton('Qt Console', self) + console = Qt.QPushButton("Qt Console", self) console.setGeometry(10, 10, 100, 35) - self.connect(console, Qt.SIGNAL('clicked()'), self.new_qt_console) + self.connect(console, Qt.SIGNAL("clicked()"), self.new_qt_console) - namespace = Qt.QPushButton('Namespace', self) + namespace = Qt.QPushButton("Namespace", self) namespace.setGeometry(120, 10, 100, 35) - self.connect(namespace, Qt.SIGNAL('clicked()'), self.print_namespace) + self.connect(namespace, Qt.SIGNAL("clicked()"), self.print_namespace) - count = Qt.QPushButton('Count++', self) + count = Qt.QPushButton("Count++", self) count.setGeometry(230, 10, 80, 35) - self.connect(count, Qt.SIGNAL('clicked()'), self.count) + self.connect(count, Qt.SIGNAL("clicked()"), self.count) # Quit and cleanup - quit = Qt.QPushButton('Quit', self) + quit = Qt.QPushButton("Quit", self) quit.setGeometry(320, 10, 60, 35) - self.connect(quit, Qt.SIGNAL('clicked()'), Qt.qApp, Qt.SLOT('quit()')) + self.connect(quit, Qt.SIGNAL("clicked()"), Qt.qApp, Qt.SLOT("quit()")) - self.app.connect(self.app, Qt.SIGNAL("lastWindowClosed()"), - self.app, Qt.SLOT("quit()")) + self.app.connect(self.app, Qt.SIGNAL("lastWindowClosed()"), self.app, Qt.SLOT("quit()")) self.app.aboutToQuit.connect(self.cleanup_consoles) -#----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- # Main script -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- if __name__ == "__main__": app = Qt.QApplication([]) diff --git a/examples/embedding/ipkernel_wxapp.py b/examples/embedding/ipkernel_wxapp.py index ffb82c864..2caad5be2 100755 --- a/examples/embedding/ipkernel_wxapp.py +++ b/examples/embedding/ipkernel_wxapp.py @@ -16,18 +16,18 @@ Ref: Modified from wxPython source code wxPython/samples/simple/simple.py """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Imports -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- import sys import wx - from internal_ipkernel import InternalIPKernel -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Functions and classes -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + class MyFrame(wx.Frame, InternalIPKernel): """ @@ -36,8 +36,7 @@ class MyFrame(wx.Frame, InternalIPKernel): """ def __init__(self, parent, title): - wx.Frame.__init__(self, parent, -1, title, - pos=(150, 150), size=(350, 285)) + wx.Frame.__init__(self, parent, -1, title, pos=(150, 150), size=(350, 285)) # Create the menubar menuBar = wx.MenuBar() @@ -86,7 +85,7 @@ def __init__(self, parent, title): panel.Layout() # Start the IPython kernel with gui support - self.init_ipkernel('wx') + self.init_ipkernel("wx") def OnTimeToClose(self, evt): """Event handler for the button click.""" @@ -107,11 +106,12 @@ def OnInit(self): self.ipkernel = frame.ipkernel return True -#----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- # Main script -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- -if __name__ == '__main__': +if __name__ == "__main__": app = MyApp(redirect=False, clearSigInt=False) # Very important, IPython-specific step: this gets GUI event loop diff --git a/ipykernel/__init__.py b/ipykernel/__init__.py index 3f753e3ee..bc5fbb726 100644 --- a/ipykernel/__init__.py +++ b/ipykernel/__init__.py @@ -1,2 +1,7 @@ -from ._version import version_info, __version__, kernel_protocol_version_info, kernel_protocol_version +from ._version import ( + __version__, + kernel_protocol_version, + kernel_protocol_version_info, + version_info, +) from .connect import * diff --git a/ipykernel/__main__.py b/ipykernel/__main__.py index d1f0cf533..f66b36c8e 100644 --- a/ipykernel/__main__.py +++ b/ipykernel/__main__.py @@ -1,3 +1,4 @@ -if __name__ == '__main__': +if __name__ == "__main__": from ipykernel import kernelapp as app + app.launch_new_instance() diff --git a/ipykernel/_eventloop_macos.py b/ipykernel/_eventloop_macos.py index 8c7cbdd91..94762b65d 100644 --- a/ipykernel/_eventloop_macos.py +++ b/ipykernel/_eventloop_macos.py @@ -10,7 +10,7 @@ import ctypes.util from threading import Event -objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library('objc')) +objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) void_p = ctypes.c_void_p @@ -25,7 +25,7 @@ def _utf8(s): """ensure utf8 bytes""" if not isinstance(s, bytes): - s = s.encode('utf8') + s = s.encode("utf8") return s @@ -42,7 +42,7 @@ def C(classname): # end obj-c boilerplate from appnope # CoreFoundation C-API calls we will use: -CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation')) +CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library("CoreFoundation")) CFAbsoluteTimeGetCurrent = CoreFoundation.CFAbsoluteTimeGetCurrent CFAbsoluteTimeGetCurrent.restype = ctypes.c_double @@ -60,43 +60,46 @@ def C(classname): CFRunLoopTimerCreate = CoreFoundation.CFRunLoopTimerCreate CFRunLoopTimerCreate.restype = void_p CFRunLoopTimerCreate.argtypes = [ - void_p, # allocator (NULL) - ctypes.c_double, # fireDate - ctypes.c_double, # interval - ctypes.c_int, # flags (0) - ctypes.c_int, # order (0) - void_p, # callout - void_p, # context + void_p, # allocator (NULL) + ctypes.c_double, # fireDate + ctypes.c_double, # interval + ctypes.c_int, # flags (0) + ctypes.c_int, # order (0) + void_p, # callout + void_p, # context ] CFRunLoopAddTimer = CoreFoundation.CFRunLoopAddTimer CFRunLoopAddTimer.restype = None -CFRunLoopAddTimer.argtypes = [ void_p, void_p, void_p ] +CFRunLoopAddTimer.argtypes = [void_p, void_p, void_p] -kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, 'kCFRunLoopCommonModes') +kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, "kCFRunLoopCommonModes") def _NSApp(): """Return the global NSApplication instance (NSApp)""" - return msg(C('NSApplication'), n('sharedApplication')) + return msg(C("NSApplication"), n("sharedApplication")) def _wake(NSApp): """Wake the Application""" - event = msg(C('NSEvent'), - n('otherEventWithType:location:modifierFlags:' - 'timestamp:windowNumber:context:subtype:data1:data2:'), - 15, # Type - 0, # location - 0, # flags - 0, # timestamp - 0, # window - None, # context - 0, # subtype - 0, # data1 - 0, # data2 + event = msg( + C("NSEvent"), + n( + "otherEventWithType:location:modifierFlags:" + "timestamp:windowNumber:context:subtype:data1:data2:" + ), + 15, # Type + 0, # location + 0, # flags + 0, # timestamp + 0, # window + None, # context + 0, # subtype + 0, # data1 + 0, # data2 ) - msg(NSApp, n('postEvent:atStart:'), void_p(event), True) + msg(NSApp, n("postEvent:atStart:"), void_p(event), True) _triggered = Event() @@ -108,8 +111,8 @@ def stop(timer=None, loop=None): NSApp = _NSApp() # if NSApp is not running, stop CFRunLoop directly, # otherwise stop and wake NSApp - if msg(NSApp, n('isRunning')): - msg(NSApp, n('stop:'), NSApp) + if msg(NSApp, n("isRunning")): + msg(NSApp, n("stop:"), NSApp) _wake(NSApp) else: CFRunLoopStop(CFRunLoopGetCurrent()) @@ -122,11 +125,11 @@ def stop(timer=None, loop=None): def _stop_after(delay): """Register callback to stop eventloop after a delay""" timer = CFRunLoopTimerCreate( - None, # allocator - CFAbsoluteTimeGetCurrent() + delay, # fireDate - 0, # interval - 0, # flags - 0, # order + None, # allocator + CFAbsoluteTimeGetCurrent() + delay, # fireDate + 0, # interval + 0, # flags + 0, # order _c_stop_callback, None, ) @@ -143,7 +146,7 @@ def mainloop(duration=1): _triggered.clear() NSApp = _NSApp() _stop_after(duration) - msg(NSApp, n('run')) + msg(NSApp, n("run")) if not _triggered.is_set(): # app closed without firing callback, # probably due to last window being closed. diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 0dabd3201..85e2e8aa1 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,15 +4,15 @@ import re # Version string must appear intact for tbump versioning -__version__ = '6.10.0' +__version__ = "6.10.0" # Build up version_info tuple for backwards compatibility -pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' +pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" match = re.match(pattern, __version__) -parts = [int(match[part]) for part in ['major', 'minor', 'patch']] -if match['rest']: - parts.append(match['rest']) +parts = [int(match[part]) for part in ["major", "minor", "patch"]] +if match["rest"]: + parts.append(match["rest"]) version_info = tuple(parts) kernel_protocol_version_info = (5, 3) -kernel_protocol_version = '%s.%s' % kernel_protocol_version_info +kernel_protocol_version = "%s.%s" % kernel_protocol_version_info diff --git a/ipykernel/comm/__init__.py b/ipykernel/comm/__init__.py index 1faa164c0..cf9c1e983 100644 --- a/ipykernel/comm/__init__.py +++ b/ipykernel/comm/__init__.py @@ -1,2 +1,2 @@ -from .manager import * from .comm import * +from .manager import * diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 97dff2bec..266dc048b 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -5,39 +5,44 @@ import uuid +from traitlets import Any, Bool, Bytes, Dict, Instance, Unicode, default from traitlets.config import LoggingConfigurable -from ipykernel.kernelbase import Kernel from ipykernel.jsonutil import json_clean -from traitlets import Instance, Unicode, Bytes, Bool, Dict, Any, default +from ipykernel.kernelbase import Kernel class Comm(LoggingConfigurable): """Class for communicating between a Frontend and a Kernel""" - kernel = Instance('ipykernel.kernelbase.Kernel', allow_none=True) - @default('kernel') + kernel = Instance("ipykernel.kernelbase.Kernel", allow_none=True) + + @default("kernel") def _default_kernel(self): if Kernel.initialized(): return Kernel.instance() comm_id = Unicode() - @default('comm_id') + @default("comm_id") def _default_comm_id(self): return uuid.uuid4().hex primary = Bool(True, help="Am I the primary or secondary Comm?") - target_name = Unicode('comm') - target_module = Unicode(None, allow_none=True, help="""requirejs module from - which to load comm target.""") + target_name = Unicode("comm") + target_module = Unicode( + None, + allow_none=True, + help="""requirejs module from + which to load comm target.""", + ) topic = Bytes() - @default('topic') + @default("topic") def _default_topic(self): - return ('comm-%s' % self.comm_id).encode('ascii') + return ("comm-%s" % self.comm_id).encode("ascii") _open_data = Dict(help="data dict, if any, to be included in comm_open") _close_data = Dict(help="data dict, if any, to be included in comm_close") @@ -47,9 +52,9 @@ def _default_topic(self): _closed = Bool(True) - def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwargs): + def __init__(self, target_name="", data=None, metadata=None, buffers=None, **kwargs): if target_name: - kwargs['target_name'] = target_name + kwargs["target_name"] = target_name super().__init__(**kwargs) if self.kernel: if self.primary: @@ -63,7 +68,9 @@ def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys) data = {} if data is None else data metadata = {} if metadata is None else metadata content = json_clean(dict(data=data, comm_id=self.comm_id, **keys)) - self.kernel.session.send(self.kernel.iopub_socket, msg_type, + self.kernel.session.send( + self.kernel.iopub_socket, + msg_type, content, metadata=json_clean(metadata), parent=self.kernel.get_parent("shell"), @@ -81,18 +88,23 @@ def open(self, data=None, metadata=None, buffers=None): """Open the frontend-side version of this comm""" if data is None: data = self._open_data - comm_manager = getattr(self.kernel, 'comm_manager', None) + comm_manager = getattr(self.kernel, "comm_manager", None) if comm_manager is None: - raise RuntimeError("Comms cannot be opened without a kernel " - "and a comm_manager attached to that kernel.") + raise RuntimeError( + "Comms cannot be opened without a kernel " + "and a comm_manager attached to that kernel." + ) comm_manager.register_comm(self) try: - self._publish_msg('comm_open', - data=data, metadata=metadata, buffers=buffers, - target_name=self.target_name, - target_module=self.target_module, - ) + self._publish_msg( + "comm_open", + data=data, + metadata=metadata, + buffers=buffers, + target_name=self.target_name, + target_module=self.target_module, + ) self._closed = False except Exception: comm_manager.unregister_comm(self) @@ -110,8 +122,11 @@ def close(self, data=None, metadata=None, buffers=None, deleting=False): return if data is None: data = self._close_data - self._publish_msg('comm_close', - data=data, metadata=metadata, buffers=buffers, + self._publish_msg( + "comm_close", + data=data, + metadata=metadata, + buffers=buffers, ) if not deleting: # If deleting, the comm can't be registered @@ -119,8 +134,11 @@ def close(self, data=None, metadata=None, buffers=None, deleting=False): def send(self, data=None, metadata=None, buffers=None): """Send a message to the frontend-side version of this comm""" - self._publish_msg('comm_msg', - data=data, metadata=metadata, buffers=buffers, + self._publish_msg( + "comm_msg", + data=data, + metadata=metadata, + buffers=buffers, ) # registering callbacks @@ -157,10 +175,10 @@ def handle_msg(self, msg): if self._msg_callback: shell = self.kernel.shell if shell: - shell.events.trigger('pre_execute') + shell.events.trigger("pre_execute") self._msg_callback(msg) if shell: - shell.events.trigger('post_execute') + shell.events.trigger("post_execute") -__all__ = ['Comm'] +__all__ = ["Comm"] diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 631b67b13..8dae16235 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -5,10 +5,9 @@ import logging +from traitlets import Dict, Instance from traitlets.config import LoggingConfigurable - from traitlets.utils.importstring import import_item -from traitlets import Instance, Dict from .comm import Comm @@ -16,7 +15,7 @@ class CommManager(LoggingConfigurable): """Manager for Comms in the Kernel""" - kernel = Instance('ipykernel.kernelbase.Kernel') + kernel = Instance("ipykernel.kernelbase.Kernel") comms = Dict() targets = Dict() @@ -72,13 +71,14 @@ def get_comm(self, comm_id): # Message handlers def comm_open(self, stream, ident, msg): """Handler for comm_open messages""" - content = msg['content'] - comm_id = content['comm_id'] - target_name = content['target_name'] + content = msg["content"] + comm_id = content["comm_id"] + target_name = content["target_name"] f = self.targets.get(target_name, None) - comm = Comm(comm_id=comm_id, - primary=False, - target_name=target_name, + comm = Comm( + comm_id=comm_id, + primary=False, + target_name=target_name, ) self.register_comm(comm) if f is None: @@ -94,13 +94,16 @@ def comm_open(self, stream, ident, msg): try: comm.close() except Exception: - self.log.error("""Could not close comm during `comm_open` failure - clean-up. The comm may not have been opened yet.""", exc_info=True) + self.log.error( + """Could not close comm during `comm_open` failure + clean-up. The comm may not have been opened yet.""", + exc_info=True, + ) def comm_msg(self, stream, ident, msg): """Handler for comm_msg messages""" - content = msg['content'] - comm_id = content['comm_id'] + content = msg["content"] + comm_id = content["comm_id"] comm = self.get_comm(comm_id) if comm is None: return @@ -108,12 +111,12 @@ def comm_msg(self, stream, ident, msg): try: comm.handle_msg(msg) except Exception: - self.log.error('Exception in comm_msg for %s', comm_id, exc_info=True) + self.log.error("Exception in comm_msg for %s", comm_id, exc_info=True) def comm_close(self, stream, ident, msg): """Handler for comm_close messages""" - content = msg['content'] - comm_id = content['comm_id'] + content = msg["content"] + comm_id = content["comm_id"] comm = self.get_comm(comm_id) if comm is None: return @@ -124,6 +127,7 @@ def comm_close(self, stream, ident, msg): try: comm.handle_close(msg) except Exception: - self.log.error('Exception in comm_close for %s', comm_id, exc_info=True) + self.log.error("Exception in comm_close for %s", comm_id, exc_info=True) + -__all__ = ['CommManager'] +__all__ = ["CommManager"] diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 95770e7be..585261593 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -1,48 +1,54 @@ -from IPython.core.compilerop import CachingCompiler -import tempfile import os import sys +import tempfile + +from IPython.core.compilerop import CachingCompiler def murmur2_x86(data, seed): - m = 0x5bd1e995 + m = 0x5BD1E995 data = [chr(d) for d in str.encode(data, "utf8")] length = len(data) h = seed ^ length - rounded_end = (length & 0xfffffffc) + rounded_end = length & 0xFFFFFFFC for i in range(0, rounded_end, 4): - k = (ord(data[i]) & 0xff) | ((ord(data[i + 1]) & 0xff) << 8) | \ - ((ord(data[i + 2]) & 0xff) << 16) | (ord(data[i + 3]) << 24) - k = (k * m) & 0xffffffff + k = ( + (ord(data[i]) & 0xFF) + | ((ord(data[i + 1]) & 0xFF) << 8) + | ((ord(data[i + 2]) & 0xFF) << 16) + | (ord(data[i + 3]) << 24) + ) + k = (k * m) & 0xFFFFFFFF k ^= k >> 24 - k = (k * m) & 0xffffffff + k = (k * m) & 0xFFFFFFFF - h = (h * m) & 0xffffffff + h = (h * m) & 0xFFFFFFFF h ^= k val = length & 0x03 k = 0 if val == 3: - k = (ord(data[rounded_end + 2]) & 0xff) << 16 + k = (ord(data[rounded_end + 2]) & 0xFF) << 16 if val in [2, 3]: - k |= (ord(data[rounded_end + 1]) & 0xff) << 8 + k |= (ord(data[rounded_end + 1]) & 0xFF) << 8 if val in [1, 2, 3]: - k |= ord(data[rounded_end]) & 0xff + k |= ord(data[rounded_end]) & 0xFF h ^= k - h = (h * m) & 0xffffffff + h = (h * m) & 0xFFFFFFFF h ^= h >> 13 - h = (h * m) & 0xffffffff + h = (h * m) & 0xFFFFFFFF h ^= h >> 15 return h -convert_to_long_pathname = lambda filename:filename -if sys.platform == 'win32': +convert_to_long_pathname = lambda filename: filename + +if sys.platform == "win32": try: import ctypes - from ctypes.wintypes import MAX_PATH, LPCWSTR, LPWSTR, DWORD + from ctypes.wintypes import DWORD, LPCWSTR, LPWSTR, MAX_PATH _GetLongPathName = ctypes.windll.kernel32.GetLongPathNameW _GetLongPathName.argtypes = [LPCWSTR, LPWSTR, DWORD] @@ -62,14 +68,15 @@ def _convert_to_long_pathname(filename): else: convert_to_long_pathname = _convert_to_long_pathname + def get_tmp_directory(): tmp_dir = convert_to_long_pathname(tempfile.gettempdir()) pid = os.getpid() - return tmp_dir + os.sep + 'ipykernel_' + str(pid) + return tmp_dir + os.sep + "ipykernel_" + str(pid) def get_tmp_hash_seed(): - hash_seed = 0xc70f6907 + hash_seed = 0xC70F6907 return hash_seed @@ -77,12 +84,11 @@ def get_file_name(code): cell_name = os.environ.get("IPYKERNEL_CELL_NAME") if cell_name is None: name = murmur2_x86(code, get_tmp_hash_seed()) - cell_name = get_tmp_directory() + os.sep + str(name) + '.py' + cell_name = get_tmp_directory() + os.sep + str(name) + ".py" return cell_name class XCachingCompiler(CachingCompiler): - def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.log = None diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 73ff22b3f..b56b9b8ff 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -5,13 +5,12 @@ import json import sys -from subprocess import Popen, PIPE +from subprocess import PIPE, Popen import jupyter_client from jupyter_client import write_connection_file - def get_connection_file(app=None): """Return the path to the connection file of an app @@ -21,13 +20,15 @@ def get_connection_file(app=None): If unspecified, the currently running app will be used """ from traitlets.utils import filefind + if app is None: from ipykernel.kernelapp import IPKernelApp + if not IPKernelApp.initialized(): raise RuntimeError("app not specified, and not in a running Kernel") app = IPKernelApp.instance() - return filefind(app.connection_file, ['.', app.connection_dir]) + return filefind(app.connection_file, [".", app.connection_dir]) def _find_connection_file(connection_file): @@ -102,25 +103,25 @@ def connect_qtconsole(connection_file=None, argv=None): cf = _find_connection_file(connection_file) - cmd = ';'.join([ - "from IPython.qt.console import qtconsoleapp", - "qtconsoleapp.main()" - ]) + cmd = ";".join(["from IPython.qt.console import qtconsoleapp", "qtconsoleapp.main()"]) kwargs = {} # Launch the Qt console in a separate session & process group, so # interrupting the kernel doesn't kill it. - kwargs['start_new_session'] = True + kwargs["start_new_session"] = True - return Popen([sys.executable, '-c', cmd, '--existing', cf] + argv, - stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != 'win32'), + return Popen( + [sys.executable, "-c", cmd, "--existing", cf] + argv, + stdout=PIPE, + stderr=PIPE, + close_fds=(sys.platform != "win32"), **kwargs ) __all__ = [ - 'write_connection_file', - 'get_connection_file', - 'get_connection_info', - 'connect_qtconsole', + "write_connection_file", + "get_connection_file", + "get_connection_info", + "connect_qtconsole", ] diff --git a/ipykernel/control.py b/ipykernel/control.py index cba8b0994..bb5f7b5f9 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -1,5 +1,7 @@ from threading import Thread + import zmq + if zmq.pyzmq_version_info() >= (17, 0): from tornado.ioloop import IOLoop else: @@ -8,7 +10,6 @@ class ControlThread(Thread): - def __init__(self, **kwargs): Thread.__init__(self, name="Control", **kwargs) self.io_loop = IOLoop(make_current=False) diff --git a/ipykernel/datapub.py b/ipykernel/datapub.py index fbc263d08..d8fe04093 100644 --- a/ipykernel/datapub.py +++ b/ipykernel/datapub.py @@ -2,29 +2,34 @@ """ import warnings -warnings.warn("ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", + +warnings.warn( + "ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from traitlets import Any, CBytes, Dict, Instance from traitlets.config import Configurable -from traitlets import Instance, Dict, CBytes, Any + from ipykernel.jsonutil import json_clean + try: # available since ipyparallel 5.0.0 from ipyparallel.serialize import serialize_object except ImportError: # Deprecated since ipykernel 4.3.0 from ipykernel.serialize import serialize_object + from jupyter_client.session import Session, extract_header class ZMQDataPublisher(Configurable): - topic = topic = CBytes(b'datapub') + topic = topic = CBytes(b"datapub") session = Instance(Session, allow_none=True) pub_socket = Any(allow_none=True) parent_header = Dict({}) @@ -42,12 +47,16 @@ def publish_data(self, data): The data to be published. Think of it as a namespace. """ session = self.session - buffers = serialize_object(data, + buffers = serialize_object( + data, buffer_threshold=session.buffer_threshold, item_threshold=session.item_threshold, ) content = json_clean(dict(keys=list(data.keys()))) - session.send(self.pub_socket, 'data_message', content=content, + session.send( + self.pub_socket, + "data_message", + content=content, parent=self.parent_header, buffers=buffers, ident=self.topic, @@ -62,10 +71,12 @@ def publish_data(data): data : dict The data to be published. Think of it as a namespace. """ - warnings.warn("ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", + warnings.warn( + "ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) from ipykernel.zmqshell import ZMQInteractiveShell + ZMQInteractiveShell.instance().data_pub.publish_data(data) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index f229c9402..21585b364 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,35 +1,37 @@ -import sys import os import re +import sys import threading import zmq -from zmq.utils import jsonapi - -from tornado.queues import Queue -from tornado.locks import Event - from IPython.core.getipython import get_ipython from IPython.core.inputtransformer2 import leading_empty_lines +from tornado.locks import Event +from tornado.queues import Queue +from zmq.utils import jsonapi try: from jupyter_client.jsonutil import json_default except ImportError: from jupyter_client.jsonutil import date_default as json_default -from .compiler import (get_file_name, get_tmp_directory, get_tmp_hash_seed) +from .compiler import get_file_name, get_tmp_directory, get_tmp_hash_seed try: # This import is required to have the next ones working... - from debugpy.server import api # noqa from _pydevd_bundle import pydevd_frame_utils - from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager, _FramesTracker + from _pydevd_bundle.pydevd_suspended_frames import ( + SuspendedFramesManager, + _FramesTracker, + ) + from debugpy.server import api # noqa + _is_debugpy_available = True except ImportError: _is_debugpy_available = False # Required for backwards compatiblity -ROUTING_ID = getattr(zmq, 'ROUTING_ID', None) or zmq.IDENTITY +ROUTING_ID = getattr(zmq, "ROUTING_ID", None) or zmq.IDENTITY class _FakeCode: @@ -49,6 +51,7 @@ def __init__(self, f_code, f_globals, f_locals): class _DummyPyDB: def __init__(self): from _pydevd_bundle.pydevd_api import PyDevdAPI + self.variable_presentation = PyDevdAPI.VariablePresentation() @@ -61,13 +64,13 @@ def __init__(self): def track(self): var = get_ipython().user_ns - self.frame = _FakeFrame(_FakeCode('', get_file_name('sys._getframe()')), var, var) - self.tracker.track('thread1', pydevd_frame_utils.create_frames_list_from_frame(self.frame)) + self.frame = _FakeFrame(_FakeCode("", get_file_name("sys._getframe()")), var, var) + self.tracker.track("thread1", pydevd_frame_utils.create_frames_list_from_frame(self.frame)) def untrack_all(self): self.tracker.untrack_all() - def get_children_variables(self, variable_ref = None): + def get_children_variables(self, variable_ref=None): var_ref = variable_ref if not var_ref: var_ref = id(self.frame) @@ -77,13 +80,13 @@ def get_children_variables(self, variable_ref = None): class DebugpyMessageQueue: - HEADER = 'Content-Length: ' + HEADER = "Content-Length: " HEADER_LENGTH = 16 - SEPARATOR = '\r\n\r\n' + SEPARATOR = "\r\n\r\n" SEPARATOR_LENGTH = 4 def __init__(self, event_callback, log): - self.tcp_buffer = '' + self.tcp_buffer = "" self._reset_tcp_pos() self.event_callback = event_callback self.message_queue = Queue() @@ -96,21 +99,21 @@ def _reset_tcp_pos(self): self.message_pos = -1 def _put_message(self, raw_msg): - self.log.debug('QUEUE - _put_message:') + self.log.debug("QUEUE - _put_message:") msg = jsonapi.loads(raw_msg) - if msg['type'] == 'event': - self.log.debug('QUEUE - received event:') + if msg["type"] == "event": + self.log.debug("QUEUE - received event:") self.log.debug(msg) self.event_callback(msg) else: - self.log.debug('QUEUE - put message:') + self.log.debug("QUEUE - put message:") self.log.debug(msg) self.message_queue.put_nowait(msg) def put_tcp_frame(self, frame): self.tcp_buffer += frame - self.log.debug('QUEUE - received frame') + self.log.debug("QUEUE - received frame") while True: # Finds header if self.header_pos == -1: @@ -118,37 +121,39 @@ def put_tcp_frame(self, frame): if self.header_pos == -1: return - self.log.debug('QUEUE - found header at pos %i', self.header_pos) + self.log.debug("QUEUE - found header at pos %i", self.header_pos) - #Finds separator + # Finds separator if self.separator_pos == -1: hint = self.header_pos + DebugpyMessageQueue.HEADER_LENGTH self.separator_pos = self.tcp_buffer.find(DebugpyMessageQueue.SEPARATOR, hint) if self.separator_pos == -1: return - self.log.debug('QUEUE - found separator at pos %i', self.separator_pos) + self.log.debug("QUEUE - found separator at pos %i", self.separator_pos) if self.message_pos == -1: size_pos = self.header_pos + DebugpyMessageQueue.HEADER_LENGTH self.message_pos = self.separator_pos + DebugpyMessageQueue.SEPARATOR_LENGTH - self.message_size = int(self.tcp_buffer[size_pos:self.separator_pos]) + self.message_size = int(self.tcp_buffer[size_pos : self.separator_pos]) - self.log.debug('QUEUE - found message at pos %i', self.message_pos) - self.log.debug('QUEUE - message size is %i', self.message_size) + self.log.debug("QUEUE - found message at pos %i", self.message_pos) + self.log.debug("QUEUE - message size is %i", self.message_size) if len(self.tcp_buffer) - self.message_pos < self.message_size: return - self._put_message(self.tcp_buffer[self.message_pos:self.message_pos + self.message_size]) + self._put_message( + self.tcp_buffer[self.message_pos : self.message_pos + self.message_size] + ) if len(self.tcp_buffer) - self.message_pos == self.message_size: - self.log.debug('QUEUE - resetting tcp_buffer') - self.tcp_buffer = '' + self.log.debug("QUEUE - resetting tcp_buffer") + self.tcp_buffer = "" self._reset_tcp_pos() return else: - self.tcp_buffer = self.tcp_buffer[self.message_pos + self.message_size:] - self.log.debug('QUEUE - slicing tcp_buffer: %s', self.tcp_buffer) + self.tcp_buffer = self.tcp_buffer[self.message_pos + self.message_size :] + self.log.debug("QUEUE - slicing tcp_buffer: %s", self.tcp_buffer) self._reset_tcp_pos() async def get_message(self): @@ -156,13 +161,12 @@ async def get_message(self): class DebugpyClient: - def __init__(self, log, debugpy_stream, event_callback): self.log = log self.debugpy_stream = debugpy_stream self.event_callback = event_callback self.message_queue = DebugpyMessageQueue(self._forward_event, self.log) - self.debugpy_host = '127.0.0.1' + self.debugpy_host = "127.0.0.1" self.debugpy_port = -1 self.routing_id = None self.wait_for_attach = True @@ -171,12 +175,12 @@ def __init__(self, log, debugpy_stream, event_callback): def _get_endpoint(self): host, port = self.get_host_port() - return 'tcp://' + host + ':' + str(port) + return "tcp://" + host + ":" + str(port) def _forward_event(self, msg): - if msg['event'] == 'initialized': + if msg["event"] == "initialized": self.init_event.set() - self.init_event_seq = msg['seq'] + self.init_event_seq = msg["seq"] self.event_callback(msg) def _send_request(self, msg): @@ -189,7 +193,9 @@ def _send_request(self, msg): allow_nan=False, ) content_length = str(len(content)) - buf = (DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR).encode('ascii') + buf = (DebugpyMessageQueue.HEADER + content_length + DebugpyMessageQueue.SEPARATOR).encode( + "ascii" + ) buf += content self.log.debug("DEBUGPYCLIENT:") self.log.debug(self.routing_id) @@ -208,9 +214,9 @@ async def _handle_init_sequence(self): # 2] Sends configurationDone request configurationDone = { - 'type': 'request', - 'seq': int(self.init_event_seq) + 1, - 'command': 'configurationDone' + "type": "request", + "seq": int(self.init_event_seq) + 1, + "command": "configurationDone", } self._send_request(configurationDone) @@ -224,11 +230,11 @@ async def _handle_init_sequence(self): def get_host_port(self): if self.debugpy_port == -1: socket = self.debugpy_stream.socket - socket.bind_to_random_port('tcp://' + self.debugpy_host) - self.endpoint = socket.getsockopt(zmq.LAST_ENDPOINT).decode('utf-8') + socket.bind_to_random_port("tcp://" + self.debugpy_host) + self.endpoint = socket.getsockopt(zmq.LAST_ENDPOINT).decode("utf-8") socket.unbind(self.endpoint) - index = self.endpoint.rfind(':') - self.debugpy_port = self.endpoint[index+1:] + index = self.endpoint.rfind(":") + self.debugpy_port = self.endpoint[index + 1 :] return self.debugpy_host, self.debugpy_port def connect_tcp_socket(self): @@ -247,13 +253,13 @@ def receive_dap_frame(self, frame): async def send_dap_request(self, msg): self._send_request(msg) - if self.wait_for_attach and msg['command'] == 'attach': + if self.wait_for_attach and msg["command"] == "attach": rep = await self._handle_init_sequence() self.wait_for_attach = False return rep else: rep = await self._wait_for_response() - self.log.debug('DEBUGPYCLIENT - returning:') + self.log.debug("DEBUGPYCLIENT - returning:") self.log.debug(rep) return rep @@ -262,19 +268,21 @@ class Debugger: # Requests that requires that the debugger has started started_debug_msg_types = [ - 'dumpCell', 'setBreakpoints', - 'source', 'stackTrace', - 'variables', 'attach', - 'configurationDone' + "dumpCell", + "setBreakpoints", + "source", + "stackTrace", + "variables", + "attach", + "configurationDone", ] # Requests that can be handled even if the debugger is not running - static_debug_msg_types = [ - 'debugInfo', 'inspectVariables', - 'richInspectVariables', 'modules' - ] + static_debug_msg_types = ["debugInfo", "inspectVariables", "richInspectVariables", "modules"] - def __init__(self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code = True): + def __init__( + self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code=True + ): self.log = log self.debugpy_client = DebugpyClient(log, debugpy_stream, self._handle_event) self.shell_socket = shell_socket @@ -298,26 +306,26 @@ def __init__(self, log, debugpy_stream, event_callback, shell_socket, session, j self.debugpy_initialized = False self._removed_cleanup = {} - self.debugpy_host = '127.0.0.1' + self.debugpy_host = "127.0.0.1" self.debugpy_port = 0 self.endpoint = None self.variable_explorer = VariableExplorer() def _handle_event(self, msg): - if msg['event'] == 'stopped': - if msg['body']['allThreadsStopped']: + if msg["event"] == "stopped": + if msg["body"]["allThreadsStopped"]: self.stopped_queue.put_nowait(msg) # Do not forward the event now, will be done in the handle_stopped_event return else: - self.stopped_threads.add(msg['body']['threadId']) + self.stopped_threads.add(msg["body"]["threadId"]) self.event_callback(msg) - elif msg['event'] == 'continued': - if msg['body']['allThreadsContinued']: + elif msg["event"] == "continued": + if msg["body"]["allThreadsContinued"]: self.stopped_threads = set() else: - self.stopped_threads.remove(msg['body']['threadId']) + self.stopped_threads.remove(msg["body"]["threadId"]) self.event_callback(msg) else: self.event_callback(msg) @@ -326,43 +334,32 @@ async def _forward_message(self, msg): return await self.debugpy_client.send_dap_request(msg) def _build_variables_response(self, request, variables): - var_list = [var for var in variables if self.accept_variable(var['name'])] + var_list = [var for var in variables if self.accept_variable(var["name"])] reply = { - 'seq': request['seq'], - 'type': 'response', - 'request_seq': request['seq'], - 'success': True, - 'command': request['command'], - 'body': { - 'variables': var_list - } + "seq": request["seq"], + "type": "response", + "request_seq": request["seq"], + "success": True, + "command": request["command"], + "body": {"variables": var_list}, } return reply def _accept_stopped_thread(self, thread_name): # TODO: identify Thread-2, Thread-3 and Thread-4. These are NOT # Control, IOPub or Heartbeat threads - forbid_list = [ - 'IPythonHistorySavingThread', - 'Thread-2', - 'Thread-3', - 'Thread-4' - ] + forbid_list = ["IPythonHistorySavingThread", "Thread-2", "Thread-3", "Thread-4"] return thread_name not in forbid_list async def handle_stopped_event(self): # Wait for a stopped event message in the stopped queue # This message is used for triggering the 'threads' request event = await self.stopped_queue.get() - req = { - 'seq': event['seq'] + 1, - 'type': 'request', - 'command': 'threads' - } + req = {"seq": event["seq"] + 1, "type": "request", "command": "threads"} rep = await self._forward_message(req) - for t in rep['body']['threads']: - if self._accept_stopped_thread(t['name']): - self.stopped_threads.add(t['id']) + for t in rep["body"]["threads"]: + if self._accept_stopped_thread(t["name"]): + self.stopped_threads.add(t["id"]) self.event_callback(event) @property @@ -375,17 +372,19 @@ def start(self): if not os.path.exists(tmp_dir): os.makedirs(tmp_dir) host, port = self.debugpy_client.get_host_port() - code = 'import debugpy;' - code += 'debugpy.listen(("' + host + '",' + port + '))' - content = { - 'code': code, - 'silent': True - } - self.session.send(self.shell_socket, 'execute_request', content, - None, (self.shell_socket.getsockopt(ROUTING_ID))) + code = "import debugpy;" + code += 'debugpy.listen(("' + host + '",' + port + "))" + content = {"code": code, "silent": True} + self.session.send( + self.shell_socket, + "execute_request", + content, + None, + (self.shell_socket.getsockopt(ROUTING_ID)), + ) ident, msg = self.session.recv(self.shell_socket, mode=0) - self.debugpy_initialized = msg['content']['status'] == 'ok' + self.debugpy_initialized = msg["content"]["status"] == "ok" # Don't remove leading empty lines when debugging so the breakpoints are correctly positioned cleanup_transforms = get_ipython().input_transformer_manager.cleanup_transforms @@ -406,20 +405,18 @@ def stop(self): cleanup_transforms.insert(index, func) async def dumpCell(self, message): - code = message['arguments']['code'] + code = message["arguments"]["code"] file_name = get_file_name(code) - with open(file_name, 'w', encoding='utf-8') as f: + with open(file_name, "w", encoding="utf-8") as f: f.write(code) reply = { - 'type': 'response', - 'request_seq': message['seq'], - 'success': True, - 'command': message['command'], - 'body': { - 'sourcePath': file_name - } + "type": "response", + "request_seq": message["seq"], + "success": True, + "command": message["command"], + "body": {"sourcePath": file_name}, } return reply @@ -429,22 +426,16 @@ async def setBreakpoints(self, message): return await self._forward_message(message) async def source(self, message): - reply = { - 'type': 'response', - 'request_seq': message['seq'], - 'command': message['command'] - } + reply = {"type": "response", "request_seq": message["seq"], "command": message["command"]} source_path = message["arguments"]["source"]["path"] if os.path.isfile(source_path): - with open(source_path, encoding='utf-8') as f: - reply['success'] = True - reply['body'] = { - 'content': f.read() - } + with open(source_path, encoding="utf-8") as f: + reply["success"] = True + reply["body"] = {"content": f.read()} else: - reply['success'] = False - reply['message'] = 'source unavailable' - reply['body'] = {} + reply["success"] = False + reply["message"] = "source unavailable" + reply["body"] = {} return reply @@ -462,105 +453,98 @@ async def stackTrace(self, message): try: sf_list = reply["body"]["stackFrames"] module_idx = len(sf_list) - next( - i - for i, v in enumerate(reversed(sf_list), 1) - if v["name"] == "" and i != 1 + i for i, v in enumerate(reversed(sf_list), 1) if v["name"] == "" and i != 1 ) - reply["body"]["stackFrames"] = reply["body"]["stackFrames"][ - : module_idx + 1 - ] + reply["body"]["stackFrames"] = reply["body"]["stackFrames"][: module_idx + 1] except StopIteration: pass return reply def accept_variable(self, variable_name): forbid_list = [ - '__name__', - '__doc__', - '__package__', - '__loader__', - '__spec__', - '__annotations__', - '__builtins__', - '__builtin__', - '__display__', - 'get_ipython', - 'debugpy', - 'exit', - 'quit', - 'In', - 'Out', - '_oh', - '_dh', - '_', - '__', - '___' + "__name__", + "__doc__", + "__package__", + "__loader__", + "__spec__", + "__annotations__", + "__builtins__", + "__builtin__", + "__display__", + "get_ipython", + "debugpy", + "exit", + "quit", + "In", + "Out", + "_oh", + "_dh", + "_", + "__", + "___", ] cond = variable_name not in forbid_list - cond = cond and not bool(re.search(r'^_\d', variable_name)) - cond = cond and variable_name[0:2] != '_i' + cond = cond and not bool(re.search(r"^_\d", variable_name)) + cond = cond and variable_name[0:2] != "_i" return cond async def variables(self, message): reply = {} if not self.stopped_threads: - variables = self.variable_explorer.get_children_variables(message['arguments']['variablesReference']) + variables = self.variable_explorer.get_children_variables( + message["arguments"]["variablesReference"] + ) return self._build_variables_response(message, variables) else: reply = await self._forward_message(message) # TODO : check start and count arguments work as expected in debugpy - reply['body']['variables'] = \ - [var for var in reply['body']['variables'] if self.accept_variable(var['name'])] + reply["body"]["variables"] = [ + var for var in reply["body"]["variables"] if self.accept_variable(var["name"]) + ] return reply async def attach(self, message): host, port = self.debugpy_client.get_host_port() - message['arguments']['connect'] = { - 'host': host, - 'port': port - } - message['arguments']['logToFile'] = True + message["arguments"]["connect"] = {"host": host, "port": port} + message["arguments"]["logToFile"] = True # Experimental option to break in non-user code. # The ipykernel source is in the call stack, so the user # has to manipulate the step-over and step-into in a wize way. # Set debugOptions for breakpoints in python standard library source. if not self.just_my_code: - message['arguments']['debugOptions'] = [ 'DebugStdLib' ] + message["arguments"]["debugOptions"] = ["DebugStdLib"] return await self._forward_message(message) async def configurationDone(self, message): reply = { - 'seq': message['seq'], - 'type': 'response', - 'request_seq': message['seq'], - 'success': True, - 'command': message['command'] + "seq": message["seq"], + "type": "response", + "request_seq": message["seq"], + "success": True, + "command": message["command"], } return reply async def debugInfo(self, message): breakpoint_list = [] for key, value in self.breakpoint_list.items(): - breakpoint_list.append({ - 'source': key, - 'breakpoints': value - }) + breakpoint_list.append({"source": key, "breakpoints": value}) reply = { - 'type': 'response', - 'request_seq': message['seq'], - 'success': True, - 'command': message['command'], - 'body': { - 'isStarted': self.is_started, - 'hashMethod': 'Murmur2', - 'hashSeed': get_tmp_hash_seed(), - 'tmpFilePrefix': get_tmp_directory() + os.sep, - 'tmpFileSuffix': '.py', - 'breakpoints': breakpoint_list, - 'stoppedThreads': list(self.stopped_threads), - 'richRendering': True, - 'exceptionPaths': ['Python Exceptions'] - } + "type": "response", + "request_seq": message["seq"], + "success": True, + "command": message["command"], + "body": { + "isStarted": self.is_started, + "hashMethod": "Murmur2", + "hashSeed": get_tmp_hash_seed(), + "tmpFilePrefix": get_tmp_directory() + os.sep, + "tmpFileSuffix": ".py", + "breakpoints": breakpoint_list, + "stoppedThreads": list(self.stopped_threads), + "richRendering": True, + "exceptionPaths": ["Python Exceptions"], + }, } return reply @@ -627,56 +611,52 @@ async def richInspectVariables(self, message): async def modules(self, message): modules = list(sys.modules.values()) - startModule = message.get('startModule', 0) - moduleCount = message.get('moduleCount', len(modules)) + startModule = message.get("startModule", 0) + moduleCount = message.get("moduleCount", len(modules)) mods = [] for i in range(startModule, moduleCount): module = modules[i] - filename = getattr(getattr(module, '__spec__', None), 'origin', None) - if filename and filename.endswith('.py'): - mods.append({ - 'id': i, - 'name': module.__name__, - 'path': filename - }) - - reply = {'body': {'modules': mods, 'totalModules': len(modules)}} + filename = getattr(getattr(module, "__spec__", None), "origin", None) + if filename and filename.endswith(".py"): + mods.append({"id": i, "name": module.__name__, "path": filename}) + + reply = {"body": {"modules": mods, "totalModules": len(modules)}} return reply async def process_request(self, message): reply = {} - if message['command'] == 'initialize': + if message["command"] == "initialize": if self.is_started: - self.log.info('The debugger has already started') + self.log.info("The debugger has already started") else: self.is_started = self.start() if self.is_started: - self.log.info('The debugger has started') + self.log.info("The debugger has started") else: reply = { - 'command': 'initialize', - 'request_seq': message['seq'], - 'seq': 3, - 'success': False, - 'type': 'response' + "command": "initialize", + "request_seq": message["seq"], + "seq": 3, + "success": False, + "type": "response", } - handler = self.static_debug_handlers.get(message['command'], None) + handler = self.static_debug_handlers.get(message["command"], None) if handler is not None: reply = await handler(message) elif self.is_started: - handler = self.started_debug_handlers.get(message['command'], None) + handler = self.started_debug_handlers.get(message["command"], None) if handler is not None: reply = await handler(message) else: reply = await self._forward_message(message) - if message['command'] == 'disconnect': + if message["command"] == "disconnect": self.stop() self.breakpoint_list = {} self.stopped_threads = set() self.is_started = False - self.log.info('The debugger has stopped') + self.log.info("The debugger has stopped") return reply diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index 19ee151f4..2ba2c08c3 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -7,15 +7,17 @@ import sys from IPython.core.displayhook import DisplayHook +from jupyter_client.session import Session, extract_header +from traitlets import Any, Dict, Instance + from ipykernel.jsonutil import encode_images, json_clean -from traitlets import Instance, Dict, Any -from jupyter_client.session import extract_header, Session class ZMQDisplayHook: """A simple displayhook that publishes the object's repr over a ZeroMQ socket.""" - topic = b'execute_result' + + topic = b"execute_result" def __init__(self, session, pub_socket): self.session = session @@ -33,11 +35,14 @@ def __call__(self, obj): builtins._ = obj sys.stdout.flush() sys.stderr.flush() - contents = {'execution_count': self.get_execution_count(), - 'data': {'text/plain': repr(obj)}, - 'metadata': {}} - self.session.send(self.pub_socket, 'execute_result', contents, - parent=self.parent_header, ident=self.topic) + contents = { + "execution_count": self.get_execution_count(), + "data": {"text/plain": repr(obj)}, + "metadata": {}, + } + self.session.send( + self.pub_socket, "execute_result", contents, parent=self.parent_header, ident=self.topic + ) def set_parent(self, parent): self.parent_header = extract_header(parent) @@ -47,7 +52,8 @@ class ZMQShellDisplayHook(DisplayHook): """A displayhook subclass that publishes data using ZeroMQ. This is intended to work with an InteractiveShell instance. It sends a dict of different representations of the object.""" - topic=None + + topic = None session = Instance(Session, allow_none=True) pub_socket = Any(allow_none=True) @@ -58,23 +64,27 @@ def set_parent(self, parent): self.parent_header = extract_header(parent) def start_displayhook(self): - self.msg = self.session.msg('execute_result', { - 'data': {}, - 'metadata': {}, - }, parent=self.parent_header) + self.msg = self.session.msg( + "execute_result", + { + "data": {}, + "metadata": {}, + }, + parent=self.parent_header, + ) def write_output_prompt(self): """Write the output prompt.""" - self.msg['content']['execution_count'] = self.prompt_count + self.msg["content"]["execution_count"] = self.prompt_count def write_format_data(self, format_dict, md_dict=None): - self.msg['content']['data'] = json_clean(encode_images(format_dict)) - self.msg['content']['metadata'] = md_dict + self.msg["content"]["data"] = json_clean(encode_images(format_dict)) + self.msg["content"]["metadata"] = md_dict def finish_displayhook(self): """Finish up all displayhook activities.""" sys.stdout.flush() sys.stderr.flush() - if self.msg['content']['data']: + if self.msg["content"]["data"]: self.session.send(self.pub_socket, self.msg, ident=self.topic) self.msg = None diff --git a/ipykernel/embed.py b/ipykernel/embed.py index 4334eb182..53c11119b 100644 --- a/ipykernel/embed.py +++ b/ipykernel/embed.py @@ -1,8 +1,8 @@ """Simple function for embedding an IPython kernel """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Imports -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- import sys @@ -10,9 +10,10 @@ from .kernelapp import IPKernelApp -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Code -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + def embed_kernel(module=None, local_ns=None, **kwargs): """Embed and start an IPython kernel in a given scope. diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 4911c87ee..eae3a9e59 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -3,14 +3,13 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from functools import partial import os -import sys import platform +import sys +from distutils.version import LooseVersion as V +from functools import partial import zmq - -from distutils.version import LooseVersion as V from traitlets.config.application import Application @@ -19,7 +18,7 @@ def _use_appnope(): Checks if we are on OS X 10.9 or greater. """ - return sys.platform == 'darwin' and V(platform.mac_ver()[0]) >= V('10.9') + return sys.platform == "darwin" and V(platform.mac_ver()[0]) >= V("10.9") def _notify_stream_qt(kernel, stream): @@ -51,16 +50,18 @@ def process_stream_events(): timer.timeout.connect(process_stream_events) timer.start(0) + # mapping of keys to loop functions loop_map = { - 'inline': None, - 'nbagg': None, - 'notebook': None, - 'ipympl': None, - 'widget': None, + "inline": None, + "nbagg": None, + "notebook": None, + "ipympl": None, + "widget": None, None: None, } + def register_integration(*toolkitnames): """Decorator to register an event loop to integrate with the IPython kernel @@ -74,6 +75,7 @@ def register_integration(*toolkitnames): :mod:`ipykernel.eventloops` provides and registers such functions for a few common event loops. """ + def decorator(func): for name in toolkitnames: loop_map[name] = func @@ -106,12 +108,12 @@ def _loop_qt(app): app._in_event_loop = False -@register_integration('qt4') +@register_integration("qt4") def loop_qt4(kernel): """Start a kernel with PyQt4 event loop integration.""" - from IPython.lib.guisupport import get_app_qt4 from IPython.external.qt_for_kernel import QtGui + from IPython.lib.guisupport import get_app_qt4 kernel.app = get_app_qt4([" "]) if isinstance(kernel.app, QtGui.QApplication): @@ -121,19 +123,21 @@ def loop_qt4(kernel): _loop_qt(kernel.app) -@register_integration('qt', 'qt5') +@register_integration("qt", "qt5") def loop_qt5(kernel): """Start a kernel with PyQt5 event loop integration.""" - if os.environ.get('QT_API', None) is None: + if os.environ.get("QT_API", None) is None: try: import PyQt5 - os.environ['QT_API'] = 'pyqt5' + + os.environ["QT_API"] = "pyqt5" except ImportError: try: import PySide2 - os.environ['QT_API'] = 'pyside2' + + os.environ["QT_API"] = "pyside2" except ImportError: - os.environ['QT_API'] = 'pyqt5' + os.environ["QT_API"] = "pyqt5" return loop_qt4(kernel) @@ -156,7 +160,7 @@ def _loop_wx(app): app._in_event_loop = False -@register_integration('wx') +@register_integration("wx") def loop_wx(kernel): """Start a kernel with wx event loop support.""" @@ -195,16 +199,14 @@ def OnInit(self): # The redirect=False here makes sure that wx doesn't replace # sys.stdout/stderr with its own classes. - if not ( - getattr(kernel, 'app', None) - and isinstance(kernel.app, wx.App) - ): + if not (getattr(kernel, "app", None) and isinstance(kernel.app, wx.App)): kernel.app = IPWxApp(redirect=False) # The import of wx on Linux sets the handler for signal.SIGINT # to 0. This is a bug in wx or gtk. We fix by just setting it # back to the Python default. import signal + if not callable(signal.getsignal(signal.SIGINT)): signal.signal(signal.SIGINT, signal.default_int_handler) @@ -214,20 +216,21 @@ def OnInit(self): @loop_wx.exit def loop_wx_exit(kernel): import wx + wx.Exit() -@register_integration('tk') +@register_integration("tk") def loop_tk(kernel): """Start a kernel with the Tk event loop.""" - from tkinter import Tk, READABLE + from tkinter import READABLE, Tk app = Tk() # Capability detection: # per https://docs.python.org/3/library/tkinter.html#file-handlers # file handlers are not available on Windows - if hasattr(app, 'createfilehandler'): + if hasattr(app, "createfilehandler"): # A basic wrapper for structural similarity with the Windows version class BasicAppWrapper: def __init__(self, app): @@ -254,7 +257,9 @@ def process_stream_events(stream, *a, **kw): else: import asyncio + import nest_asyncio + nest_asyncio.apply() doi = kernel.do_one_iteration @@ -291,7 +296,7 @@ def loop_tk_exit(kernel): pass -@register_integration('gtk') +@register_integration("gtk") def loop_gtk(kernel): """Start the kernel, coordinating with the GTK event loop""" from .gui.gtkembed import GTKEmbed @@ -306,7 +311,7 @@ def loop_gtk_exit(kernel): kernel._gtk.stop() -@register_integration('gtk3') +@register_integration("gtk3") def loop_gtk3(kernel): """Start the kernel, coordinating with the GTK event loop""" from .gui.gtk3embed import GTKEmbed @@ -321,7 +326,7 @@ def loop_gtk3_exit(kernel): kernel._gtk.stop() -@register_integration('osx') +@register_integration("osx") def loop_cocoa(kernel): """Start the kernel, coordinating with the Cocoa CFRunLoop event loop via the matplotlib MacOSX backend. @@ -329,6 +334,7 @@ def loop_cocoa(kernel): from ._eventloop_macos import mainloop, stop real_excepthook = sys.excepthook + def handle_int(etype, value, tb): """don't let KeyboardInterrupts look like crashes""" # wake the eventloop when we get a signal @@ -362,13 +368,15 @@ def handle_int(etype, value, tb): @loop_cocoa.exit def loop_cocoa_exit(kernel): from ._eventloop_macos import stop + stop() -@register_integration('asyncio') +@register_integration("asyncio") def loop_asyncio(kernel): - '''Start a kernel with asyncio event loop support.''' + """Start a kernel with asyncio event loop support.""" import asyncio + loop = asyncio.get_event_loop() # loop is already running (e.g. tornado 5), nothing left to do if loop.is_running(): @@ -409,11 +417,12 @@ def process_stream_events(stream): def loop_asyncio_exit(kernel): """Exit hook for asyncio""" import asyncio + loop = asyncio.get_event_loop() @asyncio.coroutine def close_loop(): - if hasattr(loop, 'shutdown_asyncgens'): + if hasattr(loop, "shutdown_asyncgens"): yield from loop.shutdown_asyncgens() loop._should_close = True loop.stop() @@ -433,9 +442,10 @@ def enable_gui(gui, kernel=None): raise ValueError(e) if kernel is None: if Application.initialized(): - kernel = getattr(Application.instance(), 'kernel', None) + kernel = getattr(Application.instance(), "kernel", None) if kernel is None: - raise RuntimeError("You didn't specify a kernel," + raise RuntimeError( + "You didn't specify a kernel," " and no IPython Application with a kernel appears to be running." ) loop = loop_map[gui] diff --git a/ipykernel/gui/__init__.py b/ipykernel/gui/__init__.py index 1351f3c27..bbf3b9ffc 100644 --- a/ipykernel/gui/__init__.py +++ b/ipykernel/gui/__init__.py @@ -5,11 +5,11 @@ toolkits. """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (C) 2010-2011 The IPython Development Team. # # Distributed under the terms of the BSD License. # # The full license is in the file COPYING.txt, distributed as part of this # software. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index 4d6803877..198f8cacd 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -1,31 +1,33 @@ """GUI support for the IPython ZeroMQ kernel - GTK toolkit support. """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (C) 2010-2011 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING.txt, distributed as part of this software. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Imports -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # stdlib import sys # Third-party import gi -gi.require_version ('Gdk', '3.0') -gi.require_version ('Gtk', '3.0') + +gi.require_version("Gdk", "3.0") +gi.require_version("Gtk", "3.0") from gi.repository import GObject, Gtk -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Classes and functions -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + class GTKEmbed: - """A class to embed a kernel into the GTK main event loop. - """ + """A class to embed a kernel into the GTK main event loop.""" + def __init__(self, kernel): self.kernel = kernel # These two will later store the real gtk functions when we hijack them @@ -33,8 +35,7 @@ def __init__(self, kernel): self.gtk_main_quit = None def start(self): - """Starts the GTK main event loop and sets our kernel startup routine. - """ + """Starts the GTK main event loop and sets our kernel startup routine.""" # Register our function to initiate the kernel and start gtk GObject.idle_add(self._wire_kernel) Gtk.main() @@ -46,8 +47,7 @@ def _wire_kernel(self): returns False to ensure it doesn't get run again by GTK. """ self.gtk_main, self.gtk_main_quit = self._hijack_gtk() - GObject.timeout_add(int(1000*self.kernel._poll_interval), - self.iterate_kernel) + GObject.timeout_add(int(1000 * self.kernel._poll_interval), self.iterate_kernel) return False def iterate_kernel(self): @@ -80,8 +80,10 @@ def _hijack_gtk(self): - Gtk.main - Gtk.main_quit """ + def dummy(*args, **kw): pass + # save and trap main and main_quit from gtk orig_main, Gtk.main = Gtk.main, dummy orig_main_quit, Gtk.main_quit = Gtk.main_quit, dummy diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index d77224a3f..19522c441 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -1,15 +1,15 @@ """GUI support for the IPython ZeroMQ kernel - GTK toolkit support. """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (C) 2010-2011 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING.txt, distributed as part of this software. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Imports -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # stdlib import sys @@ -17,13 +17,14 @@ import gobject import gtk -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Classes and functions -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + class GTKEmbed: - """A class to embed a kernel into the GTK main event loop. - """ + """A class to embed a kernel into the GTK main event loop.""" + def __init__(self, kernel): self.kernel = kernel # These two will later store the real gtk functions when we hijack them @@ -31,8 +32,7 @@ def __init__(self, kernel): self.gtk_main_quit = None def start(self): - """Starts the GTK main event loop and sets our kernel startup routine. - """ + """Starts the GTK main event loop and sets our kernel startup routine.""" # Register our function to initiate the kernel and start gtk gobject.idle_add(self._wire_kernel) gtk.main() @@ -44,8 +44,7 @@ def _wire_kernel(self): returns False to ensure it doesn't get run again by GTK. """ self.gtk_main, self.gtk_main_quit = self._hijack_gtk() - gobject.timeout_add(int(1000*self.kernel._poll_interval), - self.iterate_kernel) + gobject.timeout_add(int(1000 * self.kernel._poll_interval), self.iterate_kernel) return False def iterate_kernel(self): @@ -78,8 +77,10 @@ def _hijack_gtk(self): - gtk.main - gtk.main_quit """ + def dummy(*args, **kw): pass + # save and trap main and main_quit from gtk orig_main, gtk.main = gtk.main, dummy orig_main_quit, gtk.main_quit = gtk.main_quit, dummy diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 77c90b974..6f5ddbec3 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -1,16 +1,16 @@ """The client and server for a basic ping-pong style heartbeat. """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (C) 2008-2011 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Imports -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- import errno import os @@ -18,12 +18,11 @@ from threading import Thread import zmq - from jupyter_client.localinterfaces import localhost -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Code -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- class Heartbeat(Thread): @@ -31,7 +30,7 @@ class Heartbeat(Thread): def __init__(self, context, addr=None): if addr is None: - addr = ('tcp', localhost(), 0) + addr = ("tcp", localhost(), 0) Thread.__init__(self, name="Heartbeat") self.context = context self.transport, self.ip, self.port = addr @@ -45,13 +44,13 @@ def __init__(self, context, addr=None): self.name = "Heartbeat" def pick_port(self): - if self.transport == 'tcp': + if self.transport == "tcp": s = socket.socket() # '*' means all interfaces to 0MQ, which is '' to socket.socket - s.bind(('' if self.ip == '*' else self.ip, 0)) + s.bind(("" if self.ip == "*" else self.ip, 0)) self.port = s.getsockname()[1] s.close() - elif self.transport == 'ipc': + elif self.transport == "ipc": self.port = 1 while os.path.exists("%s-%s" % (self.ip, self.port)): self.port = self.port + 1 @@ -60,8 +59,8 @@ def pick_port(self): return self.port def _try_bind_socket(self): - c = ':' if self.transport == 'tcp' else '-' - return self.socket.bind('%s://%s' % (self.transport, self.ip) + c + str(self.port)) + c = ":" if self.transport == "tcp" else "-" + return self.socket.bind("%s://%s" % (self.transport, self.ip) + c + str(self.port)) def _bind_socket(self): try: diff --git a/ipykernel/inprocess/__init__.py b/ipykernel/inprocess/__init__.py index 8da75615e..b10698910 100644 --- a/ipykernel/inprocess/__init__.py +++ b/ipykernel/inprocess/__init__.py @@ -1,8 +1,4 @@ -from .channels import ( - InProcessChannel, - InProcessHBChannel, -) - +from .blocking import BlockingInProcessKernelClient +from .channels import InProcessChannel, InProcessHBChannel from .client import InProcessKernelClient from .manager import InProcessKernelManager -from .blocking import BlockingInProcessKernelClient diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index a36b95a29..269eb4158 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -2,26 +2,25 @@ Useful for test suites and blocking terminal interfaces. """ -#----------------------------------------------------------------------------- +import sys + +# ----------------------------------------------------------------------------- # Copyright (C) 2012 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING.txt, distributed as part of this software. -#----------------------------------------------------------------------------- -from queue import Queue, Empty -import sys +# ----------------------------------------------------------------------------- +from queue import Empty, Queue # IPython imports from traitlets import Type # Local imports -from .channels import ( - InProcessChannel, -) +from .channels import InProcessChannel from .client import InProcessKernelClient -class BlockingInProcessChannel(InProcessChannel): +class BlockingInProcessChannel(InProcessChannel): def __init__(self, *args, **kwds): super().__init__(*args, **kwds) self._in_queue = Queue() @@ -30,7 +29,7 @@ def call_handlers(self, msg): self._in_queue.put(msg) def get_msg(self, block=True, timeout=None): - """ Gets a message if there is one that is ready. """ + """Gets a message if there is one that is ready.""" if timeout is None: # Queue.get(timeout=None) has stupid uninteruptible # behavior, so wait for a week instead @@ -38,7 +37,7 @@ def get_msg(self, block=True, timeout=None): return self._in_queue.get(block, timeout) def get_msgs(self): - """ Get all messages that are currently ready. """ + """Get all messages that are currently ready.""" msgs = [] while True: try: @@ -48,24 +47,25 @@ def get_msgs(self): return msgs def msg_ready(self): - """ Is there a message that has been received? """ + """Is there a message that has been received?""" return not self._in_queue.empty() class BlockingInProcessStdInChannel(BlockingInProcessChannel): def call_handlers(self, msg): - """ Overridden for the in-process channel. + """Overridden for the in-process channel. This methods simply calls raw_input directly. """ - msg_type = msg['header']['msg_type'] - if msg_type == 'input_request': + msg_type = msg["header"]["msg_type"] + if msg_type == "input_request": _raw_input = self.client.kernel._sys_raw_input - prompt = msg['content']['prompt'] - print(prompt, end='', file=sys.__stdout__) + prompt = msg["content"]["prompt"] + print(prompt, end="", file=sys.__stdout__) sys.__stdout__.flush() self.client.input(_raw_input()) + class BlockingInProcessKernelClient(InProcessKernelClient): # The classes to use for the various channels. @@ -82,7 +82,7 @@ def wait_for_ready(self): except Empty: pass else: - if msg['msg_type'] == 'kernel_info_reply': + if msg["msg_type"] == "kernel_info_reply": # Checking that IOPub is connected. If it is not connected, start over. try: self.iopub_channel.get_msg(block=True, timeout=0.2) @@ -96,6 +96,6 @@ def wait_for_ready(self): while True: try: msg = self.iopub_channel.get_msg(block=True, timeout=0.2) - print(msg['msg_type']) + print(msg["msg_type"]) except Empty: break diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 14fcf62fa..b81d69321 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -5,12 +5,14 @@ from jupyter_client.channelsabc import HBChannelABC -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Channel classes -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + class InProcessChannel: """Base class for in-process channels.""" + proxy_methods = [] def __init__(self, client=None): @@ -28,18 +30,17 @@ def stop(self): self._is_alive = False def call_handlers(self, msg): - """ This method is called in the main thread when a message arrives. + """This method is called in the main thread when a message arrives. Subclasses should override this method to handle incoming messages. """ - raise NotImplementedError('call_handlers must be defined in a subclass.') + raise NotImplementedError("call_handlers must be defined in a subclass.") def flush(self, timeout=1.0): pass - def call_handlers_later(self, *args, **kwds): - """ Call the message handlers later. + """Call the message handlers later. The default implementation just calls the handlers immediately, but this method exists so that GUI toolkits can defer calling the handlers until @@ -48,7 +49,7 @@ def call_handlers_later(self, *args, **kwds): self.call_handlers(*args, **kwds) def process_events(self): - """ Process any pending GUI events. + """Process any pending GUI events. This method will be never be called from a frontend without an event loop (e.g., a terminal frontend). @@ -56,7 +57,6 @@ def process_events(self): raise NotImplementedError - class InProcessHBChannel: """A dummy heartbeat channel interface for in-process kernels. diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 84ac82a49..b8d57085f 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -1,32 +1,31 @@ """A client for in-process kernels.""" -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Copyright (C) 2012 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in # the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Imports -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- import asyncio -# IPython imports -from traitlets import Type, Instance, default -from jupyter_client.clientabc import KernelClientABC from jupyter_client.client import KernelClient +from jupyter_client.clientabc import KernelClientABC + +# IPython imports +from traitlets import Instance, Type, default # Local imports -from .channels import ( - InProcessChannel, - InProcessHBChannel, -) +from .channels import InProcessChannel, InProcessHBChannel -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Main kernel Client class -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + class InProcessKernelClient(KernelClient): """A client for an in-process kernel. @@ -45,21 +44,21 @@ class InProcessKernelClient(KernelClient): control_channel_class = Type(InProcessChannel) hb_channel_class = Type(InProcessHBChannel) - kernel = Instance('ipykernel.inprocess.ipkernel.InProcessKernel', - allow_none=True) + kernel = Instance("ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True) - #-------------------------------------------------------------------------- + # -------------------------------------------------------------------------- # Channel management methods - #-------------------------------------------------------------------------- + # -------------------------------------------------------------------------- - @default('blocking_class') + @default("blocking_class") def _default_blocking_class(self): from .blocking import BlockingInProcessKernelClient + return BlockingInProcessKernelClient def get_connection_info(self): d = super().get_connection_info() - d['kernel'] = self.kernel + d["kernel"] = self.kernel return d def start_channels(self, *args, **kwargs): @@ -99,51 +98,57 @@ def hb_channel(self): # Methods for sending specific messages # ------------------------------------- - def execute(self, code, silent=False, store_history=True, - user_expressions={}, allow_stdin=None): + def execute( + self, code, silent=False, store_history=True, user_expressions={}, allow_stdin=None + ): if allow_stdin is None: allow_stdin = self.allow_stdin - content = dict(code=code, silent=silent, store_history=store_history, - user_expressions=user_expressions, - allow_stdin=allow_stdin) - msg = self.session.msg('execute_request', content) + content = dict( + code=code, + silent=silent, + store_history=store_history, + user_expressions=user_expressions, + allow_stdin=allow_stdin, + ) + msg = self.session.msg("execute_request", content) self._dispatch_to_kernel(msg) - return msg['header']['msg_id'] + return msg["header"]["msg_id"] def complete(self, code, cursor_pos=None): if cursor_pos is None: cursor_pos = len(code) content = dict(code=code, cursor_pos=cursor_pos) - msg = self.session.msg('complete_request', content) + msg = self.session.msg("complete_request", content) self._dispatch_to_kernel(msg) - return msg['header']['msg_id'] + return msg["header"]["msg_id"] def inspect(self, code, cursor_pos=None, detail_level=0): if cursor_pos is None: cursor_pos = len(code) - content = dict(code=code, cursor_pos=cursor_pos, + content = dict( + code=code, + cursor_pos=cursor_pos, detail_level=detail_level, ) - msg = self.session.msg('inspect_request', content) + msg = self.session.msg("inspect_request", content) self._dispatch_to_kernel(msg) - return msg['header']['msg_id'] + return msg["header"]["msg_id"] - def history(self, raw=True, output=False, hist_access_type='range', **kwds): - content = dict(raw=raw, output=output, - hist_access_type=hist_access_type, **kwds) - msg = self.session.msg('history_request', content) + def history(self, raw=True, output=False, hist_access_type="range", **kwds): + content = dict(raw=raw, output=output, hist_access_type=hist_access_type, **kwds) + msg = self.session.msg("history_request", content) self._dispatch_to_kernel(msg) - return msg['header']['msg_id'] + return msg["header"]["msg_id"] def shutdown(self, restart=False): # FIXME: What to do here? - raise NotImplementedError('Cannot shutdown in-process kernel') + raise NotImplementedError("Cannot shutdown in-process kernel") def kernel_info(self): """Request kernel info.""" - msg = self.session.msg('kernel_info_request') + msg = self.session.msg("kernel_info_request") self._dispatch_to_kernel(msg) - return msg['header']['msg_id'] + return msg["header"]["msg_id"] def comm_info(self, target_name=None): """Request a dictionary of valid comms and their targets.""" @@ -151,26 +156,25 @@ def comm_info(self, target_name=None): content = {} else: content = dict(target_name=target_name) - msg = self.session.msg('comm_info_request', content) + msg = self.session.msg("comm_info_request", content) self._dispatch_to_kernel(msg) - return msg['header']['msg_id'] + return msg["header"]["msg_id"] def input(self, string): if self.kernel is None: - raise RuntimeError('Cannot send input reply. No kernel exists.') + raise RuntimeError("Cannot send input reply. No kernel exists.") self.kernel.raw_input_str = string def is_complete(self, code): - msg = self.session.msg('is_complete_request', {'code': code}) + msg = self.session.msg("is_complete_request", {"code": code}) self._dispatch_to_kernel(msg) - return msg['header']['msg_id'] + return msg["header"]["msg_id"] def _dispatch_to_kernel(self, msg): - """ Send a message to the kernel and handle a reply. - """ + """Send a message to the kernel and handle a reply.""" kernel = self.kernel if kernel is None: - raise RuntimeError('Cannot send request. No kernel exists.') + raise RuntimeError("Cannot send request. No kernel exists.") stream = kernel.shell_stream self.session.send(stream, msg) @@ -181,20 +185,20 @@ def _dispatch_to_kernel(self, msg): self.shell_channel.call_handlers_later(reply_msg) def get_shell_msg(self, block=True, timeout=None): - return self.shell_channel.get_msg(block, timeout) + return self.shell_channel.get_msg(block, timeout) def get_iopub_msg(self, block=True, timeout=None): - return self.iopub_channel.get_msg(block, timeout) + return self.iopub_channel.get_msg(block, timeout) def get_stdin_msg(self, block=True, timeout=None): - return self.stdin_channel.get_msg(block, timeout) + return self.stdin_channel.get_msg(block, timeout) def get_control_msg(self, block=True, timeout=None): - return self.control_channel.get_msg(block, timeout) + return self.control_channel.get_msg(block, timeout) -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # ABC Registration -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- KernelClientABC.register(InProcessKernelClient) diff --git a/ipykernel/inprocess/constants.py b/ipykernel/inprocess/constants.py index fe07a3406..6133c757d 100644 --- a/ipykernel/inprocess/constants.py +++ b/ipykernel/inprocess/constants.py @@ -5,4 +5,4 @@ # key everywhere. This is not just the empty bytestring to avoid tripping # certain security checks in the rest of Jupyter that assumes that empty keys # are insecure. -INPROCESS_KEY = b'inprocess' +INPROCESS_KEY = b"inprocess" diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index bc17236d6..d2b2e8f32 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -3,50 +3,48 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from contextlib import contextmanager import logging import sys +from contextlib import contextmanager from IPython.core.interactiveshell import InteractiveShellABC -from ipykernel.jsonutil import json_clean from traitlets import Any, Enum, Instance, List, Type, default + from ipykernel.ipkernel import IPythonKernel +from ipykernel.jsonutil import json_clean from ipykernel.zmqshell import ZMQInteractiveShell +from ..iostream import BackgroundSocket, IOPubThread, OutStream from .constants import INPROCESS_KEY from .socket import DummySocket -from ..iostream import OutStream, BackgroundSocket, IOPubThread -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Main kernel class -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + class InProcessKernel(IPythonKernel): - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # InProcessKernel interface - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # The frontends connected to this kernel. - frontends = List( - Instance('ipykernel.inprocess.client.InProcessKernelClient', - allow_none=True) - ) + frontends = List(Instance("ipykernel.inprocess.client.InProcessKernelClient", allow_none=True)) # The GUI environment that the kernel is running under. This need not be # specified for the normal operation for the kernel, but is required for # IPython's GUI support (including pylab). The default is 'inline' because # it is safe under all GUI toolkits. - gui = Enum(('tk', 'gtk', 'wx', 'qt', 'qt4', 'inline'), - default_value='inline') + gui = Enum(("tk", "gtk", "wx", "qt", "qt4", "inline"), default_value="inline") raw_input_str = Any() stdout = Any() stderr = Any() - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # Kernel interface - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- shell_class = Type(allow_none=True) _underlying_iopub_socket = Instance(DummySocket, ()) @@ -54,7 +52,7 @@ class InProcessKernel(IPythonKernel): shell_stream = Instance(DummySocket, ()) - @default('iopub_thread') + @default("iopub_thread") def _default_iopub_thread(self): thread = IOPubThread(self._underlying_iopub_socket) thread.start() @@ -62,7 +60,7 @@ def _default_iopub_thread(self): iopub_socket = Instance(BackgroundSocket) - @default('iopub_socket') + @default("iopub_socket") def _default_iopub_socket(self): return self.iopub_thread.background_socket @@ -71,20 +69,20 @@ def _default_iopub_socket(self): def __init__(self, **traits): super().__init__(**traits) - self._underlying_iopub_socket.observe(self._io_dispatch, names=['message_sent']) + self._underlying_iopub_socket.observe(self._io_dispatch, names=["message_sent"]) self.shell.kernel = self async def execute_request(self, stream, ident, parent): - """ Override for temporary IO redirection. """ + """Override for temporary IO redirection.""" with self._redirected_io(): await super().execute_request(stream, ident, parent) def start(self): - """ Override registration of dispatchers for streams. """ + """Override registration of dispatchers for streams.""" self.shell.exit_now = False async def _abort_queues(self): - """ The in-process kernel doesn't abort requests. """ + """The in-process kernel doesn't abort requests.""" pass async def _flush_control_queue(self): @@ -99,79 +97,77 @@ def _input_request(self, prompt, ident, parent, password=False): # Send the input request. content = json_clean(dict(prompt=prompt, password=password)) - msg = self.session.msg('input_request', content, parent) + msg = self.session.msg("input_request", content, parent) for frontend in self.frontends: - if frontend.session.session == parent['header']['session']: + if frontend.session.session == parent["header"]["session"]: frontend.stdin_channel.call_handlers(msg) break else: - logging.error('No frontend found for raw_input request') - return '' + logging.error("No frontend found for raw_input request") + return "" # Await a response. while self.raw_input_str is None: frontend.stdin_channel.process_events() return self.raw_input_str - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # Protected interface - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- @contextmanager def _redirected_io(self): - """ Temporarily redirect IO to the kernel. - """ + """Temporarily redirect IO to the kernel.""" sys_stdout, sys_stderr = sys.stdout, sys.stderr sys.stdout, sys.stderr = self.stdout, self.stderr yield sys.stdout, sys.stderr = sys_stdout, sys_stderr - #------ Trait change handlers -------------------------------------------- + # ------ Trait change handlers -------------------------------------------- def _io_dispatch(self, change): - """ Called when a message is sent to the IO socket. - """ + """Called when a message is sent to the IO socket.""" ident, msg = self.session.recv(self.iopub_socket.io_thread.socket, copy=False) for frontend in self.frontends: frontend.iopub_channel.call_handlers(msg) - #------ Trait initializers ----------------------------------------------- + # ------ Trait initializers ----------------------------------------------- - @default('log') + @default("log") def _default_log(self): return logging.getLogger(__name__) - @default('session') + @default("session") def _default_session(self): from jupyter_client.session import Session + return Session(parent=self, key=INPROCESS_KEY) - @default('shell_class') + @default("shell_class") def _default_shell_class(self): return InProcessInteractiveShell - @default('stdout') + @default("stdout") def _default_stdout(self): - return OutStream(self.session, self.iopub_thread, 'stdout', - watchfd=False) + return OutStream(self.session, self.iopub_thread, "stdout", watchfd=False) - @default('stderr') + @default("stderr") def _default_stderr(self): - return OutStream(self.session, self.iopub_thread, 'stderr', - watchfd=False) + return OutStream(self.session, self.iopub_thread, "stderr", watchfd=False) + -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Interactive shell subclass -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + class InProcessInteractiveShell(ZMQInteractiveShell): - kernel = Instance('ipykernel.inprocess.ipkernel.InProcessKernel', - allow_none=True) + kernel = Instance("ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True) - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # InteractiveShell interface - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def enable_gui(self, gui=None): """Enable GUI integration for the kernel.""" @@ -189,7 +185,7 @@ def enable_pylab(self, gui=None, import_all=True, welcome_message=False): """Activate pylab support at runtime.""" if not gui: gui = self.kernel.gui - return super().enable_pylab(gui, import_all, - welcome_message) + return super().enable_pylab(gui, import_all, welcome_message) + InteractiveShellABC.register(InProcessInteractiveShell) diff --git a/ipykernel/inprocess/manager.py b/ipykernel/inprocess/manager.py index f94c3a62b..fbbd34236 100644 --- a/ipykernel/inprocess/manager.py +++ b/ipykernel/inprocess/manager.py @@ -3,10 +3,10 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from traitlets import Instance, DottedObjectName, default -from jupyter_client.managerabc import KernelManagerABC from jupyter_client.manager import KernelManager +from jupyter_client.managerabc import KernelManagerABC from jupyter_client.session import Session +from traitlets import DottedObjectName, Instance, default from .constants import INPROCESS_KEY @@ -22,27 +22,28 @@ class InProcessKernelManager(KernelManager): """ # The kernel process with which the KernelManager is communicating. - kernel = Instance('ipykernel.inprocess.ipkernel.InProcessKernel', - allow_none=True) + kernel = Instance("ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True) # the client class for KM.client() shortcut - client_class = DottedObjectName('ipykernel.inprocess.BlockingInProcessKernelClient') + client_class = DottedObjectName("ipykernel.inprocess.BlockingInProcessKernelClient") - @default('blocking_class') + @default("blocking_class") def _default_blocking_class(self): from .blocking import BlockingInProcessKernelClient + return BlockingInProcessKernelClient - @default('session') + @default("session") def _default_session(self): # don't sign in-process messages return Session(key=INPROCESS_KEY, parent=self) - #-------------------------------------------------------------------------- + # -------------------------------------------------------------------------- # Kernel management methods - #-------------------------------------------------------------------------- + # -------------------------------------------------------------------------- def start_kernel(self, **kwds): from ipykernel.inprocess.ipkernel import InProcessKernel + self.kernel = InProcessKernel(parent=self, session=self.session) def shutdown_kernel(self): @@ -70,12 +71,12 @@ def is_alive(self): return self.kernel is not None def client(self, **kwargs): - kwargs['kernel'] = self.kernel + kwargs["kernel"] = self.kernel return super().client(**kwargs) -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # ABC Registration -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- KernelManagerABC.register(InProcessKernelManager) diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index ddaad3a38..477c36a47 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -6,25 +6,26 @@ from queue import Queue import zmq - from traitlets import HasTraits, Instance, Int -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Dummy socket class -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + class DummySocket(HasTraits): - """ A dummy socket implementing (part of) the zmq.Socket interface. """ + """A dummy socket implementing (part of) the zmq.Socket interface.""" queue = Instance(Queue, ()) - message_sent = Int(0) # Should be an Event + message_sent = Int(0) # Should be an Event context = Instance(zmq.Context) + def _context_default(self): return zmq.Context() - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- # Socket interface - #------------------------------------------------------------------------- + # ------------------------------------------------------------------------- def recv_multipart(self, flags=0, copy=True, track=False): return self.queue.get_nowait() diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 952c66905..28b176f80 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -1,20 +1,18 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from io import StringIO import sys import unittest +from io import StringIO import pytest - import tornado +from IPython.utils.io import capture_output from ipykernel.inprocess.blocking import BlockingInProcessKernelClient -from ipykernel.inprocess.manager import InProcessKernelManager from ipykernel.inprocess.ipkernel import InProcessKernel +from ipykernel.inprocess.manager import InProcessKernelManager from ipykernel.tests.utils import assemble_output -from IPython.utils.io import capture_output - def _init_asyncio_patch(): @@ -33,8 +31,13 @@ def _init_asyncio_patch(): FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8) and tornado.version_info < (6, 1): + if ( + sys.platform.startswith("win") + and sys.version_info >= (3, 8) + and tornado.version_info < (6, 1) + ): import asyncio + try: from asyncio import ( WindowsProactorEventLoopPolicy, @@ -51,7 +54,6 @@ def _init_asyncio_patch(): class InProcessKernelTestCase(unittest.TestCase): - def setUp(self): _init_asyncio_patch() self.km = InProcessKernelManager() @@ -62,46 +64,39 @@ def setUp(self): def test_pylab(self): """Does %pylab work in the in-process kernel?""" - matplotlib = pytest.importorskip('matplotlib', reason='This test requires matplotlib') + matplotlib = pytest.importorskip("matplotlib", reason="This test requires matplotlib") kc = self.kc - kc.execute('%pylab') + kc.execute("%pylab") out, err = assemble_output(kc.get_iopub_msg) - self.assertIn('matplotlib', out) + self.assertIn("matplotlib", out) def test_raw_input(self): - """ Does the in-process kernel handle raw_input correctly? - """ - io = StringIO('foobar\n') + """Does the in-process kernel handle raw_input correctly?""" + io = StringIO("foobar\n") sys_stdin = sys.stdin sys.stdin = io try: - self.kc.execute('x = input()') + self.kc.execute("x = input()") finally: sys.stdin = sys_stdin - assert self.km.kernel.shell.user_ns.get('x') == 'foobar' + assert self.km.kernel.shell.user_ns.get("x") == "foobar" - @pytest.mark.skipif( - '__pypy__' in sys.builtin_module_names, - reason="fails on pypy" - ) + @pytest.mark.skipif("__pypy__" in sys.builtin_module_names, reason="fails on pypy") def test_stdout(self): - """ Does the in-process kernel correctly capture IO? - """ + """Does the in-process kernel correctly capture IO?""" kernel = InProcessKernel() with capture_output() as io: kernel.shell.run_cell('print("foo")') - assert io.stdout == 'foo\n' + assert io.stdout == "foo\n" kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) kernel.frontends.append(kc) kc.execute('print("bar")') out, err = assemble_output(kc.get_iopub_msg) - assert out == 'bar\n' + assert out == "bar\n" - @pytest.mark.skip( - reason="Currently don't capture during test as pytest does its own capturing" - ) + @pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing") def test_capfd(self): """Does correctly capture fd""" kernel = InProcessKernel() @@ -121,6 +116,6 @@ def test_getpass_stream(self): "Tests that kernel getpass accept the stream parameter" kernel = InProcessKernel() kernel._allow_stdin = True - kernel._input_request = lambda *args, **kwargs : None + kernel._input_request = lambda *args, **kwargs: None - kernel.getpass(stream='non empty') + kernel.getpass(stream="non empty") diff --git a/ipykernel/inprocess/tests/test_kernelmanager.py b/ipykernel/inprocess/tests/test_kernelmanager.py index bd1bb81e2..850f543ce 100644 --- a/ipykernel/inprocess/tests/test_kernelmanager.py +++ b/ipykernel/inprocess/tests/test_kernelmanager.py @@ -5,12 +5,12 @@ from ipykernel.inprocess.manager import InProcessKernelManager -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Test case -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- -class InProcessKernelManagerTestCase(unittest.TestCase): +class InProcessKernelManagerTestCase(unittest.TestCase): def setUp(self): self.km = InProcessKernelManager() @@ -19,8 +19,7 @@ def tearDown(self): self.km.shutdown_kernel() def test_interface(self): - """ Does the in-process kernel manager implement the basic KM interface? - """ + """Does the in-process kernel manager implement the basic KM interface?""" km = self.km assert not km.has_kernel @@ -49,64 +48,59 @@ def test_interface(self): assert not kc.channels_running def test_execute(self): - """ Does executing code in an in-process kernel work? - """ + """Does executing code in an in-process kernel work?""" km = self.km km.start_kernel() kc = km.client() kc.start_channels() kc.wait_for_ready() - kc.execute('foo = 1') - assert km.kernel.shell.user_ns['foo'] == 1 + kc.execute("foo = 1") + assert km.kernel.shell.user_ns["foo"] == 1 def test_complete(self): - """ Does requesting completion from an in-process kernel work? - """ + """Does requesting completion from an in-process kernel work?""" km = self.km km.start_kernel() kc = km.client() kc.start_channels() kc.wait_for_ready() - km.kernel.shell.push({'my_bar': 0, 'my_baz': 1}) - kc.complete('my_ba', 5) + km.kernel.shell.push({"my_bar": 0, "my_baz": 1}) + kc.complete("my_ba", 5) msg = kc.get_shell_msg() - assert msg['header']['msg_type'] == 'complete_reply' - self.assertEqual(sorted(msg['content']['matches']), - ['my_bar', 'my_baz']) + assert msg["header"]["msg_type"] == "complete_reply" + self.assertEqual(sorted(msg["content"]["matches"]), ["my_bar", "my_baz"]) def test_inspect(self): - """ Does requesting object information from an in-process kernel work? - """ + """Does requesting object information from an in-process kernel work?""" km = self.km km.start_kernel() kc = km.client() kc.start_channels() kc.wait_for_ready() - km.kernel.shell.user_ns['foo'] = 1 - kc.inspect('foo') + km.kernel.shell.user_ns["foo"] = 1 + kc.inspect("foo") msg = kc.get_shell_msg() - assert msg['header']['msg_type'] == 'inspect_reply' - content = msg['content'] - assert content['found'] - text = content['data']['text/plain'] - self.assertIn('int', text) + assert msg["header"]["msg_type"] == "inspect_reply" + content = msg["content"] + assert content["found"] + text = content["data"]["text/plain"] + self.assertIn("int", text) def test_history(self): - """ Does requesting history from an in-process kernel work? - """ + """Does requesting history from an in-process kernel work?""" km = self.km km.start_kernel() kc = km.client() kc.start_channels() kc.wait_for_ready() - kc.execute('1') - kc.history(hist_access_type='tail', n=1) + kc.execute("1") + kc.history(hist_access_type="tail", n=1) msg = kc.shell_channel.get_msgs()[-1] - assert msg['header']['msg_type'] == 'history_reply' - history = msg['content']['history'] + assert msg["header"]["msg_type"] == "history_reply" + history = msg["content"]["history"] assert len(history) == 1 - assert history[0][2] == '1' + assert history[0][2] == "1" -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 590513bc3..fa038359b 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -4,39 +4,39 @@ # Distributed under the terms of the Modified BSD License. import atexit -from binascii import b2a_hex -from collections import deque -from imp import lock_held as import_lock_held +import io import os import sys import threading -import warnings -from weakref import WeakSet import traceback +import warnings +from binascii import b2a_hex +from collections import deque +from imp import lock_held as import_lock_held from io import StringIO, TextIOBase -import io +from weakref import WeakSet import zmq + if zmq.pyzmq_version_info() >= (17, 0): from tornado.ioloop import IOLoop else: # deprecated since pyzmq 17 from zmq.eventloop.ioloop import IOLoop -from zmq.eventloop.zmqstream import ZMQStream from jupyter_client.session import extract_header +from zmq.eventloop.zmqstream import ZMQStream - -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Globals -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- MASTER = 0 CHILD = 1 -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # IO classes -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- class IOPubThread: @@ -88,8 +88,8 @@ def _setup_event_pipe(self): pipe_in = ctx.socket(zmq.PULL) pipe_in.linger = 0 - _uuid = b2a_hex(os.urandom(16)).decode('ascii') - iface = self._event_interface = 'inproc://%s' % _uuid + _uuid = b2a_hex(os.urandom(16)).decode("ascii") + iface = self._event_interface = "inproc://%s" % _uuid pipe_in.bind(iface) self._event_puller = ZMQStream(pipe_in, self.io_loop) self._event_puller.on_recv(self._handle_event) @@ -139,8 +139,9 @@ def _setup_pipe_in(self): try: self._pipe_port = pipe_in.bind_to_random_port("tcp://127.0.0.1") except zmq.ZMQError as e: - warnings.warn("Couldn't bind IOPub Pipe to 127.0.0.1: %s" % e + - "\nsubprocess output will be unavailable." + warnings.warn( + "Couldn't bind IOPub Pipe to 127.0.0.1: %s" % e + + "\nsubprocess output will be unavailable." ) self._pipe_flag = False pipe_in.close() @@ -161,7 +162,7 @@ def _setup_pipe_out(self): # must be new context after fork ctx = zmq.Context() pipe_out = ctx.socket(zmq.PUSH) - pipe_out.linger = 3000 # 3s timeout for pipe_out sends before discarding the message + pipe_out.linger = 3000 # 3s timeout for pipe_out sends before discarding the message pipe_out.connect("tcp://127.0.0.1:%i" % self._pipe_port) return ctx, pipe_out @@ -213,7 +214,7 @@ def schedule(self, f): if self.thread.is_alive(): self._events.append(f) # wake event thread (message content is ignored) - self._event_pipe.send(b'') + self._event_pipe.send(b"") else: f() @@ -222,7 +223,7 @@ def send_multipart(self, *args, **kwargs): If my thread isn't running (e.g. forked process), send immediately. """ - self.schedule(lambda : self._really_send(*args, **kwargs)) + self.schedule(lambda: self._really_send(*args, **kwargs)) def _really_send(self, msg, *args, **kwargs): """The callback that actually sends messages""" @@ -243,6 +244,7 @@ def _really_send(self, msg, *args, **kwargs): class BackgroundSocket: """Wrapper around IOPub thread that provides zmq send[_multipart]""" + io_thread = None def __init__(self, io_thread): @@ -250,7 +252,7 @@ def __init__(self, io_thread): def __getattr__(self, attr): """Wrap socket attr access for backward-compatibility""" - if attr.startswith('__') and attr.endswith('__'): + if attr.startswith("__") and attr.endswith("__"): # don't wrap magic methods super().__getattr__(attr) if hasattr(self.io_thread.socket, attr): @@ -265,7 +267,7 @@ def __getattr__(self, attr): super().__getattr__(attr) def __setattr__(self, attr, value): - if attr == 'io_thread' or (attr.startswith('__' and attr.endswith('__'))): + if attr == "io_thread" or (attr.startswith("__" and attr.endswith("__"))): super().__setattr__(attr, value) else: warnings.warn( @@ -297,8 +299,7 @@ class OutStream(TextIOBase): # The time interval between automatic flushes, in seconds. flush_interval = 0.2 topic = None - encoding = 'UTF-8' - + encoding = "UTF-8" def fileno(self): """ @@ -332,7 +333,15 @@ def _watch_pipe_fd(self): self._exc = sys.exc_info() def __init__( - self, session, pub_thread, name, pipe=None, echo=None, *, watchfd=True, isatty=False, + self, + session, + pub_thread, + name, + pipe=None, + echo=None, + *, + watchfd=True, + isatty=False, ): """ Parameters @@ -392,7 +401,7 @@ def __init__( self._setup_stream_redirects(name) if echo: - if hasattr(echo, 'read') and hasattr(echo, 'write'): + if hasattr(echo, "read") and hasattr(echo, "write"): self.echo = echo else: raise ValueError("echo argument must be a file like object") @@ -449,6 +458,7 @@ def _schedule_flush(self): # add_timeout has to be handed to the io thread via event pipe def _schedule_in_thread(): self._io_loop.call_later(self.flush_interval, self._flush) + self.pub_thread.schedule(_schedule_in_thread) def flush(self): @@ -457,10 +467,10 @@ def flush(self): send will happen in the background thread """ if ( - self.pub_thread - and self.pub_thread.thread is not None - and self.pub_thread.thread.is_alive() - and self.pub_thread.thread.ident != threading.current_thread().ident + self.pub_thread + and self.pub_thread.thread is not None + and self.pub_thread.thread.is_alive() + and self.pub_thread.thread.ident != threading.current_thread().ident ): # request flush on the background thread self.pub_thread.schedule(self._flush) @@ -492,8 +502,7 @@ def _flush(self): self.echo.flush() except OSError as e: if self.echo is not sys.__stderr__: - print(f"Flush failed: {e}", - file=sys.__stderr__) + print(f"Flush failed: {e}", file=sys.__stderr__) data = self._flush_buffer() if data: @@ -501,9 +510,14 @@ def _flush(self): # since pub_thread is itself fork-safe. # There should be a better way to do this. self.session.pid = os.getpid() - content = {'name':self.name, 'text':data} - self.session.send(self.pub_thread, 'stream', content=content, - parent=self.parent_header, ident=self.topic) + content = {"name": self.name, "text": data} + self.session.send( + self.pub_thread, + "stream", + content=content, + parent=self.parent_header, + ident=self.topic, + ) def write(self, string: str) -> int: """Write to current stream after encoding if necessary @@ -516,23 +530,20 @@ def write(self, string: str) -> int: """ if not isinstance(string, str): - raise TypeError( - f"write() argument must be str, not {type(string)}" - ) + raise TypeError(f"write() argument must be str, not {type(string)}") if self.echo is not None: try: self.echo.write(string) except OSError as e: if self.echo is not sys.__stderr__: - print(f"Write failed: {e}", - file=sys.__stderr__) + print(f"Write failed: {e}", file=sys.__stderr__) if self.pub_thread is None: - raise ValueError('I/O operation on closed file') + raise ValueError("I/O operation on closed file") else: - is_child = (not self._is_master_process()) + is_child = not self._is_master_process() # only touch the buffer in the IO thread to avoid races with self._buffer_lock: self._buffer.write(string) @@ -551,7 +562,7 @@ def write(self, string: str) -> int: def writelines(self, sequence): if self.pub_thread is None: - raise ValueError('I/O operation on closed file') + raise ValueError("I/O operation on closed file") else: for string in sequence: self.write(string) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 26276ca91..350f7f9fd 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -2,23 +2,23 @@ import asyncio import builtins -from contextlib import contextmanager -from functools import partial import getpass import signal import sys +from contextlib import contextmanager +from functools import partial from IPython.core import release -from IPython.utils.tokenutil import token_at_cursor, line_at_cursor -from traitlets import Instance, Type, Any, List, Bool, observe, observe_compat +from IPython.utils.tokenutil import line_at_cursor, token_at_cursor +from traitlets import Any, Bool, Instance, List, Type, observe, observe_compat from zmq.eventloop.zmqstream import ZMQStream from .comm import CommManager -from .kernelbase import Kernel as KernelBase -from .zmqshell import ZMQInteractiveShell -from .eventloops import _use_appnope from .compiler import XCachingCompiler from .debugger import Debugger, _is_debugpy_available +from .eventloops import _use_appnope +from .kernelbase import Kernel as KernelBase +from .zmqshell import ZMQInteractiveShell try: from IPython.core.interactiveshell import _asyncio_runner @@ -26,42 +26,43 @@ _asyncio_runner = None try: - from IPython.core.completer import ( - rectify_completions as _rectify_completions, - provisionalcompleter as _provisionalcompleter, - ) + from IPython.core.completer import provisionalcompleter as _provisionalcompleter + from IPython.core.completer import rectify_completions as _rectify_completions + _use_experimental_60_completion = True except ImportError: _use_experimental_60_completion = False -_EXPERIMENTAL_KEY_NAME = '_jupyter_types_experimental' +_EXPERIMENTAL_KEY_NAME = "_jupyter_types_experimental" class IPythonKernel(KernelBase): - shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', - allow_none=True) + shell = Instance("IPython.core.interactiveshell.InteractiveShellABC", allow_none=True) shell_class = Type(ZMQInteractiveShell) - use_experimental_completions = Bool(True, + use_experimental_completions = Bool( + True, help="Set this flag to False to deactivate the use of experimental IPython completion APIs.", ).tag(config=True) debugpy_stream = Instance(ZMQStream, allow_none=True) if _is_debugpy_available else None user_module = Any() - @observe('user_module') + + @observe("user_module") @observe_compat def _user_module_changed(self, change): if self.shell is not None: - self.shell.user_module = change['new'] + self.shell.user_module = change["new"] user_ns = Instance(dict, args=None, allow_none=True) - @observe('user_ns') + + @observe("user_ns") @observe_compat def _user_ns_changed(self, change): if self.shell is not None: - self.shell.user_ns = change['new'] + self.shell.user_ns = change["new"] self.shell.init_user_ns() # A reference to the Python builtin 'raw_input' function. @@ -74,90 +75,93 @@ def __init__(self, **kwargs): # Initialize the Debugger if _is_debugpy_available: - self.debugger = Debugger(self.log, - self.debugpy_stream, - self._publish_debug_event, - self.debug_shell_socket, - self.session, - self.debug_just_my_code) + self.debugger = Debugger( + self.log, + self.debugpy_stream, + self._publish_debug_event, + self.debug_shell_socket, + self.session, + self.debug_just_my_code, + ) # Initialize the InteractiveShell subclass - self.shell = self.shell_class.instance(parent=self, - profile_dir = self.profile_dir, - user_module = self.user_module, - user_ns = self.user_ns, - kernel = self, - compiler_class = XCachingCompiler, + self.shell = self.shell_class.instance( + parent=self, + profile_dir=self.profile_dir, + user_module=self.user_module, + user_ns=self.user_ns, + kernel=self, + compiler_class=XCachingCompiler, ) self.shell.displayhook.session = self.session self.shell.displayhook.pub_socket = self.iopub_socket - self.shell.displayhook.topic = self._topic('execute_result') + self.shell.displayhook.topic = self._topic("execute_result") self.shell.display_pub.session = self.session self.shell.display_pub.pub_socket = self.iopub_socket self.comm_manager = CommManager(parent=self, kernel=self) self.shell.configurables.append(self.comm_manager) - comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ] + comm_msg_types = ["comm_open", "comm_msg", "comm_close"] for msg_type in comm_msg_types: self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type) if _use_appnope() and self._darwin_app_nap: # Disable app-nap as the kernel is not a gui but can have guis import appnope + appnope.nope() - help_links = List([ - { - 'text': "Python Reference", - 'url': "https://docs.python.org/%i.%i" % sys.version_info[:2], - }, - { - 'text': "IPython Reference", - 'url': "https://ipython.org/documentation.html", - }, - { - 'text': "NumPy Reference", - 'url': "https://docs.scipy.org/doc/numpy/reference/", - }, - { - 'text': "SciPy Reference", - 'url': "https://docs.scipy.org/doc/scipy/reference/", - }, - { - 'text': "Matplotlib Reference", - 'url': "https://matplotlib.org/contents.html", - }, - { - 'text': "SymPy Reference", - 'url': "http://docs.sympy.org/latest/index.html", - }, - { - 'text': "pandas Reference", - 'url': "https://pandas.pydata.org/pandas-docs/stable/", - }, - ]).tag(config=True) + help_links = List( + [ + { + "text": "Python Reference", + "url": "https://docs.python.org/%i.%i" % sys.version_info[:2], + }, + { + "text": "IPython Reference", + "url": "https://ipython.org/documentation.html", + }, + { + "text": "NumPy Reference", + "url": "https://docs.scipy.org/doc/numpy/reference/", + }, + { + "text": "SciPy Reference", + "url": "https://docs.scipy.org/doc/scipy/reference/", + }, + { + "text": "Matplotlib Reference", + "url": "https://matplotlib.org/contents.html", + }, + { + "text": "SymPy Reference", + "url": "http://docs.sympy.org/latest/index.html", + }, + { + "text": "pandas Reference", + "url": "https://pandas.pydata.org/pandas-docs/stable/", + }, + ] + ).tag(config=True) # Kernel info fields - implementation = 'ipython' + implementation = "ipython" implementation_version = release.version language_info = { - 'name': 'python', - 'version': sys.version.split()[0], - 'mimetype': 'text/x-python', - 'codemirror_mode': { - 'name': 'ipython', - 'version': sys.version_info[0] - }, - 'pygments_lexer': 'ipython%d' % 3, - 'nbconvert_exporter': 'python', - 'file_extension': '.py' + "name": "python", + "version": sys.version.split()[0], + "mimetype": "text/x-python", + "codemirror_mode": {"name": "ipython", "version": sys.version_info[0]}, + "pygments_lexer": "ipython%d" % 3, + "nbconvert_exporter": "python", + "file_extension": ".py", } def dispatch_debugpy(self, msg): if _is_debugpy_available: # The first frame is the socket id, we can drop it - frame = msg[1].bytes.decode('utf-8') + frame = msg[1].bytes.decode("utf-8") self.log.debug("Debugpy received: %s", frame) self.debugger.tcp_client.receive_dap_frame(frame) @@ -177,14 +181,16 @@ def start(self): self.debugpy_stream.on_recv(self.dispatch_debugpy, copy=False) super().start() if self.debugpy_stream: - asyncio.run_coroutine_threadsafe(self.poll_stopped_queue(), self.control_thread.io_loop.asyncio_loop) + asyncio.run_coroutine_threadsafe( + self.poll_stopped_queue(), self.control_thread.io_loop.asyncio_loop + ) - def set_parent(self, ident, parent, channel='shell'): + def set_parent(self, ident, parent, channel="shell"): """Overridden from parent to tell the display hook and output streams about the parent message. """ super().set_parent(ident, parent, channel) - if channel == 'shell': + if channel == "shell": self.shell.set_parent(parent) def init_metadata(self, parent): @@ -195,10 +201,12 @@ def init_metadata(self, parent): md = super().init_metadata(parent) # FIXME: remove deprecated ipyparallel-specific code # This is required for ipyparallel < 5.0 - md.update({ - 'dependencies_met' : True, - 'engine' : self.ident, - }) + md.update( + { + "dependencies_met": True, + "engine": self.ident, + } + ) return md def finish_metadata(self, parent, metadata, reply_content): @@ -209,10 +217,7 @@ def finish_metadata(self, parent, metadata, reply_content): # FIXME: remove deprecated ipyparallel-specific code # This is required by ipyparallel < 5.0 metadata["status"] = reply_content["status"] - if ( - reply_content["status"] == "error" - and reply_content["ename"] == "UnmetDependency" - ): + if reply_content["status"] == "error" and reply_content["ename"] == "UnmetDependency": metadata["dependencies_met"] = False return metadata @@ -268,20 +273,17 @@ def cancel_unless_done(f, _ignored): # when sigint finishes, # abort the coroutine with CancelledError - sigint_future.add_done_callback( - partial(cancel_unless_done, future) - ) + sigint_future.add_done_callback(partial(cancel_unless_done, future)) # when the main future finishes, # stop watching for SIGINT events - future.add_done_callback( - partial(cancel_unless_done, sigint_future) - ) + future.add_done_callback(partial(cancel_unless_done, sigint_future)) def handle_sigint(*args): def set_sigint_result(): if sigint_future.cancelled() or sigint_future.done(): return sigint_future.set_result(1) + # use add_callback for thread safety self.io_loop.add_callback(set_sigint_result) @@ -293,14 +295,15 @@ def set_sigint_result(): # restore the previous sigint handler signal.signal(signal.SIGINT, save_sigint) - async def do_execute(self, code, silent, store_history=True, - user_expressions=None, allow_stdin=False): - shell = self.shell # we'll need this a lot here + async def do_execute( + self, code, silent, store_history=True, user_expressions=None, allow_stdin=False + ): + shell = self.shell # we'll need this a lot here self._forward_input(allow_stdin) reply_content = {} - if hasattr(shell, 'run_cell_async') and hasattr(shell, 'should_run_async'): + if hasattr(shell, "run_cell_async") and hasattr(shell, "should_run_async"): run_cell = shell.run_cell_async should_run_async = shell.should_run_async else: @@ -309,6 +312,7 @@ async def do_execute(self, code, silent, store_history=True, # use blocking run_cell and wrap it in coroutine async def run_cell(*args, **kwargs): return shell.run_cell(*args, **kwargs) + try: # default case: runner is asyncio and asyncio is already running @@ -336,7 +340,7 @@ async def run_cell(*args, **kwargs): store_history=store_history, silent=silent, transformed_cell=transformed_cell, - preprocessing_exc_tuple=preprocessing_exc_tuple + preprocessing_exc_tuple=preprocessing_exc_tuple, ) coro_future = asyncio.ensure_future(coro) @@ -345,9 +349,9 @@ async def run_cell(*args, **kwargs): try: res = await coro_future finally: - shell.events.trigger('post_execute') + shell.events.trigger("post_execute") if not silent: - shell.events.trigger('post_run_cell', res) + shell.events.trigger("post_run_cell", res) else: # runner isn't already running, # make synchronous call, @@ -362,42 +366,42 @@ async def run_cell(*args, **kwargs): err = res.error_in_exec if res.success: - reply_content['status'] = 'ok' + reply_content["status"] = "ok" else: - reply_content['status'] = 'error' - - reply_content.update({ - 'traceback': shell._last_traceback or [], - 'ename': str(type(err).__name__), - 'evalue': str(err), - }) + reply_content["status"] = "error" + + reply_content.update( + { + "traceback": shell._last_traceback or [], + "ename": str(type(err).__name__), + "evalue": str(err), + } + ) # FIXME: deprecated piece for ipyparallel (remove in 5.0): - e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, - method='execute') - reply_content['engine_info'] = e_info - + e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method="execute") + reply_content["engine_info"] = e_info # Return the execution counter so clients can display prompts - reply_content['execution_count'] = shell.execution_count - 1 - - if 'traceback' in reply_content: - self.log.info("Exception in execute request:\n%s", '\n'.join(reply_content['traceback'])) + reply_content["execution_count"] = shell.execution_count - 1 + if "traceback" in reply_content: + self.log.info( + "Exception in execute request:\n%s", "\n".join(reply_content["traceback"]) + ) # At this point, we can tell whether the main code execution succeeded # or not. If it did, we proceed to evaluate user_expressions - if reply_content['status'] == 'ok': - reply_content['user_expressions'] = \ - shell.user_expressions(user_expressions or {}) + if reply_content["status"] == "ok": + reply_content["user_expressions"] = shell.user_expressions(user_expressions or {}) else: # If there was an error, don't even try to compute expressions - reply_content['user_expressions'] = {} + reply_content["user_expressions"] = {} # Payloads should be retrieved regardless of outcome, so we can both # recover partial output (that could have been generated early in a # block, before an error) and always clear the payload system. - reply_content['payload'] = shell.payload_manager.read_payload() + reply_content["payload"] = shell.payload_manager.read_payload() # Be aggressive about clearing the payload because we don't want # it to sit in memory until the next execute_request comes in. shell.payload_manager.clear_payload() @@ -416,12 +420,14 @@ def do_complete(self, code, cursor_pos): line, offset = line_at_cursor(code, cursor_pos) line_cursor = cursor_pos - offset - txt, matches = self.shell.complete('', line, line_cursor) - return {'matches' : matches, - 'cursor_end' : cursor_pos, - 'cursor_start' : cursor_pos - len(txt), - 'metadata' : {}, - 'status' : 'ok'} + txt, matches = self.shell.complete("", line, line_cursor) + return { + "matches": matches, + "cursor_end": cursor_pos, + "cursor_start": cursor_pos - len(txt), + "metadata": {}, + "status": "ok", + } async def do_debug_request(self, msg): if _is_debugpy_available: @@ -439,12 +445,14 @@ def _experimental_do_complete(self, code, cursor_pos): comps = [] for comp in completions: - comps.append(dict( - start=comp.start, - end=comp.end, - text=comp.text, - type=comp.type, - )) + comps.append( + dict( + start=comp.start, + end=comp.end, + text=comp.text, + type=comp.type, + ) + ) if completions: s = completions[0].start @@ -455,18 +463,20 @@ def _experimental_do_complete(self, code, cursor_pos): e = cursor_pos matches = [] - return {'matches': matches, - 'cursor_end': e, - 'cursor_start': s, - 'metadata': {_EXPERIMENTAL_KEY_NAME: comps}, - 'status': 'ok'} + return { + "matches": matches, + "cursor_end": e, + "cursor_start": s, + "metadata": {_EXPERIMENTAL_KEY_NAME: comps}, + "status": "ok", + } def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): name = token_at_cursor(code, cursor_pos) - reply_content = {'status' : 'ok'} - reply_content['data'] = {} - reply_content['metadata'] = {} + reply_content = {"status": "ok"} + reply_content["data"] = {} + reply_content["metadata"] = {} try: if release.version_info >= (8,): # `omit_sections` keyword will be available in IPython 8, see @@ -477,73 +487,84 @@ def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): omit_sections=omit_sections, ) else: - bundle = self.shell.object_inspect_mime( - name, - detail_level=detail_level - ) - reply_content['data'].update(bundle) + bundle = self.shell.object_inspect_mime(name, detail_level=detail_level) + reply_content["data"].update(bundle) if not self.shell.enable_html_pager: - reply_content['data'].pop('text/html') - reply_content['found'] = True + reply_content["data"].pop("text/html") + reply_content["found"] = True except KeyError: - reply_content['found'] = False + reply_content["found"] = False return reply_content - def do_history(self, hist_access_type, output, raw, session=0, start=0, - stop=None, n=None, pattern=None, unique=False): - if hist_access_type == 'tail': - hist = self.shell.history_manager.get_tail(n, raw=raw, output=output, - include_latest=True) + def do_history( + self, + hist_access_type, + output, + raw, + session=0, + start=0, + stop=None, + n=None, + pattern=None, + unique=False, + ): + if hist_access_type == "tail": + hist = self.shell.history_manager.get_tail( + n, raw=raw, output=output, include_latest=True + ) - elif hist_access_type == 'range': - hist = self.shell.history_manager.get_range(session, start, stop, - raw=raw, output=output) + elif hist_access_type == "range": + hist = self.shell.history_manager.get_range( + session, start, stop, raw=raw, output=output + ) - elif hist_access_type == 'search': + elif hist_access_type == "search": hist = self.shell.history_manager.search( - pattern, raw=raw, output=output, n=n, unique=unique) + pattern, raw=raw, output=output, n=n, unique=unique + ) else: hist = [] return { - 'status': 'ok', - 'history' : list(hist), + "status": "ok", + "history": list(hist), } def do_shutdown(self, restart): self.shell.exit_now = True - return dict(status='ok', restart=restart) + return dict(status="ok", restart=restart) def do_is_complete(self, code): - transformer_manager = getattr(self.shell, 'input_transformer_manager', None) + transformer_manager = getattr(self.shell, "input_transformer_manager", None) if transformer_manager is None: # input_splitter attribute is deprecated transformer_manager = self.shell.input_splitter status, indent_spaces = transformer_manager.check_complete(code) - r = {'status': status} - if status == 'incomplete': - r['indent'] = ' ' * indent_spaces + r = {"status": status} + if status == "incomplete": + r["indent"] = " " * indent_spaces return r def do_apply(self, content, bufs, msg_id, reply_metadata): from .serialize import serialize_object, unpack_apply_message + shell = self.shell try: working = shell.user_ns - prefix = "_"+str(msg_id).replace("-","")+"_" + prefix = "_" + str(msg_id).replace("-", "") + "_" - f,args,kwargs = unpack_apply_message(bufs, working, copy=False) + f, args, kwargs = unpack_apply_message(bufs, working, copy=False) - fname = getattr(f, '__name__', 'f') + fname = getattr(f, "__name__", "f") - fname = prefix+"f" - argname = prefix+"args" - kwargname = prefix+"kwargs" - resultname = prefix+"result" + fname = prefix + "f" + argname = prefix + "args" + kwargname = prefix + "kwargs" + resultname = prefix + "result" - ns = { fname : f, argname : args, kwargname : kwargs , resultname : None } + ns = {fname: f, argname: args, kwargname: kwargs, resultname: None} # print ns working.update(ns) code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname) @@ -554,7 +575,8 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): for key in ns: working.pop(key) - result_buf = serialize_object(result, + result_buf = serialize_object( + result, buffer_threshold=self.session.buffer_threshold, item_threshold=self.session.item_threshold, ) @@ -568,29 +590,37 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): "evalue": str(e), } # FIXME: deprecated piece for ipyparallel (remove in 5.0): - e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method='apply') - reply_content['engine_info'] = e_info - - self.send_response(self.iopub_socket, 'error', reply_content, - ident=self._topic('error'), channel='shell') - self.log.info("Exception in apply request:\n%s", '\n'.join(reply_content['traceback'])) + e_info = dict(engine_uuid=self.ident, engine_id=self.int_id, method="apply") + reply_content["engine_info"] = e_info + + self.send_response( + self.iopub_socket, + "error", + reply_content, + ident=self._topic("error"), + channel="shell", + ) + self.log.info("Exception in apply request:\n%s", "\n".join(reply_content["traceback"])) result_buf = [] - reply_content['status'] = 'error' + reply_content["status"] = "error" else: - reply_content = {'status' : 'ok'} + reply_content = {"status": "ok"} return reply_content, result_buf def do_clear(self): self.shell.reset(False) - return dict(status='ok') + return dict(status="ok") # This exists only for backwards compatibility - use IPythonKernel instead + class Kernel(IPythonKernel): def __init__(self, *args, **kwargs): import warnings - warnings.warn('Kernel is a deprecated alias of ipykernel.ipkernel.IPythonKernel', - DeprecationWarning) + + warnings.warn( + "Kernel is a deprecated alias of ipykernel.ipkernel.IPythonKernel", DeprecationWarning + ) super().__init__(*args, **kwargs) diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index a777cc72a..36565a842 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -3,45 +3,48 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from binascii import b2a_base64 import math +import numbers import re import types +from binascii import b2a_base64 from datetime import datetime -import numbers + from jupyter_client._version import version_info as jupyter_client_version -next_attr_name = '__next__' +next_attr_name = "__next__" -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Globals and constants -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # timestamp formats ISO8601 = "%Y-%m-%dT%H:%M:%S.%f" -ISO8601_PAT=re.compile(r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d{1,6})?Z?([\+\-]\d{2}:?\d{2})?$") +ISO8601_PAT = re.compile( + r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(\.\d{1,6})?Z?([\+\-]\d{2}:?\d{2})?$" +) # holy crap, strptime is not threadsafe. # Calling it once at import seems to help. datetime.strptime("1", "%d") -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Classes and functions -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # constants for identifying png/jpeg data -PNG = b'\x89PNG\r\n\x1a\n' +PNG = b"\x89PNG\r\n\x1a\n" # front of PNG base64-encoded -PNG64 = b'iVBORw0KG' -JPEG = b'\xff\xd8' +PNG64 = b"iVBORw0KG" +JPEG = b"\xff\xd8" # front of JPEG base64-encoded -JPEG64 = b'/9' +JPEG64 = b"/9" # constants for identifying gif data -GIF_64 = b'R0lGODdh' -GIF89_64 = b'R0lGODlh' +GIF_64 = b"R0lGODdh" +GIF89_64 = b"R0lGODlh" # front of PDF base64-encoded -PDF64 = b'JVBER' +PDF64 = b"JVBER" JUPYTER_CLIENT_MAJOR_VERSION = jupyter_client_version[0] @@ -126,10 +129,11 @@ def json_clean(obj): if isinstance(obj, bytes): # unanmbiguous binary data is base64-encoded # (this probably should have happened upstream) - return b2a_base64(obj).decode('ascii') + return b2a_base64(obj).decode("ascii") if isinstance(obj, container_to_list) or ( - hasattr(obj, '__iter__') and hasattr(obj, next_attr_name)): + hasattr(obj, "__iter__") and hasattr(obj, next_attr_name) + ): obj = list(obj) if isinstance(obj, list): @@ -142,11 +146,13 @@ def json_clean(obj): nkeys = len(obj) nkeys_collapsed = len(set(map(str, obj))) if nkeys != nkeys_collapsed: - raise ValueError('dict cannot be safely converted to JSON: ' - 'key collision would lead to dropped values') + raise ValueError( + "dict cannot be safely converted to JSON: " + "key collision would lead to dropped values" + ) # If all OK, proceed by making the new dict that will be json-safe out = {} - for k,v in obj.items(): + for k, v in obj.items(): out[str(k)] = json_clean(v) return out if isinstance(obj, datetime): diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index dc33530f8..6eba8f50c 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -4,81 +4,88 @@ # Distributed under the terms of the Modified BSD License. import atexit -import os -import sys import errno +import logging +import os import signal +import sys import traceback -import logging from functools import partial -from io import TextIOWrapper, FileIO +from io import FileIO, TextIOWrapper from logging import StreamHandler -from tornado import ioloop - import zmq -from zmq.eventloop.zmqstream import ZMQStream - from IPython.core.application import ( - BaseIPythonApplication, base_flags, base_aliases, catch_config_error + BaseIPythonApplication, + base_aliases, + base_flags, + catch_config_error, ) from IPython.core.profiledir import ProfileDir -from IPython.core.shellapp import ( - InteractiveShellApp, shell_flags, shell_aliases -) +from IPython.core.shellapp import InteractiveShellApp, shell_aliases, shell_flags +from jupyter_client import write_connection_file +from jupyter_client.connect import ConnectionFileMixin +from jupyter_client.session import Session, session_aliases, session_flags +from jupyter_core.paths import jupyter_runtime_dir +from tornado import ioloop from traitlets import ( - Any, Instance, Dict, Unicode, Integer, Bool, DottedObjectName, Type, default + Any, + Bool, + Dict, + DottedObjectName, + Instance, + Integer, + Type, + Unicode, + default, ) -from traitlets.utils.importstring import import_item from traitlets.utils import filefind -from jupyter_core.paths import jupyter_runtime_dir -from jupyter_client import write_connection_file -from jupyter_client.connect import ConnectionFileMixin +from traitlets.utils.importstring import import_item +from zmq.eventloop.zmqstream import ZMQStream -# local imports -from .iostream import IOPubThread from .control import ControlThread from .heartbeat import Heartbeat + +# local imports +from .iostream import IOPubThread from .ipkernel import IPythonKernel from .parentpoller import ParentPollerUnix, ParentPollerWindows -from jupyter_client.session import ( - Session, session_flags, session_aliases, -) from .zmqshell import ZMQInteractiveShell -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Flags and Aliases -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- kernel_aliases = dict(base_aliases) -kernel_aliases.update({ - 'ip' : 'IPKernelApp.ip', - 'hb' : 'IPKernelApp.hb_port', - 'shell' : 'IPKernelApp.shell_port', - 'iopub' : 'IPKernelApp.iopub_port', - 'stdin' : 'IPKernelApp.stdin_port', - 'control' : 'IPKernelApp.control_port', - 'f' : 'IPKernelApp.connection_file', - 'transport': 'IPKernelApp.transport', -}) +kernel_aliases.update( + { + "ip": "IPKernelApp.ip", + "hb": "IPKernelApp.hb_port", + "shell": "IPKernelApp.shell_port", + "iopub": "IPKernelApp.iopub_port", + "stdin": "IPKernelApp.stdin_port", + "control": "IPKernelApp.control_port", + "f": "IPKernelApp.connection_file", + "transport": "IPKernelApp.transport", + } +) kernel_flags = dict(base_flags) -kernel_flags.update({ - 'no-stdout' : ( - {'IPKernelApp' : {'no_stdout' : True}}, - "redirect stdout to the null device"), - 'no-stderr' : ( - {'IPKernelApp' : {'no_stderr' : True}}, - "redirect stderr to the null device"), - 'pylab' : ( - {'IPKernelApp' : {'pylab' : 'auto'}}, - """Pre-load matplotlib and numpy for interactive use with - the default matplotlib backend."""), - 'trio-loop' : ( - {'InteractiveShell' : {'trio_loop' : False}}, - 'Enable Trio as main event loop.' - ), -}) +kernel_flags.update( + { + "no-stdout": ({"IPKernelApp": {"no_stdout": True}}, "redirect stdout to the null device"), + "no-stderr": ({"IPKernelApp": {"no_stderr": True}}, "redirect stderr to the null device"), + "pylab": ( + {"IPKernelApp": {"pylab": "auto"}}, + """Pre-load matplotlib and numpy for interactive use with + the default matplotlib backend.""", + ), + "trio-loop": ( + {"InteractiveShell": {"trio_loop": False}}, + "Enable Trio as main event loop.", + ), + } +) # inherit flags&aliases for any IPython shell apps kernel_aliases.update(shell_aliases) @@ -98,26 +105,28 @@ """ -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Application class for starting an IPython Kernel -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + -class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, - ConnectionFileMixin): - name='ipython-kernel' +class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMixin): + name = "ipython-kernel" aliases = Dict(kernel_aliases) flags = Dict(kernel_flags) classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session] # the kernel class, as an importstring - kernel_class = Type('ipykernel.ipkernel.IPythonKernel', - klass='ipykernel.kernelbase.Kernel', - help="""The Kernel subclass to be used. + kernel_class = Type( + "ipykernel.ipkernel.IPythonKernel", + klass="ipykernel.kernelbase.Kernel", + help="""The Kernel subclass to be used. This should allow easy re-use of the IPKernelApp entry point to configure and launch kernels other than IPython's own. - """).tag(config=True) + """, + ).tag(config=True) kernel = Any() - poller = Any() # don't restrict this even though current pollers are all Threads + poller = Any() # don't restrict this even though current pollers are all Threads heartbeat = Instance(Heartbeat, allow_none=True) context = Any() @@ -133,16 +142,16 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, _ports = Dict() subcommands = { - 'install': ( - 'ipykernel.kernelspec.InstallIPythonKernelSpecApp', - 'Install the IPython kernel' + "install": ( + "ipykernel.kernelspec.InstallIPythonKernelSpecApp", + "Install the IPython kernel", ), } # connection info: connection_dir = Unicode() - @default('connection_dir') + @default("connection_dir") def _default_connection_dir(self): return jupyter_runtime_dir() @@ -158,10 +167,12 @@ def abs_connection_file(self): no_stderr = Bool(False, help="redirect stderr to the null device").tag(config=True) trio_loop = Bool(False, help="Set main event loop.").tag(config=True) quiet = Bool(True, help="Only send stdout/stderr to output stream").tag(config=True) - outstream_class = DottedObjectName('ipykernel.iostream.OutStream', - help="The importstring for the OutStream factory").tag(config=True) - displayhook_class = DottedObjectName('ipykernel.displayhook.ZMQDisplayHook', - help="The importstring for the DisplayHook factory").tag(config=True) + outstream_class = DottedObjectName( + "ipykernel.iostream.OutStream", help="The importstring for the OutStream factory" + ).tag(config=True) + displayhook_class = DottedObjectName( + "ipykernel.displayhook.ZMQDisplayHook", help="The importstring for the DisplayHook factory" + ).tag(config=True) capture_fd_output = Bool( True, @@ -170,14 +181,18 @@ def abs_connection_file(self): ).tag(config=True) # polling - parent_handle = Integer(int(os.environ.get('JPY_PARENT_PID') or 0), + parent_handle = Integer( + int(os.environ.get("JPY_PARENT_PID") or 0), help="""kill this process if its parent dies. On Windows, the argument specifies the HANDLE of the parent process, otherwise it is simply boolean. - """).tag(config=True) - interrupt = Integer(int(os.environ.get('JPY_INTERRUPT_EVENT') or 0), + """, + ).tag(config=True) + interrupt = Integer( + int(os.environ.get("JPY_INTERRUPT_EVENT") or 0), help="""ONLY USED ON WINDOWS Interrupt this process when the parent is signaled. - """).tag(config=True) + """, + ).tag(config=True) def init_crash_handler(self): sys.excepthook = self.excepthook @@ -187,7 +202,7 @@ def excepthook(self, etype, evalue, tb): traceback.print_exception(etype, evalue, tb, file=sys.__stderr__) def init_poller(self): - if sys.platform == 'win32': + if sys.platform == "win32": if self.interrupt or self.parent_handle: self.poller = ParentPollerWindows(self.interrupt, self.parent_handle) elif self.parent_handle and self.parent_handle != 1: @@ -197,13 +212,13 @@ def init_poller(self): self.poller = ParentPollerUnix() def _try_bind_socket(self, s, port): - iface = '%s://%s' % (self.transport, self.ip) - if self.transport == 'tcp': + iface = "%s://%s" % (self.transport, self.ip) + if self.transport == "tcp": if port <= 0: port = s.bind_to_random_port(iface) else: s.bind("tcp://%s:%i" % (self.ip, port)) - elif self.transport == 'ipc': + elif self.transport == "ipc": if port <= 0: port = 1 path = "%s-%i" % (self.ip, port) @@ -238,9 +253,17 @@ def write_connection_file(self): """write connection info to JSON file""" cf = self.abs_connection_file self.log.debug("Writing connection file: %s", cf) - write_connection_file(cf, ip=self.ip, key=self.session.key, transport=self.transport, - shell_port=self.shell_port, stdin_port=self.stdin_port, hb_port=self.hb_port, - iopub_port=self.iopub_port, control_port=self.control_port) + write_connection_file( + cf, + ip=self.ip, + key=self.session.key, + transport=self.transport, + shell_port=self.shell_port, + stdin_port=self.stdin_port, + hb_port=self.hb_port, + iopub_port=self.iopub_port, + control_port=self.control_port, + ) def cleanup_connection_file(self): cf = self.abs_connection_file @@ -254,9 +277,9 @@ def cleanup_connection_file(self): def init_connection_file(self): if not self.connection_file: - self.connection_file = "kernel-%s.json"%os.getpid() + self.connection_file = "kernel-%s.json" % os.getpid() try: - self.connection_file = filefind(self.connection_file, ['.', self.connection_dir]) + self.connection_file = filefind(self.connection_file, [".", self.connection_dir]) except OSError: self.log.debug("Connection file not found: %s", self.connection_file) # This means I own it, and I'll create it in this directory: @@ -267,7 +290,9 @@ def init_connection_file(self): try: self.load_connection_file() except Exception: - self.log.error("Failed to load connection file: %r", self.connection_file, exc_info=True) + self.log.error( + "Failed to load connection file: %r", self.connection_file, exc_info=True + ) self.exit(1) def init_sockets(self): @@ -287,12 +312,11 @@ def init_sockets(self): self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port) self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port) - if hasattr(zmq, 'ROUTER_HANDOVER'): + if hasattr(zmq, "ROUTER_HANDOVER"): # set router-handover to workaround zeromq reconnect problems # in certain rare circumstances # see ipython/ipykernel#270 and zeromq/libzmq#2892 - self.shell_socket.router_handover = \ - self.stdin_socket.router_handover = 1 + self.shell_socket.router_handover = self.stdin_socket.router_handover = 1 self.init_control(context) self.init_iopub(context) @@ -311,7 +335,7 @@ def init_control(self, context): if self.shell_socket.getsockopt(zmq.LAST_ENDPOINT): self.debug_shell_socket.connect(self.shell_socket.getsockopt(zmq.LAST_ENDPOINT)) - if hasattr(zmq, 'ROUTER_HANDOVER'): + if hasattr(zmq, "ROUTER_HANDOVER"): # set router-handover to workaround zeromq reconnect problems # in certain rare circumstances # see ipython/ipykernel#270 and zeromq/libzmq#2892 @@ -362,7 +386,7 @@ def close(self): if self.debug_shell_socket and not self.debug_shell_socket.closed: self.debug_shell_socket.close() - for channel in ('shell', 'control', 'stdin'): + for channel in ("shell", "control", "stdin"): self.log.debug("Closing %s channel", channel) socket = getattr(self, channel + "_socket", None) if socket and not socket.closed: @@ -374,8 +398,10 @@ def close(self): def log_connection_info(self): """display connection info, and store ports""" basename = os.path.basename(self.connection_file) - if basename == self.connection_file or \ - os.path.dirname(self.connection_file) == self.connection_dir: + if ( + basename == self.connection_file + or os.path.dirname(self.connection_file) == self.connection_dir + ): # use shortname tail = basename else: @@ -397,14 +423,18 @@ def log_connection_info(self): for line in lines: print(line, file=sys.__stdout__) - self._ports = dict(shell=self.shell_port, iopub=self.iopub_port, - stdin=self.stdin_port, hb=self.hb_port, - control=self.control_port) + self._ports = dict( + shell=self.shell_port, + iopub=self.iopub_port, + stdin=self.stdin_port, + hb=self.hb_port, + control=self.control_port, + ) def init_blackhole(self): """redirects stdout/stderr to devnull if necessary""" if self.no_stdout or self.no_stderr: - blackhole = open(os.devnull, 'w') + blackhole = open(os.devnull, "w") if self.no_stdout: sys.stdout = sys.__stdout__ = blackhole if self.no_stderr: @@ -423,23 +453,15 @@ def init_io(self): if not self.capture_fd_output: outstream_factory = partial(outstream_factory, watchfd=False) - sys.stdout = outstream_factory(self.session, self.iopub_thread, - 'stdout', - echo=e_stdout) + sys.stdout = outstream_factory(self.session, self.iopub_thread, "stdout", echo=e_stdout) if sys.stderr is not None: sys.stderr.flush() - sys.stderr = outstream_factory( - self.session, self.iopub_thread, "stderr", echo=e_stderr - ) + sys.stderr = outstream_factory(self.session, self.iopub_thread, "stderr", echo=e_stderr) if hasattr(sys.stderr, "_original_stdstream_copy"): for handler in self.log.handlers: - if isinstance(handler, StreamHandler) and ( - handler.stream.buffer.fileno() == 2 - ): - self.log.debug( - "Seeing logger to stderr, rerouting to raw filedescriptor." - ) + if isinstance(handler, StreamHandler) and (handler.stream.buffer.fileno() == 2): + self.log.debug("Seeing logger to stderr, rerouting to raw filedescriptor.") handler.stream = TextIOWrapper( FileIO(sys.stderr._original_stdstream_copy, "w") @@ -473,16 +495,20 @@ def patch_io(self): # change default file to __stderr__ from forwarded stderr faulthandler_enable = faulthandler.enable + def enable(file=sys.__stderr__, all_threads=True, **kwargs): return faulthandler_enable(file=file, all_threads=all_threads, **kwargs) faulthandler.enable = enable - if hasattr(faulthandler, 'register'): + if hasattr(faulthandler, "register"): faulthandler_register = faulthandler.register + def register(signum, file=sys.__stderr__, all_threads=True, chain=False, **kwargs): - return faulthandler_register(signum, file=file, all_threads=all_threads, - chain=chain, **kwargs) + return faulthandler_register( + signum, file=file, all_threads=all_threads, chain=chain, **kwargs + ) + faulthandler.register = register def init_signal(self): @@ -496,22 +522,22 @@ def init_kernel(self): self.control_thread.start() kernel_factory = self.kernel_class.instance - kernel = kernel_factory(parent=self, session=self.session, - control_stream=control_stream, - debugpy_stream=debugpy_stream, - debug_shell_socket=self.debug_shell_socket, - shell_stream=shell_stream, - control_thread=self.control_thread, - iopub_thread=self.iopub_thread, - iopub_socket=self.iopub_socket, - stdin_socket=self.stdin_socket, - log=self.log, - profile_dir=self.profile_dir, - user_ns=self.user_ns, + kernel = kernel_factory( + parent=self, + session=self.session, + control_stream=control_stream, + debugpy_stream=debugpy_stream, + debug_shell_socket=self.debug_shell_socket, + shell_stream=shell_stream, + control_thread=self.control_thread, + iopub_thread=self.iopub_thread, + iopub_socket=self.iopub_socket, + stdin_socket=self.stdin_socket, + log=self.log, + profile_dir=self.profile_dir, + user_ns=self.user_ns, ) - kernel.record_ports({ - name + '_port': port for name, port in self._ports.items() - }) + kernel.record_ports({name + "_port": port for name, port in self._ports.items()}) self.kernel = kernel # Allow the displayhook to get the execution count @@ -524,8 +550,8 @@ def init_gui_pylab(self): # this is higher priority than matplotlibrc, # but lower priority than anything else (mpl.use() for instance). # This only affects matplotlib >= 1.5 - if not os.environ.get('MPLBACKEND'): - os.environ['MPLBACKEND'] = 'module://matplotlib_inline.backend_inline' + if not os.environ.get("MPLBACKEND"): + os.environ["MPLBACKEND"] = "module://matplotlib_inline.backend_inline" # Provide a wrapper for :meth:`InteractiveShellApp.init_gui_pylab` # to ensure that any exception is printed straight to stderr. @@ -538,28 +564,28 @@ def init_gui_pylab(self): try: # replace error-sending traceback with stderr def print_tb(etype, evalue, stb): - print ("GUI event loop or pylab initialization failed", - file=sys.stderr) - print (shell.InteractiveTB.stb2text(stb), file=sys.stderr) + print("GUI event loop or pylab initialization failed", file=sys.stderr) + print(shell.InteractiveTB.stb2text(stb), file=sys.stderr) + shell._showtraceback = print_tb InteractiveShellApp.init_gui_pylab(self) finally: shell._showtraceback = _showtraceback def init_shell(self): - self.shell = getattr(self.kernel, 'shell', None) + self.shell = getattr(self.kernel, "shell", None) if self.shell: self.shell.configurables.append(self) def configure_tornado_logger(self): - """ Configure the tornado logging.Logger. + """Configure the tornado logging.Logger. Must set up the tornado logger or else tornado will call basicConfig for the root logger which makes the root logger go to the real sys.stderr instead of the capture streams. This function mimics the setup of logging.basicConfig. """ - logger = logging.getLogger('tornado') + logger = logging.getLogger("tornado") handler = logging.StreamHandler() formatter = logging.Formatter(logging.BASIC_FORMAT) handler.setFormatter(formatter) @@ -590,6 +616,7 @@ def _init_asyncio_patch(self): """ if sys.platform.startswith("win") and sys.version_info >= (3, 8): import asyncio + try: from asyncio import ( WindowsProactorEventLoopPolicy, @@ -611,7 +638,9 @@ def init_pdb(self): non-recoverable state. """ import pdb + from IPython.core import debugger + if hasattr(debugger, "InterruptiblePdb"): # Only available in newer IPython releases: debugger.Pdb = debugger.InterruptiblePdb @@ -666,6 +695,7 @@ def start(self): self.io_loop = ioloop.IOLoop.current() if self.trio_loop: from ipykernel.trio_runner import TrioRunner + tr = TrioRunner() tr.initialize(self.kernel, self.io_loop) try: @@ -689,5 +719,5 @@ def main(): app.start() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index c775398fc..12a3d93b0 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -24,8 +24,6 @@ SIGKILL = "windown-SIGKILL-sentinel" - - try: # jupyter_client >= 5, use tz-aware now from jupyter_client.session import utcnow as now @@ -39,8 +37,19 @@ from jupyter_client.session import Session from tornado import ioloop from tornado.queues import Queue, QueueEmpty -from traitlets import (Any, Bool, Dict, Float, Instance, Integer, List, Set, - Unicode, default, observe) +from traitlets import ( + Any, + Bool, + Dict, + Float, + Instance, + Integer, + List, + Set, + Unicode, + default, + observe, +) from traitlets.config.configurable import SingletonConfigurable from zmq.eventloop.zmqstream import ZMQStream @@ -51,14 +60,14 @@ class Kernel(SingletonConfigurable): - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- # Kernel interface - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- # attribute to override with a GUI eventloop = Any(None) - @observe('eventloop') + @observe("eventloop") def _update_eventloop(self, change): """schedule call to eventloop from IOLoop""" loop = ioloop.IOLoop.current() @@ -66,7 +75,7 @@ def _update_eventloop(self, change): loop.add_callback(self.enter_eventloop) session = Instance(Session, allow_none=True) - profile_dir = Instance('IPython.core.profiledir.ProfileDir', allow_none=True) + profile_dir = Instance("IPython.core.profiledir.ProfileDir", allow_none=True) shell_stream = Instance(ZMQStream, allow_none=True) shell_streams = List( @@ -119,7 +128,7 @@ def _shell_streams_changed(self, change): int_id = Integer(-1) ident = Unicode() - @default('ident') + @default("ident") def _default_ident(self): return str(uuid.uuid4()) @@ -133,25 +142,27 @@ def _default_ident(self): # Experimental option to break in non-user code. # The ipykernel source is in the call stack, so the user # has to manipulate the step-over and step-into in a wize way. - debug_just_my_code = Bool(True, + debug_just_my_code = Bool( + True, help="""Set to False if you want to debug python standard and dependent libraries. - """ + """, ).tag(config=True) # track associations with current request # Private interface - _darwin_app_nap = Bool(True, + _darwin_app_nap = Bool( + True, help="""Whether to use appnope for compatibility with OS X App Nap. Only affects OS X >= 10.9. - """ + """, ).tag(config=True) # track associations with current request _allow_stdin = Bool(False) _parents = Dict({"shell": {}, "control": {}}) - _parent_ident = Dict({'shell': b'', 'control': b''}) + _parent_ident = Dict({"shell": b"", "control": b""}) @property def _parent_header(self): @@ -189,7 +200,7 @@ def _parent_header(self): causing significant delays, which can manifest as e.g. "Run all" in a notebook aborting some, but not all, messages after an error. - """ + """, ) # If the shutdown was requested over the network, we leave here the @@ -211,16 +222,26 @@ def _parent_header(self): execution_count = 0 msg_types = [ - 'execute_request', 'complete_request', - 'inspect_request', 'history_request', - 'comm_info_request', 'kernel_info_request', - 'connect_request', 'shutdown_request', - 'is_complete_request', 'interrupt_request', + "execute_request", + "complete_request", + "inspect_request", + "history_request", + "comm_info_request", + "kernel_info_request", + "connect_request", + "shutdown_request", + "is_complete_request", + "interrupt_request", # deprecated: - 'apply_request', + "apply_request", ] # add deprecated ipyparallel control messages - control_msg_types = msg_types + ['clear_request', 'abort_request', 'debug_request', 'usage_request'] + control_msg_types = msg_types + [ + "clear_request", + "abort_request", + "debug_request", + "usage_request", + ] def __init__(self, **kwargs): super().__init__(**kwargs) @@ -281,11 +302,11 @@ async def process_control(self, msg): self.log.debug("Control received: %s", msg) # Set the parent message for side effects. - self.set_parent(idents, msg, channel='control') - self._publish_status('busy', 'control') + self.set_parent(idents, msg, channel="control") + self._publish_status("busy", "control") - header = msg['header'] - msg_type = header['msg_type'] + header = msg["header"] + msg_type = header["msg_type"] handler = self.control_handlers.get(msg_type, None) if handler is None: @@ -300,7 +321,7 @@ async def process_control(self, msg): sys.stdout.flush() sys.stderr.flush() - self._publish_status('idle', 'control') + self._publish_status("idle", "control") # flush to ensure reply is sent self.control_stream.flush(zmq.POLLOUT) @@ -309,7 +330,7 @@ def should_handle(self, stream, msg, idents): Allows subclasses to prevent handling of certain messages (e.g. aborted requests). """ - msg_id = msg['header']['msg_id'] + msg_id = msg["header"]["msg_id"] if msg_id in self.aborted: # is it safe to assume a msg_id will not be resubmitted? self.aborted.remove(msg_id) @@ -331,15 +352,15 @@ async def dispatch_shell(self, msg): return # Set the parent message for side effects. - self.set_parent(idents, msg, channel='shell') - self._publish_status('busy', 'shell') + self.set_parent(idents, msg, channel="shell") + self._publish_status("busy", "shell") - msg_type = msg['header']['msg_type'] + msg_type = msg["header"]["msg_type"] # Only abort execute requests - if self._aborting and msg_type == 'execute_request': + if self._aborting and msg_type == "execute_request": self._send_abort_reply(self.shell_stream, msg, idents) - self._publish_status('idle', 'shell') + self._publish_status("idle", "shell") # flush to ensure reply is sent before # handling the next request self.shell_stream.flush(zmq.POLLOUT) @@ -348,8 +369,8 @@ async def dispatch_shell(self, msg): # Print some info about this message and leave a '--->' marker, so it's # easier to trace visually the message chain when debugging. Each # handler prints its message at the end. - self.log.debug('\n*** MESSAGE TYPE:%s***', msg_type) - self.log.debug(' Content: %s\n --->\n ', msg['content']) + self.log.debug("\n*** MESSAGE TYPE:%s***", msg_type) + self.log.debug(" Content: %s\n --->\n ", msg["content"]) if not self.should_handle(self.shell_stream, msg, idents): return @@ -380,7 +401,7 @@ async def dispatch_shell(self, msg): sys.stdout.flush() sys.stderr.flush() - self._publish_status('idle', 'shell') + self._publish_status("idle", "shell") # flush to ensure reply is sent before # handling the next request self.shell_stream.flush(zmq.POLLOUT) @@ -478,7 +499,8 @@ async def dispatch_queue(self): help="""Monotonic counter of messages """, ) - @default('_message_counter') + + @default("_message_counter") def _message_counter_default(self): return itertools.count() @@ -520,8 +542,7 @@ def start(self): ) # publish idle status - self._publish_status('starting', 'shell') - + self._publish_status("starting", "shell") def record_ports(self, ports): """Record the ports that this kernel is using. @@ -531,16 +552,19 @@ def record_ports(self, ports): """ self._recorded_ports = ports - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- # Kernel request handlers - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- def _publish_execute_input(self, code, parent, execution_count): """Publish the code request on the iopub stream.""" - self.session.send(self.iopub_socket, 'execute_input', - {'code':code, 'execution_count': execution_count}, - parent=parent, ident=self._topic('execute_input') + self.session.send( + self.iopub_socket, + "execute_input", + {"code": code, "execution_count": execution_count}, + parent=parent, + ident=self._topic("execute_input"), ) def _publish_status(self, status, channel, parent=None): @@ -562,7 +586,7 @@ def _publish_debug_event(self, event): ident=self._topic("debug_event"), ) - def set_parent(self, ident, parent, channel='shell'): + def set_parent(self, ident, parent, channel="shell"): """Set the current parent request Side effects (IOPub messages) and replies are associated with @@ -591,8 +615,18 @@ def get_parent(self, channel="shell"): """ return self._parents.get(channel, {}) - def send_response(self, stream, msg_or_type, content=None, ident=None, - buffers=None, track=False, header=None, metadata=None, channel='shell'): + def send_response( + self, + stream, + msg_or_type, + content=None, + ident=None, + buffers=None, + track=False, + header=None, + metadata=None, + channel="shell", + ): """Send a response to the message we're currently processing. This accepts all the parameters of :meth:`jupyter_client.session.Session.send` @@ -621,7 +655,7 @@ def init_metadata(self, parent): # FIXME: `started` is part of ipyparallel # Remove for ipykernel 5.0 return { - 'started': now(), + "started": now(), } def finish_metadata(self, parent, metadata, reply_content): @@ -635,18 +669,18 @@ async def execute_request(self, stream, ident, parent): """handle an execute_request""" try: - content = parent['content'] - code = content['code'] - silent = content['silent'] - store_history = content.get('store_history', not silent) - user_expressions = content.get('user_expressions', {}) - allow_stdin = content.get('allow_stdin', False) + content = parent["content"] + code = content["code"] + silent = content["silent"] + store_history = content.get("store_history", not silent) + user_expressions = content.get("user_expressions", {}) + allow_stdin = content.get("allow_stdin", False) except Exception: self.log.error("Got bad msg: ") self.log.error("%s", parent) return - stop_on_error = content.get('stop_on_error', True) + stop_on_error = content.get("stop_on_error", True) metadata = self.init_metadata(parent) @@ -657,8 +691,11 @@ async def execute_request(self, stream, ident, parent): self._publish_execute_input(code, parent, self.execution_count) reply_content = self.do_execute( - code, silent, store_history, - user_expressions, allow_stdin, + code, + silent, + store_history, + user_expressions, + allow_stdin, ) if inspect.isawaitable(reply_content): reply_content = await reply_content @@ -676,25 +713,25 @@ async def execute_request(self, stream, ident, parent): reply_content = json_clean(reply_content) metadata = self.finish_metadata(parent, metadata, reply_content) - reply_msg = self.session.send(stream, 'execute_reply', - reply_content, parent, metadata=metadata, - ident=ident) + reply_msg = self.session.send( + stream, "execute_reply", reply_content, parent, metadata=metadata, ident=ident + ) self.log.debug("%s", reply_msg) - if not silent and reply_msg['content']['status'] == 'error' and stop_on_error: + if not silent and reply_msg["content"]["status"] == "error" and stop_on_error: self._abort_queues() - def do_execute(self, code, silent, store_history=True, - user_expressions=None, allow_stdin=False): - """Execute user code. Must be overridden by subclasses. - """ + def do_execute( + self, code, silent, store_history=True, user_expressions=None, allow_stdin=False + ): + """Execute user code. Must be overridden by subclasses.""" raise NotImplementedError async def complete_request(self, stream, ident, parent): - content = parent['content'] - code = content['code'] - cursor_pos = content['cursor_pos'] + content = parent["content"] + code = content["code"] + cursor_pos = content["cursor_pos"] matches = self.do_complete(code, cursor_pos) if inspect.isawaitable(matches): @@ -704,88 +741,94 @@ async def complete_request(self, stream, ident, parent): self.session.send(stream, "complete_reply", matches, parent, ident) def do_complete(self, code, cursor_pos): - """Override in subclasses to find completions. - """ - return {'matches' : [], - 'cursor_end' : cursor_pos, - 'cursor_start' : cursor_pos, - 'metadata' : {}, - 'status' : 'ok'} + """Override in subclasses to find completions.""" + return { + "matches": [], + "cursor_end": cursor_pos, + "cursor_start": cursor_pos, + "metadata": {}, + "status": "ok", + } async def inspect_request(self, stream, ident, parent): - content = parent['content'] + content = parent["content"] reply_content = self.do_inspect( - content['code'], content['cursor_pos'], - content.get('detail_level', 0), - set(content.get('omit_sections', [])), + content["code"], + content["cursor_pos"], + content.get("detail_level", 0), + set(content.get("omit_sections", [])), ) if inspect.isawaitable(reply_content): reply_content = await reply_content # Before we send this object over, we scrub it for JSON usage reply_content = json_clean(reply_content) - msg = self.session.send(stream, 'inspect_reply', - reply_content, parent, ident) + msg = self.session.send(stream, "inspect_reply", reply_content, parent, ident) self.log.debug("%s", msg) def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): - """Override in subclasses to allow introspection. - """ - return {'status': 'ok', 'data': {}, 'metadata': {}, 'found': False} + """Override in subclasses to allow introspection.""" + return {"status": "ok", "data": {}, "metadata": {}, "found": False} async def history_request(self, stream, ident, parent): - content = parent['content'] + content = parent["content"] reply_content = self.do_history(**content) if inspect.isawaitable(reply_content): reply_content = await reply_content reply_content = json_clean(reply_content) - msg = self.session.send(stream, 'history_reply', - reply_content, parent, ident) + msg = self.session.send(stream, "history_reply", reply_content, parent, ident) self.log.debug("%s", msg) - def do_history(self, hist_access_type, output, raw, session=None, start=None, - stop=None, n=None, pattern=None, unique=False): - """Override in subclasses to access history. - """ - return {'status': 'ok', 'history': []} + def do_history( + self, + hist_access_type, + output, + raw, + session=None, + start=None, + stop=None, + n=None, + pattern=None, + unique=False, + ): + """Override in subclasses to access history.""" + return {"status": "ok", "history": []} async def connect_request(self, stream, ident, parent): if self._recorded_ports is not None: content = self._recorded_ports.copy() else: content = {} - content['status'] = 'ok' - msg = self.session.send(stream, 'connect_reply', - content, parent, ident) + content["status"] = "ok" + msg = self.session.send(stream, "connect_reply", content, parent, ident) self.log.debug("%s", msg) @property def kernel_info(self): return { - 'protocol_version': kernel_protocol_version, - 'implementation': self.implementation, - 'implementation_version': self.implementation_version, - 'language_info': self.language_info, - 'banner': self.banner, - 'help_links': self.help_links, + "protocol_version": kernel_protocol_version, + "implementation": self.implementation, + "implementation_version": self.implementation_version, + "language_info": self.language_info, + "banner": self.banner, + "help_links": self.help_links, } async def kernel_info_request(self, stream, ident, parent): - content = {'status': 'ok'} + content = {"status": "ok"} content.update(self.kernel_info) - msg = self.session.send(stream, 'kernel_info_reply', - content, parent, ident) + msg = self.session.send(stream, "kernel_info_reply", content, parent, ident) self.log.debug("%s", msg) async def comm_info_request(self, stream, ident, parent): - content = parent['content'] - target_name = content.get('target_name', None) + content = parent["content"] + target_name = content.get("target_name", None) # Should this be moved to ipkernel? - if hasattr(self, 'comm_manager'): + if hasattr(self, "comm_manager"): comms = { k: dict(target_name=v.target_name) for (k, v) in self.comm_manager.comms.items() @@ -793,9 +836,8 @@ async def comm_info_request(self, stream, ident, parent): } else: comms = {} - reply_content = dict(comms=comms, status='ok') - msg = self.session.send(stream, 'comm_info_reply', - reply_content, parent, ident) + reply_content = dict(comms=comms, status="ok") + msg = self.session.send(stream, "comm_info_reply", reply_content, parent, ident) self.log.debug("%s", msg) def _send_interupt_children(self): @@ -819,27 +861,25 @@ def _send_interupt_children(self): async def interrupt_request(self, stream, ident, parent): self._send_interupt_children() - content = parent['content'] - self.session.send(stream, 'interrupt_reply', content, parent, ident=ident) + content = parent["content"] + self.session.send(stream, "interrupt_reply", content, parent, ident=ident) return async def shutdown_request(self, stream, ident, parent): - content = self.do_shutdown(parent['content']['restart']) + content = self.do_shutdown(parent["content"]["restart"]) if inspect.isawaitable(content): content = await content - self.session.send(stream, 'shutdown_reply', content, parent, ident=ident) + self.session.send(stream, "shutdown_reply", content, parent, ident=ident) # same content, but different msg_id for broadcasting on IOPub - self._shutdown_message = self.session.msg('shutdown_reply', - content, parent - ) + self._shutdown_message = self.session.msg("shutdown_reply", content, parent) await self._at_shutdown() - self.log.debug('Stopping control ioloop') + self.log.debug("Stopping control ioloop") control_io_loop = self.control_stream.io_loop control_io_loop.add_callback(control_io_loop.stop) - self.log.debug('Stopping shell ioloop') + self.log.debug("Stopping shell ioloop") shell_io_loop = self.shell_stream.io_loop shell_io_loop.add_callback(shell_io_loop.stop) @@ -847,34 +887,31 @@ def do_shutdown(self, restart): """Override in subclasses to do things when the frontend shuts down the kernel. """ - return {'status': 'ok', 'restart': restart} + return {"status": "ok", "restart": restart} async def is_complete_request(self, stream, ident, parent): - content = parent['content'] - code = content['code'] + content = parent["content"] + code = content["code"] reply_content = self.do_is_complete(code) if inspect.isawaitable(reply_content): reply_content = await reply_content reply_content = json_clean(reply_content) - reply_msg = self.session.send(stream, 'is_complete_reply', - reply_content, parent, ident) + reply_msg = self.session.send(stream, "is_complete_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) def do_is_complete(self, code): - """Override in subclasses to find completions. - """ - return { 'status' : 'unknown'} + """Override in subclasses to find completions.""" + return {"status": "unknown"} async def debug_request(self, stream, ident, parent): - content = parent['content'] + content = parent["content"] reply_content = self.do_debug_request(content) if inspect.isawaitable(reply_content): reply_content = await reply_content reply_content = json_clean(reply_content) - reply_msg = self.session.send(stream, 'debug_reply', reply_content, - parent, ident) + reply_msg = self.session.send(stream, "debug_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) # Taken from https://github.com/jupyter-server/jupyter-resource-usage/blob/e6ec53fa69fdb6de8e878974bcff006310658408/jupyter_resource_usage/metrics.py#L16 @@ -892,37 +929,38 @@ def get_process_metric_value(self, process, name, attribute=None): return None async def usage_request(self, stream, ident, parent): - reply_content = { - 'hostname': socket.gethostname() - } + reply_content = {"hostname": socket.gethostname()} current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) process_metric_value = self.get_process_metric_value - reply_content['kernel_cpu'] = sum([process_metric_value(process, 'cpu_percent', None) for process in all_processes]) - reply_content['kernel_memory'] = sum([process_metric_value(process, 'memory_info', 'rss') for process in all_processes]) + reply_content["kernel_cpu"] = sum( + [process_metric_value(process, "cpu_percent", None) for process in all_processes] + ) + reply_content["kernel_memory"] = sum( + [process_metric_value(process, "memory_info", "rss") for process in all_processes] + ) cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. if cpu_percent != None and cpu_percent != 0.0: - reply_content['host_cpu_percent'] = cpu_percent - reply_content['host_virtual_memory'] = dict(psutil.virtual_memory()._asdict()) - reply_msg = self.session.send(stream, 'usage_reply', reply_content, - parent, ident) + reply_content["host_cpu_percent"] = cpu_percent + reply_content["host_virtual_memory"] = dict(psutil.virtual_memory()._asdict()) + reply_msg = self.session.send(stream, "usage_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) async def do_debug_request(self, msg): raise NotImplementedError - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- # Engine methods (DEPRECATED) - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- async def apply_request(self, stream, ident, parent): self.log.warning("apply_request is deprecated in kernel_base, moving to ipyparallel.") try: - content = parent['content'] - bufs = parent['buffers'] - msg_id = parent['header']['msg_id'] + content = parent["content"] + bufs = parent["buffers"] + msg_id = parent["header"]["msg_id"] except Exception: self.log.error("Got bad msg: %s", parent, exc_info=True) return @@ -937,21 +975,30 @@ async def apply_request(self, stream, ident, parent): md = self.finish_metadata(parent, md, reply_content) - self.session.send(stream, 'apply_reply', reply_content, - parent=parent, ident=ident,buffers=result_buf, metadata=md) + self.session.send( + stream, + "apply_reply", + reply_content, + parent=parent, + ident=ident, + buffers=result_buf, + metadata=md, + ) def do_apply(self, content, bufs, msg_id, reply_metadata): """DEPRECATED""" raise NotImplementedError - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- # Control messages (DEPRECATED) - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- async def abort_request(self, stream, ident, parent): """abort a specific msg by id""" - self.log.warning("abort_request is deprecated in kernel_base. It is only part of IPython parallel") - msg_ids = parent['content'].get('msg_ids', None) + self.log.warning( + "abort_request is deprecated in kernel_base. It is only part of IPython parallel" + ) + msg_ids = parent["content"].get("msg_ids", None) if isinstance(msg_ids, str): msg_ids = [msg_ids] if not msg_ids: @@ -959,25 +1006,27 @@ async def abort_request(self, stream, ident, parent): for mid in msg_ids: self.aborted.add(str(mid)) - content = dict(status='ok') - reply_msg = self.session.send(stream, 'abort_reply', content=content, - parent=parent, ident=ident) + content = dict(status="ok") + reply_msg = self.session.send( + stream, "abort_reply", content=content, parent=parent, ident=ident + ) self.log.debug("%s", reply_msg) async def clear_request(self, stream, idents, parent): """Clear our namespace.""" - self.log.warning("clear_request is deprecated in kernel_base. It is only part of IPython parallel") + self.log.warning( + "clear_request is deprecated in kernel_base. It is only part of IPython parallel" + ) content = self.do_clear() - self.session.send(stream, 'clear_reply', ident=idents, parent=parent, - content = content) + self.session.send(stream, "clear_reply", ident=idents, parent=parent, content=content) def do_clear(self): """DEPRECATED since 4.0.3""" raise NotImplementedError - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- # Protected interface - #--------------------------------------------------------------------------- + # --------------------------------------------------------------------------- def _topic(self, topic): """prefixed topic for IOPub messages""" @@ -1010,15 +1059,11 @@ async def stop_aborting(): # if we have a delay, give messages this long to arrive on the queue # before we stop aborting requests - asyncio.get_event_loop().call_later( - self.stop_on_error_timeout, schedule_stop_aborting - ) + asyncio.get_event_loop().call_later(self.stop_on_error_timeout, schedule_stop_aborting) def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" - self.log.info( - f"Aborting {msg['header']['msg_id']}: {msg['header']['msg_type']}" - ) + self.log.info(f"Aborting {msg['header']['msg_id']}: {msg['header']['msg_type']}") reply_type = msg["header"]["msg_type"].rsplit("_", 1)[0] + "_reply" status = {"status": "aborted"} md = self.init_metadata(msg) @@ -1026,17 +1071,22 @@ def _send_abort_reply(self, stream, msg, idents): md.update(status) self.session.send( - stream, reply_type, metadata=md, - content=status, parent=msg, ident=idents, + stream, + reply_type, + metadata=md, + content=status, + parent=msg, + ident=idents, ) def _no_raw_input(self): """Raise StdinNotImplementedError if active frontend doesn't support stdin.""" - raise StdinNotImplementedError("raw_input was called, but this " - "frontend does not support stdin.") + raise StdinNotImplementedError( + "raw_input was called, but this " "frontend does not support stdin." + ) - def getpass(self, prompt='', stream=None): + def getpass(self, prompt="", stream=None): """Forward getpass to frontends Raises @@ -1062,7 +1112,7 @@ def getpass(self, prompt='', stream=None): password=True, ) - def raw_input(self, prompt=''): + def raw_input(self, prompt=""): """Forward raw_input to frontends Raises @@ -1097,8 +1147,7 @@ def _input_request(self, prompt, ident, parent, password=False): # Send the input request. content = json_clean(dict(prompt=prompt, password=password)) - self.session.send(self.stdin_socket, 'input_request', content, parent, - ident=ident) + self.session.send(self.stdin_socket, "input_request", content, parent, ident=ident) # Await a response. while True: @@ -1109,9 +1158,7 @@ def _input_request(self, prompt, ident, parent, password=False): # zmq.select() is also uninterruptible, but at least this # way reads get noticed immediately and KeyboardInterrupts # get noticed fairly quickly by human response time standards. - rlist, _, xlist = zmq.select( - [self.stdin_socket], [], [self.stdin_socket], 0.01 - ) + rlist, _, xlist = zmq.select([self.stdin_socket], [], [self.stdin_socket], 0.01) if rlist or xlist: ident, reply = self.session.recv(self.stdin_socket) if (ident, reply) != (None, None): @@ -1126,8 +1173,8 @@ def _input_request(self, prompt, ident, parent, password=False): value = reply["content"]["value"] except Exception: self.log.error("Bad input_reply: %s", parent) - value = '' - if value == '\x04': + value = "" + if value == "\x04": # EOF raise EOFError return value @@ -1194,8 +1241,7 @@ async def _progressively_terminate_all_children(self): await asyncio.sleep(delay) async def _at_shutdown(self): - """Actions taken at shutdown by the kernel, called by python's atexit. - """ + """Actions taken at shutdown by the kernel, called by python's atexit.""" try: await self._progressively_terminate_all_children() except Exception as e: diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 34e4960b0..585d87127 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -17,10 +17,10 @@ pjoin = os.path.join -KERNEL_NAME = 'python%i' % sys.version_info[0] +KERNEL_NAME = "python%i" % sys.version_info[0] # path to kernelspec resources -RESOURCES = pjoin(os.path.dirname(__file__), 'resources') +RESOURCES = pjoin(os.path.dirname(__file__), "resources") def make_ipkernel_cmd(mod="ipykernel_launcher", executable=None, extra_arguments=None): @@ -42,7 +42,7 @@ def make_ipkernel_cmd(mod="ipykernel_launcher", executable=None, extra_arguments if executable is None: executable = sys.executable extra_arguments = extra_arguments or [] - arguments = [executable, '-m', mod, '-f', '{connection_file}'] + arguments = [executable, "-m", mod, "-f", "{connection_file}"] arguments.extend(extra_arguments) return arguments @@ -51,10 +51,10 @@ def make_ipkernel_cmd(mod="ipykernel_launcher", executable=None, extra_arguments def get_kernel_dict(extra_arguments=None): """Construct dict for kernel.json""" return { - 'argv': make_ipkernel_cmd(extra_arguments=extra_arguments), - 'display_name': 'Python %i (ipykernel)' % sys.version_info[0], - 'language': 'python', - 'metadata': { 'debugger': _is_debugpy_available} + "argv": make_ipkernel_cmd(extra_arguments=extra_arguments), + "display_name": "Python %i (ipykernel)" % sys.version_info[0], + "language": "python", + "metadata": {"debugger": _is_debugpy_available}, } @@ -67,7 +67,7 @@ def write_kernel_spec(path=None, overrides=None, extra_arguments=None): The path to the kernelspec is always returned. """ if path is None: - path = os.path.join(tempfile.mkdtemp(suffix='_kernels'), KERNEL_NAME) + path = os.path.join(tempfile.mkdtemp(suffix="_kernels"), KERNEL_NAME) # stage resources shutil.copytree(RESOURCES, path) @@ -82,14 +82,21 @@ def write_kernel_spec(path=None, overrides=None, extra_arguments=None): if overrides: kernel_dict.update(overrides) - with open(pjoin(path, 'kernel.json'), 'w') as f: + with open(pjoin(path, "kernel.json"), "w") as f: json.dump(kernel_dict, f, indent=1) return path -def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, display_name=None, - prefix=None, profile=None, env=None): +def install( + kernel_spec_manager=None, + user=False, + kernel_name=KERNEL_NAME, + display_name=None, + prefix=None, + profile=None, + env=None, +): """Install the IPython kernelspec for Jupyter Parameters @@ -132,18 +139,20 @@ def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, displ extra_arguments = ["--profile", profile] if not display_name: # add the profile to the default display name - overrides["display_name"] = 'Python %i [profile=%s]' % (sys.version_info[0], profile) + overrides["display_name"] = "Python %i [profile=%s]" % (sys.version_info[0], profile) else: extra_arguments = None if env: - overrides['env'] = env + overrides["env"] = env path = write_kernel_spec(overrides=overrides, extra_arguments=extra_arguments) dest = kernel_spec_manager.install_kernel_spec( - path, kernel_name=kernel_name, user=user, prefix=prefix) + path, kernel_name=kernel_name, user=user, prefix=prefix + ) # cleanup afterward shutil.rmtree(path) return dest + # Entrypoint from traitlets.config import Application @@ -151,7 +160,8 @@ def install(kernel_spec_manager=None, user=False, kernel_name=KERNEL_NAME, displ class InstallIPythonKernelSpecApp(Application): """Dummy app wrapping argparse""" - name = 'ipython-kernel-install' + + name = "ipython-kernel-install" def initialize(self, argv=None): if argv is None: @@ -160,33 +170,67 @@ def initialize(self, argv=None): def start(self): import argparse - parser = argparse.ArgumentParser(prog=self.name, - description="Install the IPython kernel spec.") - parser.add_argument('--user', action='store_true', - help="Install for the current user instead of system-wide") - parser.add_argument('--name', type=str, default=KERNEL_NAME, + + parser = argparse.ArgumentParser( + prog=self.name, description="Install the IPython kernel spec." + ) + parser.add_argument( + "--user", + action="store_true", + help="Install for the current user instead of system-wide", + ) + parser.add_argument( + "--name", + type=str, + default=KERNEL_NAME, help="Specify a name for the kernelspec." - " This is needed to have multiple IPython kernels at the same time.") - parser.add_argument('--display-name', type=str, + " This is needed to have multiple IPython kernels at the same time.", + ) + parser.add_argument( + "--display-name", + type=str, help="Specify the display name for the kernelspec." - " This is helpful when you have multiple IPython kernels.") - parser.add_argument('--profile', type=str, + " This is helpful when you have multiple IPython kernels.", + ) + parser.add_argument( + "--profile", + type=str, help="Specify an IPython profile to load. " - "This can be used to create custom versions of the kernel.") - parser.add_argument('--prefix', type=str, + "This can be used to create custom versions of the kernel.", + ) + parser.add_argument( + "--prefix", + type=str, help="Specify an install prefix for the kernelspec." - " This is needed to install into a non-default location, such as a conda/virtual-env.") - parser.add_argument('--sys-prefix', action='store_const', const=sys.prefix, dest='prefix', + " This is needed to install into a non-default location, such as a conda/virtual-env.", + ) + parser.add_argument( + "--sys-prefix", + action="store_const", + const=sys.prefix, + dest="prefix", help="Install to Python's sys.prefix." - " Shorthand for --prefix='%s'. For use in conda/virtual-envs." % sys.prefix) - parser.add_argument('--env', action='append', nargs=2, metavar=('ENV', 'VALUE'), - help="Set environment variables for the kernel.") + " Shorthand for --prefix='%s'. For use in conda/virtual-envs." % sys.prefix, + ) + parser.add_argument( + "--env", + action="append", + nargs=2, + metavar=("ENV", "VALUE"), + help="Set environment variables for the kernel.", + ) opts = parser.parse_args(self.argv) if opts.env: - opts.env = {k:v for (k, v) in opts.env} + opts.env = {k: v for (k, v) in opts.env} try: - dest = install(user=opts.user, kernel_name=opts.name, profile=opts.profile, - prefix=opts.prefix, display_name=opts.display_name, env=opts.env) + dest = install( + user=opts.user, + kernel_name=opts.name, + profile=opts.profile, + prefix=opts.prefix, + display_name=opts.display_name, + env=opts.env, + ) except OSError as e: if e.errno == errno.EACCES: print(e, file=sys.stderr) @@ -197,5 +241,5 @@ def start(self): print("Installed kernelspec %s in %s" % (opts.name, dest)) -if __name__ == '__main__': +if __name__ == "__main__": InstallIPythonKernelSpecApp.launch_instance() diff --git a/ipykernel/log.py b/ipykernel/log.py index d0b03cc32..66bb6722c 100644 --- a/ipykernel/log.py +++ b/ipykernel/log.py @@ -1,24 +1,28 @@ +import warnings + from zmq.log.handlers import PUBHandler -import warnings -warnings.warn("ipykernel.log is deprecated. It has moved to ipyparallel.engine.log", +warnings.warn( + "ipykernel.log is deprecated. It has moved to ipyparallel.engine.log", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) + class EnginePUBHandler(PUBHandler): """A simple PUBHandler subclass that sets root_topic""" - engine=None + + engine = None def __init__(self, engine, *args, **kwargs): - PUBHandler.__init__(self,*args, **kwargs) + PUBHandler.__init__(self, *args, **kwargs) self.engine = engine @property def root_topic(self): """this is a property, in case the handler is created before the engine gets registered with an id""" - if isinstance(getattr(self.engine, 'id', None), int): - return "engine.%i"%self.engine.id + if isinstance(getattr(self.engine, "id", None), int): + return "engine.%i" % self.engine.id else: return "engine" diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 4a2124407..d120f5000 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -9,15 +9,15 @@ import platform import signal import time +import warnings from _thread import interrupt_main # Py 3 from threading import Thread from traitlets.log import get_logger -import warnings class ParentPollerUnix(Thread): - """ A Unix-specific daemon thread that terminates the program immediately + """A Unix-specific daemon thread that terminates the program immediately when the parent process no longer exists. """ @@ -28,6 +28,7 @@ def __init__(self): def run(self): # We cannot use os.waitpid because it works only for child processes. from errno import EINTR + while True: try: if os.getppid() == 1: @@ -41,13 +42,13 @@ def run(self): class ParentPollerWindows(Thread): - """ A Windows-specific daemon thread that listens for a special event that + """A Windows-specific daemon thread that listens for a special event that signals an interrupt and, optionally, terminates the program immediately when the parent process no longer exists. """ def __init__(self, interrupt_handle=None, parent_handle=None): - """ Create the poller. At least one of the optional parameters must be + """Create the poller. At least one of the optional parameters must be provided. Parameters @@ -59,7 +60,7 @@ def __init__(self, interrupt_handle=None, parent_handle=None): If provided, the program will terminate immediately when this handle is signaled. """ - assert(interrupt_handle or parent_handle) + assert interrupt_handle or parent_handle super().__init__() if ctypes is None: raise ImportError("ParentPollerWindows requires ctypes") @@ -68,12 +69,11 @@ def __init__(self, interrupt_handle=None, parent_handle=None): self.parent_handle = parent_handle def run(self): - """ Run the poll loop. This method never returns. - """ + """Run the poll loop. This method never returns.""" try: - from _winapi import WAIT_OBJECT_0, INFINITE + from _winapi import INFINITE, WAIT_OBJECT_0 except ImportError: - from _subprocess import WAIT_OBJECT_0, INFINITE + from _subprocess import INFINITE, WAIT_OBJECT_0 # Build the list of handle to listen on. handles = [] @@ -82,15 +82,16 @@ def run(self): if self.parent_handle: handles.append(self.parent_handle) arch = platform.architecture()[0] - c_int = ctypes.c_int64 if arch.startswith('64') else ctypes.c_int + c_int = ctypes.c_int64 if arch.startswith("64") else ctypes.c_int # Listen forever. while True: result = ctypes.windll.kernel32.WaitForMultipleObjects( - len(handles), # nCount - (c_int * len(handles))(*handles), # lpHandles - False, # bWaitAll - INFINITE) # dwMilliseconds + len(handles), # nCount + (c_int * len(handles))(*handles), # lpHandles + False, # bWaitAll + INFINITE, + ) # dwMilliseconds if WAIT_OBJECT_0 <= result < len(handles): handle = handles[result - WAIT_OBJECT_0] @@ -106,8 +107,10 @@ def run(self): os._exit(1) elif result < 0: # wait failed, just give up and stop polling. - warnings.warn("""Parent poll failed. If the frontend dies, + warnings.warn( + """Parent poll failed. If the frontend dies, the kernel may be left running. Please let us know about your system (bitness, Python, etc.) at - ipython-dev@scipy.org""") + ipython-dev@scipy.org""" + ) return diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 834abea4d..82c588311 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -5,41 +5,44 @@ import typing import warnings -warnings.warn("ipykernel.pickleutil is deprecated. It has moved to ipyparallel.", +warnings.warn( + "ipykernel.pickleutil is deprecated. It has moved to ipyparallel.", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) import copy -import sys import pickle +import sys from types import FunctionType -from traitlets.utils.importstring import import_item - # This registers a hook when it's imported from ipyparallel.serialize import codeutil # noqa F401 - from traitlets.log import get_logger +from traitlets.utils.importstring import import_item buffer = memoryview class_type = type PICKLE_PROTOCOL = pickle.DEFAULT_PROTOCOL + def _get_cell_type(a=None): """the type of a closure cell doesn't seem to be importable, so just create one """ + def inner(): return a + return type(inner.__closure__[0]) + cell_type = _get_cell_type() -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------- # Functions -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------- def interactive(f): @@ -51,12 +54,15 @@ def interactive(f): # build new FunctionType, so it can have the right globals # interactive functions never have closures, that's kind of the point if isinstance(f, FunctionType): - mainmod = __import__('__main__') - f = FunctionType(f.__code__, mainmod.__dict__, - f.__name__, f.__defaults__, + mainmod = __import__("__main__") + f = FunctionType( + f.__code__, + mainmod.__dict__, + f.__name__, + f.__defaults__, ) # associate with __main__ for uncanning - f.__module__ = '__main__' + f.__module__ = "__main__" return f @@ -84,6 +90,7 @@ def use_dill(): # disable special function handling, let dill take care of it can_map.pop(FunctionType, None) + def use_cloudpickle(): """use cloudpickle to expand serialization support @@ -105,9 +112,9 @@ def use_cloudpickle(): can_map.pop(FunctionType, None) -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------- # Classes -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------- class CannedObject: @@ -152,14 +159,15 @@ def get_object(self, g=None): class Reference(CannedObject): """object for wrapping a remote reference by name.""" + def __init__(self, name): if not isinstance(name, str): - raise TypeError("illegal name: %r"%name) + raise TypeError("illegal name: %r" % name) self.name = name self.buffers = [] def __repr__(self): - return ""%self.name + return "" % self.name def get_object(self, g=None): if g is None: @@ -170,33 +178,35 @@ def get_object(self, g=None): class CannedCell(CannedObject): """Can a closure cell""" + def __init__(self, cell): self.cell_contents = can(cell.cell_contents) def get_object(self, g=None): cell_contents = uncan(self.cell_contents, g) + def inner(): return cell_contents + return inner.__closure__[0] class CannedFunction(CannedObject): - def __init__(self, f): self._check_type(f) self.code = f.__code__ if f.__defaults__: - self.defaults = [ can(fd) for fd in f.__defaults__ ] + self.defaults = [can(fd) for fd in f.__defaults__] else: self.defaults = None closure = f.__closure__ if closure: - self.closure = tuple( can(cell) for cell in closure ) + self.closure = tuple(can(cell) for cell in closure) else: self.closure = None - self.module = f.__module__ or '__main__' + self.module = f.__module__ or "__main__" self.__name__ = f.__name__ self.buffers = [] @@ -205,7 +215,7 @@ def _check_type(self, obj): def get_object(self, g=None): # try to load function back into its module: - if not self.module.startswith('__'): + if not self.module.startswith("__"): __import__(self.module) g = sys.modules[self.module].__dict__ @@ -222,22 +232,22 @@ def get_object(self, g=None): newFunc = FunctionType(self.code, g, self.__name__, defaults, closure) return newFunc -class CannedClass(CannedObject): +class CannedClass(CannedObject): def __init__(self, cls): self._check_type(cls) self.name = cls.__name__ self.old_style = not isinstance(cls, type) self._canned_dict = {} - for k,v in cls.__dict__.items(): - if k not in ('__weakref__', '__dict__'): + for k, v in cls.__dict__.items(): + if k not in ("__weakref__", "__dict__"): self._canned_dict[k] = can(v) if self.old_style: mro = [] else: mro = cls.mro() - self.parents = [ can(c) for c in mro[1:] ] + self.parents = [can(c) for c in mro[1:]] self.buffers = [] def _check_type(self, obj): @@ -247,18 +257,20 @@ def get_object(self, g=None): parents = tuple(uncan(p, g) for p in self.parents) return type(self.name, parents, uncan_dict(self._canned_dict, g=g)) + class CannedArray(CannedObject): def __init__(self, obj): from numpy import ascontiguousarray + self.shape = obj.shape self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str self.pickled = False if sum(obj.shape) == 0: self.pickled = True - elif obj.dtype == 'O': + elif obj.dtype == "O": # can't handle object dtype with buffer approach self.pickled = True - elif obj.dtype.fields and any(dt == 'O' for dt,sz in obj.dtype.fields.values()): + elif obj.dtype.fields and any(dt == "O" for dt, sz in obj.dtype.fields.values()): self.pickled = True if self.pickled: # just pickle it @@ -270,6 +282,7 @@ def __init__(self, obj): def get_object(self, g=None): from numpy import frombuffer + data = self.buffers[0] if self.pickled: # we just pickled it @@ -295,23 +308,25 @@ def get_object(self, g=None): data = self.buffers[0] return self.wrap(data) + class CannedBuffer(CannedBytes): wrap = buffer + class CannedMemoryView(CannedBytes): wrap = memoryview -#------------------------------------------------------------------------------- + +# ------------------------------------------------------------------------------- # Functions -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------- -def _import_mapping(mapping, original=None): - """import any string-keys in a type mapping - """ +def _import_mapping(mapping, original=None): + """import any string-keys in a type mapping""" log = get_logger() log.debug("Importing canning map") - for key,value in list(mapping.items()): + for key, value in list(mapping.items()): if isinstance(key, str): try: cls = import_item(key) @@ -323,6 +338,7 @@ def _import_mapping(mapping, original=None): else: mapping[cls] = mapping.pop(key) + def istype(obj, check): """like isinstance(obj, check), but strict @@ -336,12 +352,13 @@ def istype(obj, check): else: return type(obj) is check + def can(obj): """prepare an object for pickling""" import_needed = False - for cls,canner in can_map.items(): + for cls, canner in can_map.items(): if isinstance(cls, str): import_needed = True break @@ -356,12 +373,14 @@ def can(obj): return obj + def can_class(obj): - if isinstance(obj, class_type) and obj.__module__ == '__main__': + if isinstance(obj, class_type) and obj.__module__ == "__main__": return CannedClass(obj) else: return obj + def can_dict(obj): """can the *values* of a dict""" if istype(obj, dict): @@ -372,8 +391,10 @@ def can_dict(obj): else: return obj + sequence_types = (list, tuple, set) + def can_sequence(obj): """can the elements of a sequence""" if istype(obj, sequence_types): @@ -382,11 +403,12 @@ def can_sequence(obj): else: return obj + def uncan(obj, g=None): """invert canning""" import_needed = False - for cls,uncanner in uncan_map.items(): + for cls, uncanner in uncan_map.items(): if isinstance(cls, str): import_needed = True break @@ -401,42 +423,45 @@ def uncan(obj, g=None): return obj + def uncan_dict(obj, g=None): if istype(obj, dict): newobj = {} for k, v in obj.items(): - newobj[k] = uncan(v,g) + newobj[k] = uncan(v, g) return newobj else: return obj + def uncan_sequence(obj, g=None): if istype(obj, sequence_types): t = type(obj) - return t([uncan(i,g) for i in obj]) + return t([uncan(i, g) for i in obj]) else: return obj -#------------------------------------------------------------------------------- + +# ------------------------------------------------------------------------------- # API dictionaries -#------------------------------------------------------------------------------- +# ------------------------------------------------------------------------------- # These dicts can be extended for custom serialization of new objects can_map = { - 'numpy.ndarray' : CannedArray, - FunctionType : CannedFunction, - bytes : CannedBytes, - memoryview : CannedMemoryView, - cell_type : CannedCell, - class_type : can_class, + "numpy.ndarray": CannedArray, + FunctionType: CannedFunction, + bytes: CannedBytes, + memoryview: CannedMemoryView, + cell_type: CannedCell, + class_type: can_class, } if buffer is not memoryview: can_map[buffer] = CannedBuffer uncan_map = { - CannedObject : lambda obj, g: obj.get_object(g), - dict : uncan_dict, + CannedObject: lambda obj, g: obj.get_object(g), + dict: uncan_dict, } # for use in _import_mapping: diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index c1935b4b0..b1627cac5 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -7,9 +7,8 @@ from matplotlib_inline.backend_inline import * # analysis: ignore # noqa F401 - warnings.warn( "`ipykernel.pylab.backend_inline` is deprecated, directly " "use `matplotlib_inline.backend_inline`", - DeprecationWarning + DeprecationWarning, ) diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index bc9006f5f..09e2bef6a 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -7,9 +7,7 @@ from matplotlib_inline.config import * # analysis: ignore # noqa F401 - warnings.warn( - "`ipykernel.pylab.config` is deprecated, directly " - "use `matplotlib_inline.config`", - DeprecationWarning + "`ipykernel.pylab.config` is deprecated, directly " "use `matplotlib_inline.config`", + DeprecationWarning, ) diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 184a60ded..43247d3ff 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -4,41 +4,53 @@ # Distributed under the terms of the Modified BSD License. import warnings -warnings.warn("ipykernel.serialize is deprecated. It has moved to ipyparallel.serialize", + +warnings.warn( + "ipykernel.serialize is deprecated. It has moved to ipyparallel.serialize", DeprecationWarning, - stacklevel=2 + stacklevel=2, ) import pickle - from itertools import chain try: # available since ipyparallel 5.0.0 from ipyparallel.serialize.canning import ( - can, uncan, can_sequence, uncan_sequence, CannedObject, - istype, sequence_types, + CannedObject, + can, + can_sequence, + istype, + sequence_types, + uncan, + uncan_sequence, ) from ipyparallel.serialize.serialize import PICKLE_PROTOCOL except ImportError: # Deprecated since ipykernel 4.3.0 from ipykernel.pickleutil import ( - can, uncan, can_sequence, uncan_sequence, CannedObject, - istype, sequence_types, PICKLE_PROTOCOL, + can, + uncan, + can_sequence, + uncan_sequence, + CannedObject, + istype, + sequence_types, + PICKLE_PROTOCOL, ) -from jupyter_client.session import MAX_ITEMS, MAX_BYTES +from jupyter_client.session import MAX_BYTES, MAX_ITEMS -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Serialization Functions -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- def _extract_buffers(obj, threshold=MAX_BYTES): """extract buffers larger than a certain threshold""" buffers = [] if isinstance(obj, CannedObject) and obj.buffers: - for i,buf in enumerate(obj.buffers): + for i, buf in enumerate(obj.buffers): if len(buf) > threshold: # buffer larger than threshold, prevent pickling obj.buffers[i] = None @@ -49,13 +61,15 @@ def _extract_buffers(obj, threshold=MAX_BYTES): obj.buffers[i] = buf.tobytes() return buffers + def _restore_buffers(obj, buffers): - """restore buffers extracted by """ + """restore buffers extracted by""" if isinstance(obj, CannedObject) and obj.buffers: - for i,buf in enumerate(obj.buffers): + for i, buf in enumerate(obj.buffers): if buf is None: obj.buffers[i] = buffers.pop(0) + def serialize_object(obj, buffer_threshold=MAX_BYTES, item_threshold=MAX_ITEMS): """Serialize an object into a list of sendable buffers. @@ -93,6 +107,7 @@ def serialize_object(obj, buffer_threshold=MAX_BYTES, item_threshold=MAX_ITEMS): buffers.insert(0, pickle.dumps(cobj, PICKLE_PROTOCOL)) return buffers + def deserialize_object(buffers, g=None): """reconstruct an object serialized by serialize_object from data buffers. @@ -124,6 +139,7 @@ def deserialize_object(buffers, g=None): return newobj, bufs + def pack_apply_message(f, args, kwargs, buffer_threshold=MAX_BYTES, item_threshold=MAX_ITEMS): """pack up a function, args, and kwargs to be sent over the wire @@ -140,12 +156,16 @@ def pack_apply_message(f, args, kwargs, buffer_threshold=MAX_BYTES, item_thresho With length at least two + len(args) + len(kwargs) """ - arg_bufs = list(chain.from_iterable( - serialize_object(arg, buffer_threshold, item_threshold) for arg in args)) + arg_bufs = list( + chain.from_iterable(serialize_object(arg, buffer_threshold, item_threshold) for arg in args) + ) kw_keys = sorted(kwargs.keys()) - kwarg_bufs = list(chain.from_iterable( - serialize_object(kwargs[key], buffer_threshold, item_threshold) for key in kw_keys)) + kwarg_bufs = list( + chain.from_iterable( + serialize_object(kwargs[key], buffer_threshold, item_threshold) for key in kw_keys + ) + ) info = dict(nargs=len(args), narg_bufs=len(arg_bufs), kw_keys=kw_keys) @@ -156,28 +176,29 @@ def pack_apply_message(f, args, kwargs, buffer_threshold=MAX_BYTES, item_thresho return msg + def unpack_apply_message(bufs, g=None, copy=True): """unpack f,args,kwargs from buffers packed by pack_apply_message() Returns: original f,args,kwargs""" - bufs = list(bufs) # allow us to pop + bufs = list(bufs) # allow us to pop assert len(bufs) >= 2, "not enough buffers!" pf = bufs.pop(0) f = uncan(pickle.loads(pf), g) pinfo = bufs.pop(0) info = pickle.loads(pinfo) - arg_bufs, kwarg_bufs = bufs[:info['narg_bufs']], bufs[info['narg_bufs']:] + arg_bufs, kwarg_bufs = bufs[: info["narg_bufs"]], bufs[info["narg_bufs"] :] args = [] - for i in range(info['nargs']): + for i in range(info["nargs"]): arg, arg_bufs = deserialize_object(arg_bufs, g) args.append(arg) args = tuple(args) assert not arg_bufs, "Shouldn't be any arg bufs left over" kwargs = {} - for key in info['kw_keys']: + for key in info["kw_keys"]: kwarg, kwarg_bufs = deserialize_object(kwarg_bufs, g) kwargs[key] = kwarg assert not kwarg_bufs, "Shouldn't be any kwarg bufs left over" - return f,args,kwargs + return f, args, kwargs diff --git a/ipykernel/tests/__init__.py b/ipykernel/tests/__init__.py index 399311940..6ca7492a0 100644 --- a/ipykernel/tests/__init__.py +++ b/ipykernel/tests/__init__.py @@ -7,8 +7,9 @@ import tempfile from unittest.mock import patch -from jupyter_core import paths as jpaths from IPython import paths as ipaths +from jupyter_core import paths as jpaths + from ipykernel.kernelspec import install pjoin = os.path.join @@ -16,16 +17,20 @@ tmp = None patchers = [] + def setup(): """setup temporary env for tests""" global tmp tmp = tempfile.mkdtemp() patchers[:] = [ - patch.dict(os.environ, { - 'HOME': tmp, - # Let tests work with --user install when HOME is changed: - 'PYTHONPATH': os.pathsep.join(sys.path), - }), + patch.dict( + os.environ, + { + "HOME": tmp, + # Let tests work with --user install when HOME is changed: + "PYTHONPATH": os.pathsep.join(sys.path), + }, + ), ] for p in patchers: p.start() diff --git a/ipykernel/tests/test_async.py b/ipykernel/tests/test_async.py index 1266fa355..b36fedff0 100644 --- a/ipykernel/tests/test_async.py +++ b/ipykernel/tests/test_async.py @@ -1,15 +1,13 @@ """Test async/await integration""" -from distutils.version import LooseVersion as V import sys +from distutils.version import LooseVersion as V -import pytest import IPython +import pytest - -from .utils import execute, flush_channels, start_new_kernel, TIMEOUT from .test_message_spec import validate_message - +from .utils import TIMEOUT, execute, flush_channels, start_new_kernel KC = KM = None @@ -26,7 +24,6 @@ def teardown_function(): KM.shutdown_kernel(now=True) - def test_async_await(): flush_channels(KC) msg_id, content = execute("import asyncio; await asyncio.sleep(0.1)", KC) @@ -46,9 +43,7 @@ def test_async_interrupt(asynclib, request): assert content["status"] == "ok", content flush_channels(KC) - msg_id = KC.execute( - f"print('begin'); import {asynclib}; await {asynclib}.sleep(5)" - ) + msg_id = KC.execute(f"print('begin'); import {asynclib}; await {asynclib}.sleep(5)") busy = KC.get_iopub_msg(timeout=TIMEOUT) validate_message(busy, "status", msg_id) assert busy["content"]["execution_state"] == "busy" diff --git a/ipykernel/tests/test_connect.py b/ipykernel/tests/test_connect.py index 16c220785..8e584ce20 100644 --- a/ipykernel/tests/test_connect.py +++ b/ipykernel/tests/test_connect.py @@ -11,24 +11,23 @@ import pytest import zmq - from traitlets.config import Config + from ipykernel import connect from ipykernel.kernelapp import IPKernelApp from .utils import TemporaryWorkingDirectory - sample_info = { - 'ip': '1.2.3.4', - 'transport': 'ipc', - 'shell_port': 1, - 'hb_port': 2, - 'iopub_port': 3, - 'stdin_port': 4, - 'control_port': 5, - 'key': b'abc123', - 'signature_scheme': 'hmac-md5', + "ip": "1.2.3.4", + "transport": "ipc", + "shell_port": 1, + "hb_port": 2, + "iopub_port": 3, + "stdin_port": 4, + "control_port": 5, + "key": b"abc123", + "signature_scheme": "hmac-md5", } @@ -45,13 +44,13 @@ def test_get_connection_file(): cfg = Config() with TemporaryWorkingDirectory() as d: cfg.ProfileDir.location = d - cf = 'kernel.json' + cf = "kernel.json" app = DummyKernelApp(config=cfg, connection_file=cf) app.initialize() profile_cf = os.path.join(app.connection_dir, cf) assert profile_cf == app.abs_connection_file - with open(profile_cf, 'w') as f: + with open(profile_cf, "w") as f: f.write("{}") assert os.path.exists(profile_cf) assert connect.get_connection_file(app) == profile_cf @@ -62,7 +61,7 @@ def test_get_connection_file(): def test_get_connection_info(): with TemporaryDirectory() as d: - cf = os.path.join(d, 'kernel.json') + cf = os.path.join(d, "kernel.json") connect.write_connection_file(cf, **sample_info) json_info = connect.get_connection_info(cf) info = connect.get_connection_info(cf, unpack=True) @@ -72,7 +71,7 @@ def test_get_connection_info(): assert sub_info == sample_info info2 = json.loads(json_info) - info2['key'] = info2['key'].encode("utf-8") + info2["key"] = info2["key"].encode("utf-8") sub_info2 = {k: v for k, v in info.items() if k in sample_info} assert sub_info2 == sample_info @@ -81,11 +80,11 @@ def test_port_bind_failure_raises(request): cfg = Config() with TemporaryWorkingDirectory() as d: cfg.ProfileDir.location = d - cf = 'kernel.json' + cf = "kernel.json" app = DummyKernelApp(config=cfg, connection_file=cf) request.addfinalizer(app.close) app.initialize() - with patch.object(app, '_try_bind_socket') as mock_try_bind: + with patch.object(app, "_try_bind_socket") as mock_try_bind: mock_try_bind.side_effect = zmq.ZMQError(-100, "fails for unknown error types") with pytest.raises(zmq.ZMQError): app.init_sockets() @@ -97,34 +96,35 @@ def test_port_bind_failure_recovery(request): errno.WSAEADDRINUSE except AttributeError: # Fake windows address in-use code - p = patch.object(errno, 'WSAEADDRINUSE', 12345, create=True) + p = patch.object(errno, "WSAEADDRINUSE", 12345, create=True) p.start() request.addfinalizer(p.stop) cfg = Config() with TemporaryWorkingDirectory() as d: cfg.ProfileDir.location = d - cf = 'kernel.json' + cf = "kernel.json" app = DummyKernelApp(config=cfg, connection_file=cf) request.addfinalizer(app.close) app.initialize() - with patch.object(app, '_try_bind_socket') as mock_try_bind: + with patch.object(app, "_try_bind_socket") as mock_try_bind: mock_try_bind.side_effect = [ zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind unix"), - zmq.ZMQError(errno.WSAEADDRINUSE, "fails for non-bind windows") + zmq.ZMQError(errno.WSAEADDRINUSE, "fails for non-bind windows"), ] + [0] * 100 # Shouldn't raise anything as retries will kick in app.init_sockets() + def test_port_bind_failure_gives_up_retries(request): cfg = Config() with TemporaryWorkingDirectory() as d: cfg.ProfileDir.location = d - cf = 'kernel.json' + cf = "kernel.json" app = DummyKernelApp(config=cfg, connection_file=cf) request.addfinalizer(app.close) app.initialize() - with patch.object(app, '_try_bind_socket') as mock_try_bind: + with patch.object(app, "_try_bind_socket") as mock_try_bind: mock_try_bind.side_effect = zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind") with pytest.raises(zmq.ZMQError): app.init_sockets() diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index d670df7fd..6b9c817cd 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -1,7 +1,8 @@ import sys + import pytest -from .utils import TIMEOUT, new_kernel, get_reply +from .utils import TIMEOUT, get_reply, new_kernel seq = 0 @@ -64,9 +65,7 @@ def kernel_with_debug(kernel): yield kernel finally: # Detach - wait_for_debug_request( - kernel, "disconnect", {"restart": False, "terminateDebuggee": True} - ) + wait_for_debug_request(kernel, "disconnect", {"restart": False, "terminateDebuggee": True}) def test_debug_initialize(kernel): @@ -156,7 +155,7 @@ def test_stop_on_breakpoint(kernel_with_debug): # Wait for stop on breakpoint msg = {"msg_type": "", "content": {}} - while msg.get('msg_type') != 'debug_event' or msg["content"].get("event") != "stopped": + while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) assert msg["content"]["body"]["reason"] == "breakpoint" @@ -192,7 +191,7 @@ def f(a, b): # Wait for stop on breakpoint msg = {"msg_type": "", "content": {}} - while msg.get('msg_type') != 'debug_event' or msg["content"].get("event") != "stopped": + while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) assert msg["content"]["body"]["reason"] == "breakpoint" @@ -227,7 +226,6 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): f(2, 3)""" - r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) source = r["body"]["sourcePath"] @@ -249,16 +247,16 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): # Wait for stop on breakpoint msg = {"msg_type": "", "content": {}} - while msg.get('msg_type') != 'debug_event' or msg["content"].get("event") != "stopped": + while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) - stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})[ - "body" - ]["stackFrames"] + stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})["body"][ + "stackFrames" + ] - scopes = wait_for_debug_request( - kernel_with_debug, "scopes", {"frameId": stacks[0]["id"]} - )["body"]["scopes"] + scopes = wait_for_debug_request(kernel_with_debug, "scopes", {"frameId": stacks[0]["id"]})[ + "body" + ]["scopes"] locals_ = wait_for_debug_request( kernel_with_debug, @@ -280,6 +278,7 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): def test_convert_to_long_pathname(): - if sys.platform == 'win32': + if sys.platform == "win32": from ipykernel.compiler import _convert_to_long_pathname + _convert_to_long_pathname(__file__) diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 78806a30a..89c02565f 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -3,19 +3,17 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import json import os import sys import time -import json - from contextlib import contextmanager -from subprocess import Popen, PIPE -from flaky import flaky +from subprocess import PIPE, Popen +from flaky import flaky from jupyter_client import BlockingKernelClient from jupyter_core import paths - SETUP_TIMEOUT = 60 TIMEOUT = 15 @@ -40,17 +38,19 @@ def connection_file_ready(connection_file): except ValueError: return False - kernel = Popen([sys.executable, '-c', cmd], stdout=PIPE, stderr=PIPE, encoding="utf-8") + kernel = Popen([sys.executable, "-c", cmd], stdout=PIPE, stderr=PIPE, encoding="utf-8") try: connection_file = os.path.join( paths.jupyter_runtime_dir(), - 'kernel-%i.json' % kernel.pid, + "kernel-%i.json" % kernel.pid, ) # wait for connection file to exist, timeout after 5s tic = time.time() - while not connection_file_ready(connection_file) \ - and kernel.poll() is None \ - and time.time() < tic + SETUP_TIMEOUT: + while ( + not connection_file_ready(connection_file) + and kernel.poll() is None + and time.time() < tic + SETUP_TIMEOUT + ): time.sleep(0.1) # Wait 100ms for the writing to finish @@ -80,96 +80,102 @@ def connection_file_ready(connection_file): @flaky(max_runs=3) def test_embed_kernel_basic(): """IPython.embed_kernel() is basically functional""" - cmd = '\n'.join([ - 'from IPython import embed_kernel', - 'def go():', - ' a=5', - ' b="hi there"', - ' embed_kernel()', - 'go()', - '', - ]) + cmd = "\n".join( + [ + "from IPython import embed_kernel", + "def go():", + " a=5", + ' b="hi there"', + " embed_kernel()", + "go()", + "", + ] + ) with setup_kernel(cmd) as client: # oinfo a (int) client.inspect("a") msg = client.get_shell_msg(timeout=TIMEOUT) - content = msg['content'] - assert content['found'] + content = msg["content"] + assert content["found"] client.execute("c=a*2") msg = client.get_shell_msg(timeout=TIMEOUT) - content = msg['content'] - assert content['status'] == 'ok' + content = msg["content"] + assert content["status"] == "ok" # oinfo c (should be 10) client.inspect("c") msg = client.get_shell_msg(timeout=TIMEOUT) - content = msg['content'] - assert content['found'] - text = content['data']['text/plain'] - assert '10' in text + content = msg["content"] + assert content["found"] + text = content["data"]["text/plain"] + assert "10" in text @flaky(max_runs=3) def test_embed_kernel_namespace(): """IPython.embed_kernel() inherits calling namespace""" - cmd = '\n'.join([ - 'from IPython import embed_kernel', - 'def go():', - ' a=5', - ' b="hi there"', - ' embed_kernel()', - 'go()', - '', - ]) + cmd = "\n".join( + [ + "from IPython import embed_kernel", + "def go():", + " a=5", + ' b="hi there"', + " embed_kernel()", + "go()", + "", + ] + ) with setup_kernel(cmd) as client: # oinfo a (int) client.inspect("a") msg = client.get_shell_msg(timeout=TIMEOUT) - content = msg['content'] - assert content['found'] - text = content['data']['text/plain'] - assert '5' in text + content = msg["content"] + assert content["found"] + text = content["data"]["text/plain"] + assert "5" in text # oinfo b (str) client.inspect("b") msg = client.get_shell_msg(timeout=TIMEOUT) - content = msg['content'] - assert content['found'] - text = content['data']['text/plain'] - assert 'hi there' in text + content = msg["content"] + assert content["found"] + text = content["data"]["text/plain"] + assert "hi there" in text # oinfo c (undefined) client.inspect("c") msg = client.get_shell_msg(timeout=TIMEOUT) - content = msg['content'] - assert not content['found'] + content = msg["content"] + assert not content["found"] + @flaky(max_runs=3) def test_embed_kernel_reentrant(): """IPython.embed_kernel() can be called multiple times""" - cmd = '\n'.join([ - 'from IPython import embed_kernel', - 'count = 0', - 'def go():', - ' global count', - ' embed_kernel()', - ' count = count + 1', - '', - 'while True:' - ' go()', - '', - ]) + cmd = "\n".join( + [ + "from IPython import embed_kernel", + "count = 0", + "def go():", + " global count", + " embed_kernel()", + " count = count + 1", + "", + "while True:" " go()", + "", + ] + ) with setup_kernel(cmd) as client: for i in range(5): client.inspect("count") msg = client.get_shell_msg(timeout=TIMEOUT) - content = msg['content'] - assert content['found'] - text = content['data']['text/plain'] + content = msg["content"] + assert content["found"] + text = content["data"]["text/plain"] assert str(i) in text # exit from embed_kernel diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index 1f25d97e2..c0db7ff66 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -5,7 +5,7 @@ import pytest import tornado -from .utils import flush_channels, start_new_kernel, execute +from .utils import execute, flush_channels, start_new_kernel KC = KM = None @@ -31,15 +31,15 @@ def teardown(): @pytest.mark.skipif(tornado.version_info < (5,), reason="only relevant on tornado 5") def test_asyncio_interrupt(): flush_channels(KC) - msg_id, content = execute('%gui asyncio', KC) - assert content['status'] == 'ok', content + msg_id, content = execute("%gui asyncio", KC) + assert content["status"] == "ok", content flush_channels(KC) msg_id, content = execute(async_code, KC) - assert content['status'] == 'ok', content + assert content["status"] == "ok", content KM.interrupt_kernel() flush_channels(KC) msg_id, content = execute(async_code, KC) - assert content['status'] == 'ok' + assert content["status"] == "ok" diff --git a/ipykernel/tests/test_heartbeat.py b/ipykernel/tests/test_heartbeat.py index 9340abd7f..7847595e8 100644 --- a/ipykernel/tests/test_heartbeat.py +++ b/ipykernel/tests/test_heartbeat.py @@ -14,7 +14,7 @@ def test_port_bind_failure_raises(): heart = Heartbeat(None) - with patch.object(heart, '_try_bind_socket') as mock_try_bind: + with patch.object(heart, "_try_bind_socket") as mock_try_bind: mock_try_bind.side_effect = zmq.ZMQError(-100, "fails for unknown error types") with pytest.raises(zmq.ZMQError): heart._bind_socket() @@ -23,7 +23,7 @@ def test_port_bind_failure_raises(): def test_port_bind_success(): heart = Heartbeat(None) - with patch.object(heart, '_try_bind_socket') as mock_try_bind: + with patch.object(heart, "_try_bind_socket") as mock_try_bind: heart._bind_socket() assert mock_try_bind.call_count == 1 @@ -37,10 +37,10 @@ def test_port_bind_failure_recovery(): try: heart = Heartbeat(None) - with patch.object(heart, '_try_bind_socket') as mock_try_bind: + with patch.object(heart, "_try_bind_socket") as mock_try_bind: mock_try_bind.side_effect = [ zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind unix"), - zmq.ZMQError(errno.WSAEADDRINUSE, "fails for non-bind windows") + zmq.ZMQError(errno.WSAEADDRINUSE, "fails for non-bind windows"), ] + [0] * 100 # Shouldn't raise anything as retries will kick in heart._bind_socket() @@ -52,7 +52,7 @@ def test_port_bind_failure_recovery(): def test_port_bind_failure_gives_up_retries(): heart = Heartbeat(None) - with patch.object(heart, '_try_bind_socket') as mock_try_bind: + with patch.object(heart, "_try_bind_socket") as mock_try_bind: mock_try_bind.side_effect = zmq.ZMQError(errno.EADDRINUSE, "fails for non-bind") with pytest.raises(zmq.ZMQError): heart._bind_socket() diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index cad617879..7e2950976 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -2,11 +2,10 @@ import io -import zmq - import pytest - +import zmq from jupyter_client.session import Session + from ipykernel.iostream import IOPubThread, OutStream @@ -18,7 +17,7 @@ def test_io_api(): thread = IOPubThread(pub) thread.start() - stream = OutStream(session, thread, 'stdout') + stream = OutStream(session, thread, "stdout") # cleanup unused zmq objects before we start testing thread.stop() @@ -40,7 +39,8 @@ def test_io_api(): with pytest.raises(io.UnsupportedOperation): stream.tell() with pytest.raises(TypeError): - stream.write(b'') + stream.write(b"") + def test_io_isatty(): session = Session() @@ -49,5 +49,5 @@ def test_io_isatty(): thread = IOPubThread(pub) thread.start() - stream = OutStream(session, thread, 'stdout', isatty=True) + stream = OutStream(session, thread, "stdout", isatty=True) assert stream.isatty() diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index aaf9fdfad..0d8edbb71 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -3,19 +3,16 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from binascii import a2b_base64 import json - -from datetime import datetime import numbers +from binascii import a2b_base64 +from datetime import datetime import pytest - from jupyter_client._version import version_info as jupyter_client_version from .. import jsonutil -from ..jsonutil import json_clean, encode_images - +from ..jsonutil import encode_images, json_clean JUPYTER_CLIENT_MAJOR_VERSION = jupyter_client_version[0] @@ -23,12 +20,16 @@ class MyInt: def __int__(self): return 389 + + numbers.Integral.register(MyInt) class MyFloat: def __float__(self): return 3.14 + + numbers.Real.register(MyFloat) @@ -36,25 +37,26 @@ def __float__(self): def test(): # list of input/expected output. Use None for the expected output if it # can be the same as the input. - pairs = [(1, None), # start with scalars - (1.0, None), - ('a', None), - (True, None), - (False, None), - (None, None), - # Containers - ([1, 2], None), - ((1, 2), [1, 2]), - ({1, 2}, [1, 2]), - (dict(x=1), None), - ({'x': 1, 'y':[1,2,3], '1':'int'}, None), - # More exotic objects - ((x for x in range(3)), [0, 1, 2]), - (iter([1, 2]), [1, 2]), - (datetime(1991, 7, 3, 12, 00), "1991-07-03T12:00:00.000000"), - (MyFloat(), 3.14), - (MyInt(), 389) - ] + pairs = [ + (1, None), # start with scalars + (1.0, None), + ("a", None), + (True, None), + (False, None), + (None, None), + # Containers + ([1, 2], None), + ((1, 2), [1, 2]), + ({1, 2}, [1, 2]), + (dict(x=1), None), + ({"x": 1, "y": [1, 2, 3], "1": "int"}, None), + # More exotic objects + ((x for x in range(3)), [0, 1, 2]), + (iter([1, 2]), [1, 2]), + (datetime(1991, 7, 3, 12, 00), "1991-07-03T12:00:00.000000"), + (MyFloat(), 3.14), + (MyInt(), 389), + ] for val, jval in pairs: if jval is None: @@ -69,16 +71,16 @@ def test(): @pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op") def test_encode_images(): # invalid data, but the header and footer are from real files - pngdata = b'\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82' - jpegdata = b'\xff\xd8\xff\xe0\x00\x10JFIFblahblahjpeg(\xa0\x0f\xff\xd9' - pdfdata = b'%PDF-1.\ntrailer<>]>>>>>>' - bindata = b'\xff\xff\xff\xff' + pngdata = b"\x89PNG\r\n\x1a\nblahblahnotactuallyvalidIEND\xaeB`\x82" + jpegdata = b"\xff\xd8\xff\xe0\x00\x10JFIFblahblahjpeg(\xa0\x0f\xff\xd9" + pdfdata = b"%PDF-1.\ntrailer<>]>>>>>>" + bindata = b"\xff\xff\xff\xff" fmt = { - 'image/png' : pngdata, - 'image/jpeg' : jpegdata, - 'application/pdf' : pdfdata, - 'application/unrecognized': bindata, + "image/png": pngdata, + "image/jpeg": jpegdata, + "application/pdf": pdfdata, + "application/unrecognized": bindata, } encoded = json_clean(encode_images(fmt)) for key, value in fmt.items(): @@ -92,17 +94,19 @@ def test_encode_images(): decoded = a2b_base64(encoded[key]) assert decoded == value + @pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op") def test_lambda(): with pytest.raises(ValueError): - json_clean(lambda : 1) + json_clean(lambda: 1) @pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op") def test_exception(): - bad_dicts = [{1:'number', '1':'string'}, - {True:'bool', 'True':'string'}, - ] + bad_dicts = [ + {1: "number", "1": "string"}, + {True: "bool", "True": "string"}, + ] for d in bad_dicts: with pytest.raises(ValueError): json_clean(d) @@ -110,6 +114,6 @@ def test_exception(): @pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op") def test_unicode_dict(): - data = {'üniço∂e': 'üniço∂e'} + data = {"üniço∂e": "üniço∂e"} clean = jsonutil.json_clean(data) assert data == clean diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 896ca52ca..7118f0a4f 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -13,16 +13,21 @@ from subprocess import Popen from tempfile import TemporaryDirectory -from flaky import flaky +import IPython import psutil import pytest - -import IPython +from flaky import flaky from IPython.paths import locate_profile from .utils import ( - new_kernel, kernel, TIMEOUT, assemble_output, execute, - flush_channels, wait_for_idle, get_reply, + TIMEOUT, + assemble_output, + execute, + flush_channels, + get_reply, + kernel, + new_kernel, + wait_for_idle, ) @@ -36,25 +41,24 @@ def _check_master(kc, expected=True, stream="stdout"): def _check_status(content): """If status=error, show the traceback""" - if content['status'] == 'error': - assert False, ''.join(['\n'] + content['traceback']) + if content["status"] == "error": + assert False, "".join(["\n"] + content["traceback"]) # printing tests + def test_simple_print(): """simple print statement in kernel""" with kernel() as kc: msg_id, content = execute(kc=kc, code="print('hi')") stdout, stderr = assemble_output(kc.get_iopub_msg) - assert stdout == 'hi\n' - assert stderr == '' + assert stdout == "hi\n" + assert stderr == "" _check_master(kc, expected=True) -@pytest.mark.skip( - reason="Currently don't capture during test as pytest does its own capturing" -) +@pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing") def test_capture_fd(): """simple print statement in kernel""" with kernel() as kc: @@ -66,9 +70,7 @@ def test_capture_fd(): _check_master(kc, expected=True) -@pytest.mark.skip( - reason="Currently don't capture during test as pytest does its own capturing" -) +@pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing") def test_subprocess_peek_at_stream_fileno(): """""" with kernel() as kc: @@ -92,26 +94,25 @@ def test_sys_path(): sys.stderr.write(stderr) sys_path = ast.literal_eval(stdout.strip()) - assert '' in sys_path + assert "" in sys_path def test_sys_path_profile_dir(): """test that sys.path doesn't get messed up when `--profile-dir` is specified""" - with new_kernel(['--profile-dir', locate_profile('default')]) as kc: + with new_kernel(["--profile-dir", locate_profile("default")]) as kc: msg_id, content = execute(kc=kc, code="import sys; print(repr(sys.path))") stdout, stderr = assemble_output(kc.get_iopub_msg) # for error-output on failure sys.stderr.write(stderr) sys_path = ast.literal_eval(stdout.strip()) - assert '' in sys_path + assert "" in sys_path @flaky(max_runs=3) @pytest.mark.skipif( - sys.platform == "win32" - or (sys.platform == "darwin" and sys.version_info >= (3, 8)), + sys.platform == "win32" or (sys.platform == "darwin" and sys.version_info >= (3, 8)), reason="subprocess prints fail on Windows and MacOS Python 3.8+", ) def test_subprocess_print(): @@ -121,14 +122,16 @@ def test_subprocess_print(): _check_master(kc, expected=True) flush_channels(kc) np = 5 - code = '\n'.join([ - "import time", - "import multiprocessing as mp", - "pool = [mp.Process(target=print, args=('hello', i,)) for i in range(%i)]" % np, - "for p in pool: p.start()", - "for p in pool: p.join()", - "time.sleep(0.5)," - ]) + code = "\n".join( + [ + "import time", + "import multiprocessing as mp", + "pool = [mp.Process(target=print, args=('hello', i,)) for i in range(%i)]" % np, + "for p in pool: p.start()", + "for p in pool: p.join()", + "time.sleep(0.5),", + ] + ) msg_id, content = execute(kc=kc, code=code) stdout, stderr = assemble_output(kc.get_iopub_msg) @@ -146,17 +149,19 @@ def test_subprocess_noprint(): with kernel() as kc: np = 5 - code = '\n'.join([ - "import multiprocessing as mp", - "pool = [mp.Process(target=range, args=(i,)) for i in range(%i)]" % np, - "for p in pool: p.start()", - "for p in pool: p.join()" - ]) + code = "\n".join( + [ + "import multiprocessing as mp", + "pool = [mp.Process(target=range, args=(i,)) for i in range(%i)]" % np, + "for p in pool: p.start()", + "for p in pool: p.join()", + ] + ) msg_id, content = execute(kc=kc, code=code) stdout, stderr = assemble_output(kc.get_iopub_msg) - assert stdout == '' - assert stderr == '' + assert stdout == "" + assert stderr == "" _check_master(kc, expected=True) _check_master(kc, expected=True, stream="stderr") @@ -164,24 +169,25 @@ def test_subprocess_noprint(): @flaky(max_runs=3) @pytest.mark.skipif( - sys.platform == "win32" - or (sys.platform == "darwin" and sys.version_info >= (3, 8)), + sys.platform == "win32" or (sys.platform == "darwin" and sys.version_info >= (3, 8)), reason="subprocess prints fail on Windows and MacOS Python 3.8+", ) def test_subprocess_error(): """error in mp.Process doesn't crash""" with new_kernel() as kc: - code = '\n'.join([ - "import multiprocessing as mp", - "p = mp.Process(target=int, args=('hi',))", - "p.start()", - "p.join()", - ]) + code = "\n".join( + [ + "import multiprocessing as mp", + "p = mp.Process(target=int, args=('hi',))", + "p.start()", + "p.join()", + ] + ) msg_id, content = execute(kc=kc, code=code) stdout, stderr = assemble_output(kc.get_iopub_msg) - assert stdout == '' + assert stdout == "" assert "ValueError" in stderr _check_master(kc, expected=True) @@ -190,6 +196,7 @@ def test_subprocess_error(): # raw_input tests + def test_raw_input(): """test input""" with kernel() as kc: @@ -200,13 +207,13 @@ def test_raw_input(): code = 'print({input_f}("{theprompt}"))'.format(**locals()) msg_id = kc.execute(code, allow_stdin=True) msg = kc.get_stdin_msg(timeout=TIMEOUT) - assert msg['header']['msg_type'] == 'input_request' - content = msg['content'] - assert content['prompt'] == theprompt + assert msg["header"]["msg_type"] == "input_request" + content = msg["content"] + assert content["prompt"] == theprompt text = "some text" kc.input(text) reply = kc.get_shell_msg(timeout=TIMEOUT) - assert reply['content']['status'] == 'ok' + assert reply["content"]["status"] == "ok" stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == text + "\n" @@ -215,30 +222,33 @@ def test_save_history(): # Saving history from the kernel with %hist -f was failing because of # unicode problems on Python 2. with kernel() as kc, TemporaryDirectory() as td: - file = os.path.join(td, 'hist.out') - execute('a=1', kc=kc) + file = os.path.join(td, "hist.out") + execute("a=1", kc=kc) wait_for_idle(kc) execute('b="abcþ"', kc=kc) wait_for_idle(kc) _, reply = execute("%hist -f " + file, kc=kc) - assert reply['status'] == 'ok' - with open(file, encoding='utf-8') as f: + assert reply["status"] == "ok" + with open(file, encoding="utf-8") as f: content = f.read() - assert 'a=1' in content + assert "a=1" in content assert 'b="abcþ"' in content def test_smoke_faulthandler(): - faulthadler = pytest.importorskip('faulthandler', reason='this test needs faulthandler') + faulthadler = pytest.importorskip("faulthandler", reason="this test needs faulthandler") with kernel() as kc: # Note: faulthandler.register is not available on windows. - code = '\n'.join([ - 'import sys', - 'import faulthandler', - 'import signal', - 'faulthandler.enable()', - 'if not sys.platform.startswith("win32"):', - ' faulthandler.register(signal.SIGTERM)']) + code = "\n".join( + [ + "import sys", + "import faulthandler", + "import signal", + "faulthandler.enable()", + 'if not sys.platform.startswith("win32"):', + " faulthandler.register(signal.SIGTERM)", + ] + ) _, reply = execute(code, kc=kc) assert reply["status"] == "ok", reply.get("traceback", "") @@ -257,67 +267,64 @@ def test_is_complete(): with kernel() as kc: # There are more test cases for this in core - here we just check # that the kernel exposes the interface correctly. - kc.is_complete('2+2') + kc.is_complete("2+2") reply = kc.get_shell_msg(timeout=TIMEOUT) - assert reply['content']['status'] == 'complete' + assert reply["content"]["status"] == "complete" # SyntaxError - kc.is_complete('raise = 2') + kc.is_complete("raise = 2") reply = kc.get_shell_msg(timeout=TIMEOUT) - assert reply['content']['status'] == 'invalid' + assert reply["content"]["status"] == "invalid" - kc.is_complete('a = [1,\n2,') + kc.is_complete("a = [1,\n2,") reply = kc.get_shell_msg(timeout=TIMEOUT) - assert reply['content']['status'] == 'incomplete' - assert reply['content']['indent'] == '' + assert reply["content"]["status"] == "incomplete" + assert reply["content"]["indent"] == "" # Cell magic ends on two blank lines for console UIs - kc.is_complete('%%timeit\na\n\n') + kc.is_complete("%%timeit\na\n\n") reply = kc.get_shell_msg(timeout=TIMEOUT) - assert reply['content']['status'] == 'complete' + assert reply["content"]["status"] == "complete" @pytest.mark.skipif(sys.platform != "win32", reason="only run on Windows") def test_complete(): with kernel() as kc: - execute('a = 1', kc=kc) + execute("a = 1", kc=kc) wait_for_idle(kc) - cell = 'import IPython\nb = a.' + cell = "import IPython\nb = a." kc.complete(cell) reply = kc.get_shell_msg(timeout=TIMEOUT) - c = reply['content'] - assert c['status'] == 'ok' - start = cell.find('a.') + c = reply["content"] + assert c["status"] == "ok" + start = cell.find("a.") end = start + 2 - assert c['cursor_end'] == cell.find('a.') + 2 - assert c['cursor_start'] <= end + assert c["cursor_end"] == cell.find("a.") + 2 + assert c["cursor_start"] <= end # there are many right answers for cursor_start, # so verify application of the completion # rather than the value of cursor_start - matches = c['matches'] + matches = c["matches"] assert matches for m in matches: - completed = cell[:c['cursor_start']] + m + completed = cell[: c["cursor_start"]] + m assert completed.startswith(cell) def test_matplotlib_inline_on_import(): - pytest.importorskip('matplotlib', reason='this test requires matplotlib') + pytest.importorskip("matplotlib", reason="this test requires matplotlib") with kernel() as kc: - cell = '\n'.join([ - 'import matplotlib, matplotlib.pyplot as plt', - 'backend = matplotlib.get_backend()' - ]) - _, reply = execute(cell, - user_expressions={'backend': 'backend'}, - kc=kc) + cell = "\n".join( + ["import matplotlib, matplotlib.pyplot as plt", "backend = matplotlib.get_backend()"] + ) + _, reply = execute(cell, user_expressions={"backend": "backend"}, kc=kc) _check_status(reply) - backend_bundle = reply['user_expressions']['backend'] + backend_bundle = reply["user_expressions"]["backend"] _check_status(backend_bundle) - assert 'backend_inline' in backend_bundle['data']['text/plain'] + assert "backend_inline" in backend_bundle["data"]["text/plain"] def test_message_order(): @@ -325,7 +332,7 @@ def test_message_order(): with kernel() as kc: _, reply = execute("a = 1", kc=kc) _check_status(reply) - offset = reply['execution_count'] + 1 + offset = reply["execution_count"] + 1 cell = "a += 1\na" msg_ids = [] # submit N executions as fast as we can @@ -334,9 +341,9 @@ def test_message_order(): # check message-handling order for i, msg_id in enumerate(msg_ids, offset): reply = kc.get_shell_msg(timeout=TIMEOUT) - _check_status(reply['content']) - assert reply['content']['execution_count'] == i - assert reply['parent_header']['msg_id'] == msg_id + _check_status(reply["content"]) + assert reply["content"]["execution_count"] == i + assert reply["parent_header"]["msg_id"] == msg_id @pytest.mark.skipif( @@ -345,29 +352,29 @@ def test_message_order(): ) def test_unc_paths(): with kernel() as kc, TemporaryDirectory() as td: - drive_file_path = os.path.join(td, 'unc.txt') - with open(drive_file_path, 'w+') as f: - f.write('# UNC test') - unc_root = '\\\\localhost\\C$' + drive_file_path = os.path.join(td, "unc.txt") + with open(drive_file_path, "w+") as f: + f.write("# UNC test") + unc_root = "\\\\localhost\\C$" file_path = os.path.splitdrive(os.path.dirname(drive_file_path))[1] unc_file_path = os.path.join(unc_root, file_path[1:]) kc.execute(f"cd {unc_file_path:s}") reply = kc.get_shell_msg(timeout=TIMEOUT) - assert reply['content']['status'] == 'ok' + assert reply["content"]["status"] == "ok" out, err = assemble_output(kc.get_iopub_msg) assert unc_file_path in out flush_channels(kc) kc.execute(code="ls") reply = kc.get_shell_msg(timeout=TIMEOUT) - assert reply['content']['status'] == 'ok' + assert reply["content"]["status"] == "ok" out, err = assemble_output(kc.get_iopub_msg) - assert 'unc.txt' in out + assert "unc.txt" in out kc.execute(code="cd") reply = kc.get_shell_msg(timeout=TIMEOUT) - assert reply['content']['status'] == 'ok' + assert reply["content"]["status"] == "ok" @pytest.mark.skipif( @@ -378,12 +385,12 @@ def test_shutdown(): """Kernel exits after polite shutdown_request""" with new_kernel() as kc: km = kc.parent - execute('a = 1', kc=kc) + execute("a = 1", kc=kc) wait_for_idle(kc) kc.shutdown() - for i in range(300): # 30s timeout + for i in range(300): # 30s timeout if km.is_alive(): - time.sleep(.1) + time.sleep(0.1) else: break assert not km.is_alive() @@ -402,19 +409,15 @@ def test_interrupt_during_input(): time.sleep(1) # Make sure it's actually waiting for input. km.interrupt_kernel() from .test_message_spec import validate_message + # If we failed to interrupt interrupt, this will timeout: reply = get_reply(kc, msg_id, TIMEOUT) - validate_message(reply, 'execute_reply', msg_id) + validate_message(reply, "execute_reply", msg_id) -@pytest.mark.skipif( - os.name == "nt", - reason="Message based interrupt not supported on Windows" -) +@pytest.mark.skipif(os.name == "nt", reason="Message based interrupt not supported on Windows") def test_interrupt_with_message(): - """ - - """ + """ """ with new_kernel() as kc: km = kc.parent km.kernel_spec.interrupt_mode = "message" @@ -422,9 +425,10 @@ def test_interrupt_with_message(): time.sleep(1) # Make sure it's actually waiting for input. km.interrupt_kernel() from .test_message_spec import validate_message + # If we failed to interrupt interrupt, this will timeout: reply = get_reply(kc, msg_id, TIMEOUT) - validate_message(reply, 'execute_reply', msg_id) + validate_message(reply, "execute_reply", msg_id) @pytest.mark.skipif( @@ -447,12 +451,13 @@ def test_interrupt_during_pdb_set_trace(): time.sleep(1) # Make sure it's actually waiting for input. km.interrupt_kernel() from .test_message_spec import validate_message + # If we failed to interrupt interrupt, this will timeout: reply = get_reply(kc, msg_id, TIMEOUT) - validate_message(reply, 'execute_reply', msg_id) + validate_message(reply, "execute_reply", msg_id) # If we failed to interrupt interrupt, this will timeout: reply = get_reply(kc, msg_id2, TIMEOUT) - validate_message(reply, 'execute_reply', msg_id2) + validate_message(reply, "execute_reply", msg_id2) def test_control_thread_priority(): @@ -548,9 +553,7 @@ def test_shutdown_subprocesses(): expressions = reply["user_expressions"] kernel_process = psutil.Process(int(expressions["pid"]["data"]["text/plain"])) child_pg = psutil.Process(int(expressions["child_pg"]["data"]["text/plain"])) - child_newpg = psutil.Process( - int(expressions["child_newpg"]["data"]["text/plain"]) - ) + child_newpg = psutil.Process(int(expressions["child_newpg"]["data"]["text/plain"])) wait_for_idle(kc) kc.shutdown() @@ -564,7 +567,7 @@ def test_shutdown_subprocesses(): # child in the process group shut down assert not child_pg.is_running() # child outside the process group was not shut down (unix only) - if os.name != 'nt': + if os.name != "nt": assert child_newpg.is_running() try: child_newpg.terminate() diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index 94321a4d3..31edff5bc 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -8,38 +8,31 @@ import tempfile from unittest import mock +import pytest from jupyter_core.paths import jupyter_data_dir from ipykernel.kernelspec import ( - make_ipkernel_cmd, - get_kernel_dict, - write_kernel_spec, - install, - InstallIPythonKernelSpecApp, KERNEL_NAME, RESOURCES, + InstallIPythonKernelSpecApp, + get_kernel_dict, + install, + make_ipkernel_cmd, + write_kernel_spec, ) -import pytest - pjoin = os.path.join def test_make_ipkernel_cmd(): cmd = make_ipkernel_cmd() - assert cmd == [ - sys.executable, - '-m', - 'ipykernel_launcher', - '-f', - '{connection_file}' - ] + assert cmd == [sys.executable, "-m", "ipykernel_launcher", "-f", "{connection_file}"] def assert_kernel_dict(d): - assert d['argv'] == make_ipkernel_cmd() - assert d['display_name'] == 'Python %i (ipykernel)' % sys.version_info[0] - assert d['language'] == 'python' + assert d["argv"] == make_ipkernel_cmd() + assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0] + assert d["language"] == "python" def test_get_kernel_dict(): @@ -62,9 +55,9 @@ def assert_is_spec(path): for fname in os.listdir(RESOURCES): dst = pjoin(path, fname) assert os.path.exists(dst) - kernel_json = pjoin(path, 'kernel.json') + kernel_json = pjoin(path, "kernel.json") assert os.path.exists(kernel_json) - with open(kernel_json, encoding='utf8') as f: + with open(kernel_json, encoding="utf8") as f: json.load(f) @@ -95,31 +88,29 @@ def test_install_kernelspec(): def test_install_user(): tmp = tempfile.mkdtemp() - with mock.patch.dict(os.environ, {'HOME': tmp}): + with mock.patch.dict(os.environ, {"HOME": tmp}): install(user=True) data_dir = jupyter_data_dir() - assert_is_spec(os.path.join(data_dir, 'kernels', KERNEL_NAME)) + assert_is_spec(os.path.join(data_dir, "kernels", KERNEL_NAME)) def test_install(): system_jupyter_dir = tempfile.mkdtemp() - with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', - [system_jupyter_dir]): + with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]): install() - assert_is_spec(os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME)) + assert_is_spec(os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME)) def test_install_profile(): system_jupyter_dir = tempfile.mkdtemp() - with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', - [system_jupyter_dir]): + with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]): install(profile="Test") - spec = os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME, "kernel.json") + spec = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json") with open(spec) as f: spec = json.load(f) assert spec["display_name"].endswith(" [profile=Test]") @@ -129,34 +120,28 @@ def test_install_profile(): def test_install_display_name_overrides_profile(): system_jupyter_dir = tempfile.mkdtemp() - with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', - [system_jupyter_dir]): + with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]): install(display_name="Display", profile="Test") - spec = os.path.join(system_jupyter_dir, 'kernels', KERNEL_NAME, "kernel.json") + spec = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json") with open(spec) as f: spec = json.load(f) assert spec["display_name"] == "Display" -@pytest.mark.parametrize("env", [ - None, - dict(spam="spam"), - dict(spam="spam", foo='bar') -]) +@pytest.mark.parametrize("env", [None, dict(spam="spam"), dict(spam="spam", foo="bar")]) def test_install_env(tmp_path, env): # python 3.5 // tmp_path must be converted to str - with mock.patch('jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH', - [str(tmp_path)]): + with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [str(tmp_path)]): install(env=env) - spec = tmp_path / 'kernels' / KERNEL_NAME / "kernel.json" + spec = tmp_path / "kernels" / KERNEL_NAME / "kernel.json" with spec.open() as f: spec = json.load(f) if env: - assert len(env) == len(spec['env']) + assert len(env) == len(spec["env"]) for k, v in env.items(): - assert spec['env'][k] == v + assert spec["env"][k] == v else: - assert 'env' not in spec + assert "env" not in spec diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index f550b898e..1a9cc58c1 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -8,29 +8,27 @@ from distutils.version import LooseVersion as V from queue import Empty -import pytest - import jupyter_client +import pytest +from traitlets import Bool, Dict, Enum, HasTraits, Integer, List, TraitError, Unicode -from traitlets import ( - HasTraits, TraitError, Bool, Unicode, Dict, Integer, List, Enum -) - -from .utils import (TIMEOUT, start_global_kernel, flush_channels, execute, - get_reply, ) +from .utils import TIMEOUT, execute, flush_channels, get_reply, start_global_kernel -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Globals -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- KC = None + def setup(): global KC KC = start_global_kernel() -#----------------------------------------------------------------------------- + +# ----------------------------------------------------------------------------- # Message Spec References -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- + class Reference(HasTraits): @@ -59,9 +57,9 @@ def check(self, d): class Version(Unicode): def __init__(self, *args, **kwargs): - self.min = kwargs.pop('min', None) - self.max = kwargs.pop('max', None) - kwargs['default_value'] = self.min + self.min = kwargs.pop("min", None) + self.max = kwargs.pop("max", None) + kwargs["default_value"] = self.min super().__init__(*args, **kwargs) def validate(self, obj, value): @@ -84,27 +82,31 @@ def check(self, d): if self.parent_header: RHeader().check(self.parent_header) + class RHeader(Reference): msg_id = Unicode() msg_type = Unicode() session = Unicode() username = Unicode() - version = Version(min='5.0') + version = Version(min="5.0") + + +mime_pat = re.compile(r"^[\w\-\+\.]+/[\w\-\+\.]+$") -mime_pat = re.compile(r'^[\w\-\+\.]+/[\w\-\+\.]+$') class MimeBundle(Reference): metadata = Dict() data = Dict() + def _data_changed(self, name, old, new): - for k,v in new.items(): + for k, v in new.items(): assert mime_pat.match(k) assert isinstance(v, str) # shell replies class Reply(Reference): - status = Enum(('ok', 'error'), default_value='ok') + status = Enum(("ok", "error"), default_value="ok") class ExecuteReply(Reply): @@ -112,28 +114,28 @@ class ExecuteReply(Reply): def check(self, d): Reference.check(self, d) - if d['status'] == 'ok': + if d["status"] == "ok": ExecuteReplyOkay().check(d) - elif d['status'] == 'error': + elif d["status"] == "error": ExecuteReplyError().check(d) - elif d['status'] == 'aborted': + elif d["status"] == "aborted": ExecuteReplyAborted().check(d) class ExecuteReplyOkay(Reply): - status = Enum(('ok',)) + status = Enum(("ok",)) user_expressions = Dict() class ExecuteReplyError(Reply): - status = Enum(('error',)) + status = Enum(("error",)) ename = Unicode() evalue = Unicode() traceback = List(Unicode()) class ExecuteReplyAborted(Reply): - status = Enum(('aborted',)) + status = Enum(("aborted",)) class InspectReply(Reply, MimeBundle): @@ -148,7 +150,7 @@ class ArgSpec(Reference): class Status(Reference): - execution_state = Enum(('busy', 'idle', 'starting'), default_value='busy') + execution_state = Enum(("busy", "idle", "starting"), default_value="busy") class CompleteReply(Reply): @@ -159,20 +161,20 @@ class CompleteReply(Reply): class LanguageInfo(Reference): - name = Unicode('python') + name = Unicode("python") version = Unicode(sys.version.split()[0]) class KernelInfoReply(Reply): - protocol_version = Version(min='5.0') - implementation = Unicode('ipython') - implementation_version = Version(min='2.1') + protocol_version = Version(min="5.0") + implementation = Unicode("ipython") + implementation_version = Version(min="2.1") language_info = Dict() banner = Unicode() def check(self, d): Reference.check(self, d) - LanguageInfo().check(d['language_info']) + LanguageInfo().check(d["language_info"]) class ConnectReply(Reference): @@ -188,11 +190,11 @@ class CommInfoReply(Reply): class IsCompleteReply(Reference): - status = Enum(('complete', 'incomplete', 'invalid', 'unknown'), default_value='complete') + status = Enum(("complete", "incomplete", "invalid", "unknown"), default_value="complete") def check(self, d): Reference.check(self, d) - if d['status'] == 'incomplete': + if d["status"] == "incomplete": IsCompleteReplyIncomplete().check(d) @@ -202,6 +204,7 @@ class IsCompleteReplyIncomplete(Reference): # IOPub messages + class ExecuteInput(Reference): code = Unicode() execution_count = Integer() @@ -209,11 +212,12 @@ class ExecuteInput(Reference): class Error(ExecuteReplyError): """Errors are the same as ExecuteReply, but without status""" - status = None # no status field + + status = None # no status field class Stream(Reference): - name = Enum(('stdout', 'stderr'), default_value='stdout') + name = Enum(("stdout", "stderr"), default_value="stdout") text = Unicode() @@ -230,21 +234,21 @@ class HistoryReply(Reply): references = { - 'execute_reply' : ExecuteReply(), - 'inspect_reply' : InspectReply(), - 'status' : Status(), - 'complete_reply' : CompleteReply(), - 'kernel_info_reply': KernelInfoReply(), - 'connect_reply': ConnectReply(), - 'comm_info_reply': CommInfoReply(), - 'is_complete_reply': IsCompleteReply(), - 'execute_input' : ExecuteInput(), - 'execute_result' : ExecuteResult(), - 'history_reply' : HistoryReply(), - 'error' : Error(), - 'stream' : Stream(), - 'display_data' : DisplayData(), - 'header' : RHeader(), + "execute_reply": ExecuteReply(), + "inspect_reply": InspectReply(), + "status": Status(), + "complete_reply": CompleteReply(), + "kernel_info_reply": KernelInfoReply(), + "connect_reply": ConnectReply(), + "comm_info_reply": CommInfoReply(), + "is_complete_reply": IsCompleteReply(), + "execute_input": ExecuteInput(), + "execute_result": ExecuteResult(), + "history_reply": HistoryReply(), + "error": Error(), + "stream": Stream(), + "display_data": DisplayData(), + "header": RHeader(), } """ Specifications of `content` part of the reply messages. @@ -262,65 +266,66 @@ def validate_message(msg, msg_type=None, parent=None): """ RMessage().check(msg) if msg_type: - assert msg['msg_type'] == msg_type + assert msg["msg_type"] == msg_type if parent: - assert msg['parent_header']['msg_id'] == parent - content = msg['content'] - ref = references[msg['msg_type']] + assert msg["parent_header"]["msg_id"] == parent + content = msg["content"] + ref = references[msg["msg_type"]] ref.check(content) -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Tests -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Shell channel + def test_execute(): flush_channels() - msg_id = KC.execute(code='x=1') + msg_id = KC.execute(code="x=1") reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'execute_reply', msg_id) + validate_message(reply, "execute_reply", msg_id) def test_execute_silent(): flush_channels() - msg_id, reply = execute(code='x=1', silent=True) + msg_id, reply = execute(code="x=1", silent=True) # flush status=idle status = KC.get_iopub_msg(timeout=TIMEOUT) - validate_message(status, 'status', msg_id) - assert status['content']['execution_state'] == 'idle' + validate_message(status, "status", msg_id) + assert status["content"]["execution_state"] == "idle" with pytest.raises(Empty): KC.get_iopub_msg(timeout=0.1) - count = reply['execution_count'] + count = reply["execution_count"] - msg_id, reply = execute(code='x=2', silent=True) + msg_id, reply = execute(code="x=2", silent=True) # flush status=idle status = KC.get_iopub_msg(timeout=TIMEOUT) - validate_message(status, 'status', msg_id) - assert status['content']['execution_state'] == 'idle' + validate_message(status, "status", msg_id) + assert status["content"]["execution_state"] == "idle" with pytest.raises(Empty): KC.get_iopub_msg(timeout=0.1) - count_2 = reply['execution_count'] + count_2 = reply["execution_count"] assert count_2 == count def test_execute_error(): flush_channels() - msg_id, reply = execute(code='1/0') - assert reply['status'] == 'error' - assert reply['ename'] == 'ZeroDivisionError' + msg_id, reply = execute(code="1/0") + assert reply["status"] == "error" + assert reply["ename"] == "ZeroDivisionError" error = KC.get_iopub_msg(timeout=TIMEOUT) - validate_message(error, 'error', msg_id) + validate_message(error, "error", msg_id) def test_execute_inc(): @@ -359,7 +364,7 @@ def test_execute_stop_on_error(): assert reply["content"]["status"] == "aborted" # second message, too reply = KC.get_shell_msg(timeout=TIMEOUT) - assert reply['content']['status'] == 'aborted' + assert reply["content"]["status"] == "aborted" flush_channels() @@ -367,101 +372,105 @@ def test_execute_stop_on_error(): KC.execute(code='print("Hello")') KC.get_shell_msg(timeout=TIMEOUT) reply = KC.get_shell_msg(timeout=TIMEOUT) - assert reply['content']['status'] == 'ok' + assert reply["content"]["status"] == "ok" def test_non_execute_stop_on_error(): """test that non-execute_request's are not aborted after an error""" flush_channels() - fail = '\n'.join([ - # sleep to ensure subsequent message is waiting in the queue to be aborted - 'import time', - 'time.sleep(0.5)', - 'raise ValueError', - ]) + fail = "\n".join( + [ + # sleep to ensure subsequent message is waiting in the queue to be aborted + "import time", + "time.sleep(0.5)", + "raise ValueError", + ] + ) KC.execute(code=fail) KC.kernel_info() KC.comm_info() KC.inspect(code="print") - reply = KC.get_shell_msg(timeout=TIMEOUT) # execute - assert reply['content']['status'] == 'error' - reply = KC.get_shell_msg(timeout=TIMEOUT) # kernel_info - assert reply['content']['status'] == 'ok' - reply = KC.get_shell_msg(timeout=TIMEOUT) # comm_info - assert reply['content']['status'] == 'ok' - reply = KC.get_shell_msg(timeout=TIMEOUT) # inspect - assert reply['content']['status'] == 'ok' + reply = KC.get_shell_msg(timeout=TIMEOUT) # execute + assert reply["content"]["status"] == "error" + reply = KC.get_shell_msg(timeout=TIMEOUT) # kernel_info + assert reply["content"]["status"] == "ok" + reply = KC.get_shell_msg(timeout=TIMEOUT) # comm_info + assert reply["content"]["status"] == "ok" + reply = KC.get_shell_msg(timeout=TIMEOUT) # inspect + assert reply["content"]["status"] == "ok" def test_user_expressions(): flush_channels() - msg_id, reply = execute(code='x=1', user_expressions=dict(foo='x+1')) - user_expressions = reply['user_expressions'] - assert user_expressions == {'foo': { - 'status': 'ok', - 'data': {'text/plain': '2'}, - 'metadata': {}, - }} + msg_id, reply = execute(code="x=1", user_expressions=dict(foo="x+1")) + user_expressions = reply["user_expressions"] + assert user_expressions == { + "foo": { + "status": "ok", + "data": {"text/plain": "2"}, + "metadata": {}, + } + } def test_user_expressions_fail(): flush_channels() - msg_id, reply = execute(code='x=0', user_expressions=dict(foo='nosuchname')) - user_expressions = reply['user_expressions'] - foo = user_expressions['foo'] - assert foo['status'] == 'error' - assert foo['ename'] == 'NameError' + msg_id, reply = execute(code="x=0", user_expressions=dict(foo="nosuchname")) + user_expressions = reply["user_expressions"] + foo = user_expressions["foo"] + assert foo["status"] == "error" + assert foo["ename"] == "NameError" def test_oinfo(): flush_channels() - msg_id = KC.inspect('a') + msg_id = KC.inspect("a") reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'inspect_reply', msg_id) + validate_message(reply, "inspect_reply", msg_id) def test_oinfo_found(): flush_channels() - msg_id, reply = execute(code='a=5') + msg_id, reply = execute(code="a=5") - msg_id = KC.inspect('a') + msg_id = KC.inspect("a") reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'inspect_reply', msg_id) - content = reply['content'] - assert content['found'] - text = content['data']['text/plain'] - assert 'Type:' in text - assert 'Docstring:' in text + validate_message(reply, "inspect_reply", msg_id) + content = reply["content"] + assert content["found"] + text = content["data"]["text/plain"] + assert "Type:" in text + assert "Docstring:" in text def test_oinfo_detail(): flush_channels() - msg_id, reply = execute(code='ip=get_ipython()') + msg_id, reply = execute(code="ip=get_ipython()") - msg_id = KC.inspect('ip.object_inspect', cursor_pos=10, detail_level=1) + msg_id = KC.inspect("ip.object_inspect", cursor_pos=10, detail_level=1) reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'inspect_reply', msg_id) - content = reply['content'] - assert content['found'] - text = content['data']['text/plain'] - assert 'Signature:' in text - assert 'Source:' in text + validate_message(reply, "inspect_reply", msg_id) + content = reply["content"] + assert content["found"] + text = content["data"]["text/plain"] + assert "Signature:" in text + assert "Source:" in text def test_oinfo_not_found(): flush_channels() - msg_id = KC.inspect('dne') + msg_id = KC.inspect("dne") reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'inspect_reply', msg_id) - content = reply['content'] - assert not content['found'] + validate_message(reply, "inspect_reply", msg_id) + content = reply["content"] + assert not content["found"] def test_complete(): @@ -469,11 +478,11 @@ def test_complete(): msg_id, reply = execute(code="alpha = albert = 5") - msg_id = KC.complete('al', 2) + msg_id = KC.complete("al", 2) reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'complete_reply', msg_id) - matches = reply['content']['matches'] - for name in ('alpha', 'albert'): + validate_message(reply, "complete_reply", msg_id) + matches = reply["content"]["matches"] + for name in ("alpha", "albert"): assert name in matches @@ -482,18 +491,18 @@ def test_kernel_info_request(): msg_id = KC.kernel_info() reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'kernel_info_reply', msg_id) + validate_message(reply, "kernel_info_reply", msg_id) def test_connect_request(): flush_channels() - msg = KC.session.msg('connect_request') + msg = KC.session.msg("connect_request") KC.shell_channel.send(msg) - return msg['header']['msg_id'] + return msg["header"]["msg_id"] msg_id = KC.kernel_info() reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'connect_reply', msg_id) + validate_message(reply, "connect_reply", msg_id) @pytest.mark.skipif( @@ -504,7 +513,7 @@ def test_comm_info_request(): flush_channels() msg_id = KC.comm_info() reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'comm_info_reply', msg_id) + validate_message(reply, "comm_info_reply", msg_id) def test_single_payload(): @@ -518,19 +527,21 @@ def test_single_payload(): transform) should avoid setting multiple set_next_input). """ flush_channels() - msg_id, reply = execute(code="ip = get_ipython()\n" - "for i in range(3):\n" - " ip.set_next_input('Hello There')\n") - payload = reply['payload'] + msg_id, reply = execute( + code="ip = get_ipython()\n" "for i in range(3):\n" " ip.set_next_input('Hello There')\n" + ) + payload = reply["payload"] next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"] assert len(next_input_pls) == 1 + def test_is_complete(): flush_channels() msg_id = KC.is_complete("a = 1") reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'is_complete_reply', msg_id) + validate_message(reply, "is_complete_reply", msg_id) + def test_history_range(): flush_channels() @@ -538,11 +549,12 @@ def test_history_range(): KC.execute(code="x=1", store_history=True) KC.get_shell_msg(timeout=TIMEOUT) - msg_id = KC.history(hist_access_type = 'range', raw = True, output = True, start = 1, stop = 2, session = 0) + msg_id = KC.history(hist_access_type="range", raw=True, output=True, start=1, stop=2, session=0) reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'history_reply', msg_id) - content = reply['content'] - assert len(content['history']) == 1 + validate_message(reply, "history_reply", msg_id) + content = reply["content"] + assert len(content["history"]) == 1 + def test_history_tail(): flush_channels() @@ -550,11 +562,12 @@ def test_history_tail(): KC.execute(code="x=1", store_history=True) KC.get_shell_msg(timeout=TIMEOUT) - msg_id = KC.history(hist_access_type = 'tail', raw = True, output = True, n = 1, session = 0) + msg_id = KC.history(hist_access_type="tail", raw=True, output=True, n=1, session=0) reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'history_reply', msg_id) - content = reply['content'] - assert len(content['history']) == 1 + validate_message(reply, "history_reply", msg_id) + content = reply["content"] + assert len(content["history"]) == 1 + def test_history_search(): flush_channels() @@ -562,11 +575,14 @@ def test_history_search(): KC.execute(code="x=1", store_history=True) KC.get_shell_msg(timeout=TIMEOUT) - msg_id = KC.history(hist_access_type = 'search', raw = True, output = True, n = 1, pattern = '*', session = 0) + msg_id = KC.history( + hist_access_type="search", raw=True, output=True, n=1, pattern="*", session=0 + ) reply = get_reply(KC, msg_id, TIMEOUT) - validate_message(reply, 'history_reply', msg_id) - content = reply['content'] - assert len(content['history']) == 1 + validate_message(reply, "history_reply", msg_id) + content = reply["content"] + assert len(content["history"]) == 1 + # IOPub channel @@ -577,9 +593,9 @@ def test_stream(): msg_id, reply = execute("print('hi')") stdout = KC.get_iopub_msg(timeout=TIMEOUT) - validate_message(stdout, 'stream', msg_id) - content = stdout['content'] - assert content['text'] == 'hi\n' + validate_message(stdout, "stream", msg_id) + content = stdout["content"] + assert content["text"] == "hi\n" def test_display_data(): @@ -588,6 +604,6 @@ def test_display_data(): msg_id, reply = execute("from IPython.display import display; display(1)") display = KC.get_iopub_msg(timeout=TIMEOUT) - validate_message(display, 'display_data', parent=msg_id) - data = display['content']['data'] - assert data['text/plain'] == '1' + validate_message(display, "display_data", parent=msg_id) + data = display["content"]["data"] + assert data["text/plain"] == "1" diff --git a/ipykernel/tests/test_pickleutil.py b/ipykernel/tests/test_pickleutil.py index d81342ae0..1cba3cb99 100644 --- a/ipykernel/tests/test_pickleutil.py +++ b/ipykernel/tests/test_pickleutil.py @@ -2,16 +2,20 @@ from ipykernel.pickleutil import can, uncan + def interactive(f): - f.__module__ = '__main__' + f.__module__ = "__main__" return f + def dumps(obj): return pickle.dumps(can(obj)) + def loads(obj): return uncan(pickle.loads(obj)) + def test_no_closure(): @interactive def foo(): @@ -22,32 +26,38 @@ def foo(): bar = loads(pfoo) assert foo() == bar() + def test_generator_closure(): # this only creates a closure on Python 3 @interactive def foo(): - i = 'i' - r = [ i for j in (1,2) ] + i = "i" + r = [i for j in (1, 2)] return r pfoo = dumps(foo) bar = loads(pfoo) assert foo() == bar() + def test_nested_closure(): @interactive def foo(): - i = 'i' + i = "i" + def g(): return i + return g() pfoo = dumps(foo) bar = loads(pfoo) assert foo() == bar() + def test_closure(): - i = 'i' + i = "i" + @interactive def foo(): return i @@ -56,8 +66,9 @@ def foo(): bar = loads(pfoo) assert foo() == bar() + def test_uncan_bytes_buffer(): - data = b'data' + data = b"data" canned = can(data) canned.buffers = [memoryview(buf) for buf in canned.buffers] out = uncan(canned) diff --git a/ipykernel/tests/test_start_kernel.py b/ipykernel/tests/test_start_kernel.py index 876b5bdcc..6d6c64e47 100644 --- a/ipykernel/tests/test_start_kernel.py +++ b/ipykernel/tests/test_start_kernel.py @@ -1,7 +1,9 @@ -from .test_embed_kernel import setup_kernel -from flaky import flaky from textwrap import dedent +from flaky import flaky + +from .test_embed_kernel import setup_kernel + TIMEOUT = 15 @@ -18,10 +20,10 @@ def test_ipython_start_kernel_userns(): with setup_kernel(cmd) as client: client.inspect("tre") msg = client.get_shell_msg(timeout=TIMEOUT) - content = msg['content'] - assert content['found'] - text = content['data']['text/plain'] - assert '123' in text + content = msg["content"] + assert content["found"] + text = content["data"]["text/plain"] + assert "123" in text # user_module should be an instance of DummyMod client.execute("usermod = get_ipython().user_module") @@ -30,10 +32,10 @@ def test_ipython_start_kernel_userns(): assert content["status"] == "ok" client.inspect("usermod") msg = client.get_shell_msg(timeout=TIMEOUT) - content = msg['content'] - assert content['found'] - text = content['data']['text/plain'] - assert 'DummyMod' in text + content = msg["content"] + assert content["found"] + text = content["data"]["text/plain"] + assert "DummyMod" in text @flaky(max_runs=3) @@ -54,7 +56,7 @@ def test_ipython_start_kernel_no_userns(): assert content["status"] == "ok" client.inspect("usermod") msg = client.get_shell_msg(timeout=TIMEOUT) - content = msg['content'] - assert content['found'] - text = content['data']['text/plain'] - assert 'DummyMod' not in text + content = msg["content"] + assert content["found"] + text = content["data"]["text/plain"] + assert "DummyMod" not in text diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index 56f587e74..1a637f53e 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -3,15 +3,15 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import unittest from queue import Queue from threading import Thread -import unittest -from traitlets import Int import zmq +from jupyter_client.session import Session +from traitlets import Int from ipykernel.zmqshell import ZMQDisplayPublisher -from jupyter_client.session import Session class NoReturnDisplayHook: @@ -20,6 +20,7 @@ class NoReturnDisplayHook: the number of times an object is called, but which does *not* return a message when it is called. """ + call_count = 0 def __call__(self, obj): @@ -32,6 +33,7 @@ class ReturnDisplayHook(NoReturnDisplayHook): as its base class, but which also returns the same message when it is called. """ + def __call__(self, obj): super().__call__(obj) return obj @@ -43,6 +45,7 @@ class CounterSession(Session): the calls made to the session object by the display publisher. """ + send_count = Int(0) def send(self, *args, **kwargs): @@ -64,10 +67,7 @@ def setUp(self): self.socket = self.context.socket(zmq.PUB) self.session = CounterSession() - self.disp_pub = ZMQDisplayPublisher( - session = self.session, - pub_socket = self.socket - ) + self.disp_pub = ZMQDisplayPublisher(session=self.session, pub_socket=self.socket) def tearDown(self): """ @@ -95,14 +95,18 @@ def test_thread_local_hooks(self): initialised with an empty list for the display hooks """ assert self.disp_pub._hooks == [] + def hook(msg): return msg + self.disp_pub.register_hook(hook) assert self.disp_pub._hooks == [hook] q = Queue() + def set_thread_hooks(): q.put(self.disp_pub._hooks) + t = Thread(target=set_thread_hooks) t.start() thread_hooks = q.get(timeout=10) @@ -113,7 +117,7 @@ def test_publish(self): Publish should prepare the message and eventually call `send` by default. """ - data = dict(a = 1) + data = dict(a=1) assert self.session.send_count == 0 self.disp_pub.publish(data) assert self.session.send_count == 1 @@ -125,7 +129,7 @@ def test_display_hook_halts_send(self): the message has been consumed, and should not be processed (`sent`) in the normal manner. """ - data = dict(a = 1) + data = dict(a=1) hook = NoReturnDisplayHook() self.disp_pub.register_hook(hook) @@ -161,7 +165,7 @@ def test_unregister_hook(self): Once a hook is unregistered, it should not be called during `publish`. """ - data = dict(a = 1) + data = dict(a=1) hook = NoReturnDisplayHook() self.disp_pub.register_hook(hook) @@ -180,7 +184,7 @@ def test_unregister_hook(self): # at the end. # # As a result, the hook call count should *not* increase, - # but the session send count *should* increase. + # but the session send count *should* increase. # first = self.disp_pub.unregister_hook(hook) self.disp_pub.publish(data) @@ -197,5 +201,5 @@ def test_unregister_hook(self): self.assertFalse(second) -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 91d87f86e..711c45542 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -7,16 +7,14 @@ import os import platform import sys -from tempfile import TemporaryDirectory -from time import time - from contextlib import contextmanager from queue import Empty from subprocess import STDOUT +from tempfile import TemporaryDirectory +from time import time from jupyter_client import manager - STARTUP_TIMEOUT = 60 TIMEOUT = 100 @@ -29,10 +27,11 @@ def start_new_kernel(**kwargs): Integrates with our output capturing for tests. """ - kwargs['stderr'] = STDOUT + kwargs["stderr"] = STDOUT try: import nose - kwargs['stdout'] = nose.iptest_stdstreams_fileno() + + kwargs["stdout"] = nose.iptest_stdstreams_fileno() except (ImportError, AttributeError): pass return manager.start_new_kernel(startup_timeout=STARTUP_TIMEOUT, **kwargs) @@ -54,12 +53,12 @@ def flush_channels(kc=None): validate_message(msg) -def get_reply(kc, msg_id, timeout=TIMEOUT, channel='shell'): +def get_reply(kc, msg_id, timeout=TIMEOUT, channel="shell"): t0 = time() while True: - get_msg = getattr(kc, f'get_{channel}_msg') + get_msg = getattr(kc, f"get_{channel}_msg") reply = get_msg(timeout=timeout) - if reply['parent_header']['msg_id'] == msg_id: + if reply["parent_header"]["msg_id"] == msg_id: break # Allow debugging ignored replies print(f"Ignoring reply not to {msg_id}: {reply}") @@ -69,29 +68,30 @@ def get_reply(kc, msg_id, timeout=TIMEOUT, channel='shell'): return reply -def execute(code='', kc=None, **kwargs): +def execute(code="", kc=None, **kwargs): """wrapper for doing common steps for validating an execution request""" from .test_message_spec import validate_message + if kc is None: kc = KC msg_id = kc.execute(code=code, **kwargs) reply = get_reply(kc, msg_id, TIMEOUT) - validate_message(reply, 'execute_reply', msg_id) + validate_message(reply, "execute_reply", msg_id) busy = kc.get_iopub_msg(timeout=TIMEOUT) - validate_message(busy, 'status', msg_id) - assert busy['content']['execution_state'] == 'busy' + validate_message(busy, "status", msg_id) + assert busy["content"]["execution_state"] == "busy" - if not kwargs.get('silent'): + if not kwargs.get("silent"): execute_input = kc.get_iopub_msg(timeout=TIMEOUT) - validate_message(execute_input, 'execute_input', msg_id) - assert execute_input['content']['code'] == code + validate_message(execute_input, "execute_input", msg_id) + assert execute_input["content"]["code"] == code # show tracebacks if present for debugging - if reply['content'].get('traceback'): - print('\n'.join(reply['content']['traceback']), file=sys.stderr) + if reply["content"].get("traceback"): + print("\n".join(reply["content"]["traceback"]), file=sys.stderr) + return msg_id, reply["content"] - return msg_id, reply['content'] def start_global_kernel(): """start the global kernel (if it isn't running) and return its client""" @@ -103,6 +103,7 @@ def start_global_kernel(): flush_channels(KC) return KC + @contextmanager def kernel(): """Context manager for the global kernel instance @@ -115,15 +116,19 @@ def kernel(): """ yield start_global_kernel() + def uses_kernel(test_f): """Decorator for tests that use the global kernel""" + def wrapped_test(): with kernel() as kc: test_f(kc) + wrapped_test.__doc__ = test_f.__doc__ wrapped_test.__name__ = test_f.__name__ return wrapped_test + def stop_global_kernel(): """Stop the global shared kernel instance, if it exists""" global KM, KC @@ -134,6 +139,7 @@ def stop_global_kernel(): KM.shutdown_kernel(now=True) KM = None + def new_kernel(argv=None): """Context manager for a new kernel in a subprocess @@ -143,45 +149,48 @@ def new_kernel(argv=None): ------- kernel_client: connected KernelClient instance """ - kwargs = {'stderr': STDOUT} + kwargs = {"stderr": STDOUT} try: import nose - kwargs['stdout'] = nose.iptest_stdstreams_fileno() + + kwargs["stdout"] = nose.iptest_stdstreams_fileno() except (ImportError, AttributeError): pass if argv is not None: - kwargs['extra_arguments'] = argv + kwargs["extra_arguments"] = argv return manager.run_kernel(**kwargs) + def assemble_output(get_msg): """assemble stdout/err from an execution""" - stdout = '' - stderr = '' + stdout = "" + stderr = "" while True: msg = get_msg(timeout=1) - msg_type = msg['msg_type'] - content = msg['content'] - if msg_type == 'status' and content['execution_state'] == 'idle': + msg_type = msg["msg_type"] + content = msg["content"] + if msg_type == "status" and content["execution_state"] == "idle": # idle message signals end of output break - elif msg['msg_type'] == 'stream': - if content['name'] == 'stdout': - stdout += content['text'] - elif content['name'] == 'stderr': - stderr += content['text'] + elif msg["msg_type"] == "stream": + if content["name"] == "stdout": + stdout += content["text"] + elif content["name"] == "stderr": + stderr += content["text"] else: - raise KeyError("bad stream: %r" % content['name']) + raise KeyError("bad stream: %r" % content["name"]) else: # other output, ignored pass return stdout, stderr + def wait_for_idle(kc): while True: msg = kc.get_iopub_msg(timeout=1) - msg_type = msg['msg_type'] - content = msg['content'] - if msg_type == 'status' and content['execution_state'] == 'idle': + msg_type = msg["msg_type"] + content = msg["content"] + if msg_type == "status" and content["execution_state"] == "idle": break @@ -194,6 +203,7 @@ class TemporaryWorkingDirectory(TemporaryDirectory): with TemporaryWorkingDirectory() as tmpdir: ... """ + def __enter__(self): self.old_wd = os.getcwd() os.chdir(self.name) diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index d78e516aa..b6d71183e 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -15,28 +15,25 @@ def __init__(self): def initialize(self, kernel, io_loop): kernel.shell.set_trio_runner(self) - kernel.shell.run_line_magic('autoawait', 'trio') - kernel.shell.magics_manager.magics['line']['autoawait'] = \ - lambda _: warnings.warn("Autoawait isn't allowed in Trio " - "background loop mode.") - bg_thread = threading.Thread(target=io_loop.start, daemon=True, - name='TornadoBackground') + kernel.shell.run_line_magic("autoawait", "trio") + kernel.shell.magics_manager.magics["line"]["autoawait"] = lambda _: warnings.warn( + "Autoawait isn't allowed in Trio " "background loop mode." + ) + bg_thread = threading.Thread(target=io_loop.start, daemon=True, name="TornadoBackground") bg_thread.start() def interrupt(self, signum, frame): if self._cell_cancel_scope: self._cell_cancel_scope.cancel() else: - raise Exception('Kernel interrupted but no cell is running') + raise Exception("Kernel interrupted but no cell is running") def run(self): old_sig = signal.signal(signal.SIGINT, self.interrupt) def log_nursery_exc(exc): - exc = '\n'.join(traceback.format_exception(type(exc), exc, - exc.__traceback__)) - logging.error('An exception occurred in a global nursery task.\n%s', - exc) + exc = "\n".join(traceback.format_exception(type(exc), exc, exc.__traceback__)) + logging.error("An exception occurred in a global nursery task.\n%s", exc) async def trio_main(): self._trio_token = trio.lowlevel.current_trio_token() diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 5f33054f9..c9ae6f769 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -19,35 +19,28 @@ import warnings from threading import local -from IPython.core.interactiveshell import ( - InteractiveShell, InteractiveShellABC -) -from IPython.core import page +from IPython.core import page, payloadpage from IPython.core.autocall import ZMQExitAutocall from IPython.core.displaypub import DisplayPublisher from IPython.core.error import UsageError -from IPython.core.magics import MacroToEdit, CodeMagics -from IPython.core.magic import magics_class, line_magic, Magics -from IPython.core import payloadpage +from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC +from IPython.core.magic import Magics, line_magic, magics_class +from IPython.core.magics import CodeMagics, MacroToEdit from IPython.core.usage import default_banner -from IPython.display import display, Javascript -from ipykernel import ( - get_connection_file, get_connection_info, connect_qtconsole -) +from IPython.display import Javascript, display from IPython.utils import openpy -from ipykernel.jsonutil import json_clean, encode_images from IPython.utils.process import arg_split, system -from traitlets import ( - Instance, Type, Dict, CBool, CBytes, Any, default, observe -) -from ipykernel.displayhook import ZMQShellDisplayHook - +from jupyter_client.session import Session, extract_header from jupyter_core.paths import jupyter_runtime_dir -from jupyter_client.session import extract_header, Session +from traitlets import Any, CBool, CBytes, Dict, Instance, Type, default, observe + +from ipykernel import connect_qtconsole, get_connection_file, get_connection_info +from ipykernel.displayhook import ZMQShellDisplayHook +from ipykernel.jsonutil import encode_images, json_clean -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- # Functions and classes -#----------------------------------------------------------------------------- +# ----------------------------------------------------------------------------- class ZMQDisplayPublisher(DisplayPublisher): @@ -56,11 +49,11 @@ class ZMQDisplayPublisher(DisplayPublisher): session = Instance(Session, allow_none=True) pub_socket = Any(allow_none=True) parent_header = Dict({}) - topic = CBytes(b'display_data') + topic = CBytes(b"display_data") # thread_local: # An attribute used to ensure the correct output message - # is processed. See ipykernel Issue 113 for a discussion. + # is processed. See ipykernel Issue 113 for a discussion. _thread_local = Any() def set_parent(self, parent): @@ -72,14 +65,14 @@ def _flush_streams(self): sys.stdout.flush() sys.stderr.flush() - @default('_thread_local') + @default("_thread_local") def _default_thread_local(self): """Initialize our thread local storage""" return local() @property def _hooks(self): - if not hasattr(self._thread_local, 'hooks'): + if not hasattr(self._thread_local, "hooks"): # create new list for a new thread self._thread_local.hooks = [] return self._thread_local.hooks @@ -113,19 +106,16 @@ def publish( transient = {} self._validate_data(data, metadata) content = {} - content['data'] = encode_images(data) - content['metadata'] = metadata - content['transient'] = transient + content["data"] = encode_images(data) + content["metadata"] = metadata + content["transient"] = transient - msg_type = 'update_display_data' if update else 'display_data' + msg_type = "update_display_data" if update else "display_data" # Use 2-stage process to send a message, # in order to put it through the transform # hooks before potentially sending. - msg = self.session.msg( - msg_type, json_clean(content), - parent=self.parent_header - ) + msg = self.session.msg(msg_type, json_clean(content), parent=self.parent_header) # Each transform either returns a new # message or None. If None is returned, @@ -136,7 +126,9 @@ def publish( return self.session.send( - self.pub_socket, msg, ident=self.topic, + self.pub_socket, + msg, + ident=self.topic, ) def clear_output(self, wait=False): @@ -153,8 +145,11 @@ def clear_output(self, wait=False): content = dict(wait=wait) self._flush_streams() self.session.send( - self.pub_socket, 'clear_output', content, - parent=self.parent_header, ident=self.topic, + self.pub_socket, + "clear_output", + content, + parent=self.parent_header, + ident=self.topic, ) def register_hook(self, hook): @@ -199,9 +194,9 @@ def unregister_hook(self, hook): @magics_class class KernelMagics(Magics): - #------------------------------------------------------------------------ + # ------------------------------------------------------------------------ # Magic overrides - #------------------------------------------------------------------------ + # ------------------------------------------------------------------------ # Once the base class stops inheriting from magic, this code needs to be # moved into a separate machinery as well. For now, at least isolate here # the magics which this class needs to implement differently from the base @@ -210,7 +205,7 @@ class KernelMagics(Magics): _find_edit_target = CodeMagics._find_edit_target @line_magic - def edit(self, parameter_s='', last_call=['','']): + def edit(self, parameter_s="", last_call=["", ""]): """Bring up an editor and execute the resulting code. Usage: @@ -287,7 +282,7 @@ def edit(self, parameter_s='', last_call=['','']): Note that %edit is also available through the alias %ed. """ - opts,args = self.parse_options(parameter_s, 'prn:') + opts, args = self.parse_options(parameter_s, "prn:") try: filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call) @@ -300,11 +295,7 @@ def edit(self, parameter_s='', last_call=['','']): # directory of client and kernel don't match filename = os.path.abspath(filename) - payload = { - 'source' : 'edit_magic', - 'filename' : filename, - 'line_number' : lineno - } + payload = {"source": "edit_magic", "filename": filename, "line_number": lineno} self.shell.payload_manager.write_payload(payload) # A few magics that are adapted to the specifics of using pexpect and a @@ -313,14 +304,14 @@ def edit(self, parameter_s='', last_call=['','']): @line_magic def clear(self, arg_s): """Clear the terminal.""" - if os.name == 'posix': + if os.name == "posix": self.shell.system("clear") else: self.shell.system("cls") - if os.name == 'nt': + if os.name == "nt": # This is the usual name in windows - cls = line_magic('cls')(clear) + cls = line_magic("cls")(clear) # Terminal pagers won't work over pexpect, but we do have our own pager @@ -330,23 +321,23 @@ def less(self, arg_s): Files ending in .py are syntax-highlighted.""" if not arg_s: - raise UsageError('Missing filename.') + raise UsageError("Missing filename.") - if arg_s.endswith('.py'): + if arg_s.endswith(".py"): cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False)) else: cont = open(arg_s).read() page.page(cont) - more = line_magic('more')(less) + more = line_magic("more")(less) # Man calls a pager, so we also need to redefine it - if os.name == 'posix': + if os.name == "posix": + @line_magic def man(self, arg_s): """Find the man page for the given command and display in pager.""" - page.page(self.shell.getoutput('man %s | col -b' % arg_s, - split=False)) + page.page(self.shell.getoutput("man %s | col -b" % arg_s, split=False)) @line_magic def connect_info(self, arg_s): @@ -373,9 +364,8 @@ def connect_info(self, arg_s): if jupyter_runtime_dir() == os.path.dirname(connection_file): connection_file = os.path.basename(connection_file) - - print (info + '\n') - print ( + print(info + "\n") + print( f"Paste the above JSON into a file, and connect with:\n" f" $> jupyter --existing \n" f"or, if you are local, you can connect with just:\n" @@ -395,12 +385,13 @@ def qtconsole(self, arg_s): # %qtconsole should imply bind_kernel for engines: # FIXME: move to ipyparallel Kernel subclass - if 'ipyparallel' in sys.modules: + if "ipyparallel" in sys.modules: from ipyparallel import bind_kernel + bind_kernel() try: - connect_qtconsole(argv=arg_split(arg_s, os.name=='posix')) + connect_qtconsole(argv=arg_split(arg_s, os.name == "posix")) except Exception as e: warnings.warn("Could not start qtconsole: %r" % e) return @@ -423,8 +414,9 @@ def autosave(self, arg_s): # javascript wants milliseconds milliseconds = 1000 * interval - display(Javascript("IPython.notebook.set_autosave_interval(%i)" % milliseconds), - include=['application/javascript'] + display( + Javascript("IPython.notebook.set_autosave_interval(%i)" % milliseconds), + include=["application/javascript"], ) if interval: print("Autosaving every %i seconds" % interval) @@ -441,7 +433,7 @@ class ZMQInteractiveShell(InteractiveShell): kernel = Any() parent_header = Any() - @default('banner1') + @default("banner1") def _default_banner1(self): return default_banner @@ -455,19 +447,19 @@ def _default_banner1(self): exiter = Instance(ZMQExitAutocall) - @default('exiter') + @default("exiter") def _default_exiter(self): return ZMQExitAutocall(self) - @observe('exit_now') + @observe("exit_now") def _update_exit_now(self, change): """stop eventloop when exit_now fires""" - if change['new']: - if hasattr(self.kernel, 'io_loop'): + if change["new"]: + if hasattr(self.kernel, "io_loop"): loop = self.kernel.io_loop loop.call_later(0.1, loop.stop) if self.kernel.eventloop: - exit_hook = getattr(self.kernel.eventloop, 'exit_hook', None) + exit_hook = getattr(self.kernel.eventloop, "exit_hook", None) if exit_hook: exit_hook(self.kernel) @@ -477,6 +469,7 @@ def _update_exit_now(self, change): # interactive input being read; we provide event loop support in ipkernel def enable_gui(self, gui): from .eventloops import enable_gui as real_enable_gui + try: real_enable_gui(gui) self.active_eventloop = gui @@ -487,17 +480,17 @@ def init_environment(self): """Configure the user's environment.""" env = os.environ # These two ensure 'ls' produces nice coloring on BSD-derived systems - env['TERM'] = 'xterm-color' - env['CLICOLOR'] = '1' + env["TERM"] = "xterm-color" + env["CLICOLOR"] = "1" # Since normal pagers don't work at all (over pexpect we don't have # single-key control of the subprocess), try to disable paging in # subprocesses as much as possible. - env['PAGER'] = 'cat' - env['GIT_PAGER'] = 'cat' + env["PAGER"] = "cat" + env["GIT_PAGER"] = "cat" def init_hooks(self): super().init_hooks() - self.set_hook('show_in_pager', page.as_hook(payloadpage.page), 99) + self.set_hook("show_in_pager", page.as_hook(payloadpage.page), 99) def init_data_pub(self): """Delay datapub init until request, for deprecation warnings""" @@ -505,9 +498,12 @@ def init_data_pub(self): @property def data_pub(self): - if not hasattr(self, '_data_pub'): - warnings.warn("InteractiveShell.data_pub is deprecated outside IPython parallel.", - DeprecationWarning, stacklevel=2) + if not hasattr(self, "_data_pub"): + warnings.warn( + "InteractiveShell.data_pub is deprecated outside IPython parallel.", + DeprecationWarning, + stacklevel=2, + ) self._data_pub = self.data_pub_class(parent=self) self._data_pub.session = self.display_pub.session @@ -520,9 +516,9 @@ def data_pub(self, pub): def ask_exit(self): """Engage the exit actions.""" - self.exit_now = (not self.keepkernel_on_exit) + self.exit_now = not self.keepkernel_on_exit payload = dict( - source='ask_exit', + source="ask_exit", keepkernel=self.keepkernel_on_exit, ) self.payload_manager.write_payload(payload) @@ -537,9 +533,9 @@ def _showtraceback(self, etype, evalue, stb): sys.stderr.flush() exc_content = { - 'traceback' : stb, - 'ename' : str(etype.__name__), - 'evalue' : str(evalue), + "traceback": stb, + "ename": str(etype.__name__), + "evalue": str(evalue), } dh = self.displayhook @@ -547,7 +543,7 @@ def _showtraceback(self, etype, evalue, stb): # to pick up topic = None if dh.topic: - topic = dh.topic.replace(b'execute_result', b'error') + topic = dh.topic.replace(b"execute_result", b"error") dh.session.send( dh.pub_socket, @@ -565,7 +561,7 @@ def set_next_input(self, text, replace=False): """Send the specified text to the frontend to be presented at the next input cell.""" payload = dict( - source='set_next_input', + source="set_next_input", text=text, replace=replace, ) @@ -576,7 +572,7 @@ def set_parent(self, parent): self.parent_header = parent self.displayhook.set_parent(parent) self.display_pub.set_parent(parent) - if hasattr(self, '_data_pub'): + if hasattr(self, "_data_pub"): self.data_pub.set_parent(parent) try: sys.stdout.set_parent(parent) @@ -593,7 +589,7 @@ def get_parent(self): def init_magics(self): super().init_magics() self.register_magics(KernelMagics) - self.magics_manager.register_alias('ed', 'edit') + self.magics_manager.register_alias("ed", "edit") def init_virtualenv(self): # Overridden not to do virtualenv detection, because it's probably @@ -612,7 +608,7 @@ def system_piped(self, cmd): not supported. Should not be a command that expects input other than simple text. """ - if cmd.rstrip().endswith('&'): + if cmd.rstrip().endswith("&"): # this is *far* from a rigorous test # We do not support backgrounding processes because we either use # pexpect or pipes to read from. Users can always just call @@ -625,15 +621,16 @@ def system_piped(self, cmd): # Instead, we store the exit_code in user_ns. # Also, protect system call from UNC paths on Windows here too # as is done in InteractiveShell.system_raw - if sys.platform == 'win32': + if sys.platform == "win32": cmd = self.var_expand(cmd, depth=1) from IPython.utils._process_win32 import AvoidUNCPath + with AvoidUNCPath() as path: if path is not None: - cmd = 'pushd %s &&%s' % (path, cmd) - self.user_ns['_exit_code'] = system(cmd) + cmd = "pushd %s &&%s" % (path, cmd) + self.user_ns["_exit_code"] = system(cmd) else: - self.user_ns['_exit_code'] = system(self.var_expand(cmd, depth=1)) + self.user_ns["_exit_code"] = system(self.var_expand(cmd, depth=1)) # Ensure new system_piped implementation is used system = system_piped diff --git a/ipykernel_launcher.py b/ipykernel_launcher.py index 009d21d9e..49aa2651a 100644 --- a/ipykernel_launcher.py +++ b/ipykernel_launcher.py @@ -6,11 +6,12 @@ import sys -if __name__ == '__main__': +if __name__ == "__main__": # Remove the CWD from sys.path while we load stuff. # This is added back by InteractiveShellApp.init_path() - if sys.path[0] == '': + if sys.path[0] == "": del sys.path[0] from ipykernel import kernelapp as app + app.launch_new_instance() diff --git a/setup.py b/setup.py index 1106d77be..42be037c0 100644 --- a/setup.py +++ b/setup.py @@ -3,16 +3,16 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import sys -from glob import glob import os import shutil +import sys +from glob import glob from setuptools import setup from setuptools.command.bdist_egg import bdist_egg # the name of the package -name = 'ipykernel' +name = "ipykernel" class bdist_egg_disabled(bdist_egg): @@ -21,6 +21,7 @@ class bdist_egg_disabled(bdist_egg): Prevents setup.py install from performing setuptools' default easy_install, which it should never ever do. """ + def run(self): sys.exit("Aborting implicit building of eggs. Use `pip install .` to install from source.") @@ -31,45 +32,45 @@ def run(self): packages = [] for d, _, _ in os.walk(pjoin(here, name)): - if os.path.exists(pjoin(d, '__init__.py')): - packages.append(d[len(here)+1:].replace(os.path.sep, '.')) + if os.path.exists(pjoin(d, "__init__.py")): + packages.append(d[len(here) + 1 :].replace(os.path.sep, ".")) package_data = { - 'ipykernel': ['resources/*.*'], + "ipykernel": ["resources/*.*"], } -with open(pjoin(here, 'README.md')) as fid: +with open(pjoin(here, "README.md")) as fid: LONG_DESCRIPTION = fid.read() setup_args = dict( name=name, cmdclass={ - 'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled, + "bdist_egg": bdist_egg if "bdist_egg" in sys.argv else bdist_egg_disabled, }, - scripts=glob(pjoin('scripts', '*')), + scripts=glob(pjoin("scripts", "*")), packages=packages, - py_modules=['ipykernel_launcher'], + py_modules=["ipykernel_launcher"], package_data=package_data, description="IPython Kernel for Jupyter", long_description_content_type="text/markdown", - author='IPython Development Team', - author_email='ipython-dev@scipy.org', - url='https://ipython.org', - license='BSD', + author="IPython Development Team", + author_email="ipython-dev@scipy.org", + url="https://ipython.org", + license="BSD", long_description=LONG_DESCRIPTION, platforms="Linux, Mac OS X, Windows", - keywords=['Interactive', 'Interpreter', 'Shell', 'Web'], - python_requires='>=3.7', + keywords=["Interactive", "Interpreter", "Shell", "Web"], + python_requires=">=3.7", install_requires=[ - 'debugpy>=1.0.0,<2.0', - 'ipython>=7.23.1', - 'traitlets>=5.1.0,<6.0', - 'jupyter_client<8.0', - 'tornado>=5.0,<7.0', - 'matplotlib-inline>=0.1.0,<0.2.0', + "debugpy>=1.0.0,<2.0", + "ipython>=7.23.1", + "traitlets>=5.1.0,<6.0", + "jupyter_client<8.0", + "tornado>=5.0,<7.0", + "matplotlib-inline>=0.1.0,<0.2.0", 'appnope;platform_system=="Darwin"', - 'psutil', - 'nest_asyncio', + "psutil", + "nest_asyncio", ], extras_require={ "test": [ @@ -78,45 +79,45 @@ def run(self): "flaky", "ipyparallel", "pre-commit", - "pytest-timeout" + "pytest-timeout", ], }, classifiers=[ - 'Intended Audience :: Developers', - 'Intended Audience :: System Administrators', - 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9' + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", ], ) -if any(a.startswith(('bdist', 'install')) for a in sys.argv): +if any(a.startswith(("bdist", "install")) for a in sys.argv): sys.path.insert(0, here) - from ipykernel.kernelspec import write_kernel_spec, make_ipkernel_cmd, KERNEL_NAME + from ipykernel.kernelspec import KERNEL_NAME, make_ipkernel_cmd, write_kernel_spec # When building a wheel, the executable specified in the kernelspec is simply 'python'. - if any(a.startswith('bdist') for a in sys.argv): - argv = make_ipkernel_cmd(executable='python') + if any(a.startswith("bdist") for a in sys.argv): + argv = make_ipkernel_cmd(executable="python") # When installing from source, the full `sys.executable` can be used. - if any(a.startswith('install') for a in sys.argv): + if any(a.startswith("install") for a in sys.argv): argv = make_ipkernel_cmd() - dest = os.path.join(here, 'data_kernelspec') + dest = os.path.join(here, "data_kernelspec") if os.path.exists(dest): shutil.rmtree(dest) - write_kernel_spec(dest, overrides={'argv': argv}) + write_kernel_spec(dest, overrides={"argv": argv}) - setup_args['data_files'] = [ + setup_args["data_files"] = [ ( - pjoin('share', 'jupyter', 'kernels', KERNEL_NAME), - glob(pjoin('data_kernelspec', '*')), + pjoin("share", "jupyter", "kernels", KERNEL_NAME), + glob(pjoin("data_kernelspec", "*")), ) ] -if __name__ == '__main__': +if __name__ == "__main__": setup(**setup_args) From d7e07ede7b447de75a6854ac49508f5e985ceab5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 29 Mar 2022 06:37:17 -0500 Subject: [PATCH 0776/1195] fix import order --- ipykernel/debugger.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 21585b364..62082a3cd 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -19,12 +19,13 @@ try: # This import is required to have the next ones working... - from _pydevd_bundle import pydevd_frame_utils - from _pydevd_bundle.pydevd_suspended_frames import ( + from debugpy.server import api # noqa + + from _pydevd_bundle import pydevd_frame_utils # isort: skip + from _pydevd_bundle.pydevd_suspended_frames import ( # isort: skip SuspendedFramesManager, _FramesTracker, ) - from debugpy.server import api # noqa _is_debugpy_available = True except ImportError: From 53e86fb25f0ae34c93eba57c2ea0f4984131f65e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 29 Mar 2022 06:39:59 -0500 Subject: [PATCH 0777/1195] remove problematic check --- .pre-commit-config.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f1922568..fbc07722b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,12 +49,3 @@ repos: rev: v8.12.0 hooks: - id: eslint - - - repo: https://github.com/sirosen/check-jsonschema - rev: 0.14.1 - hooks: - - id: check-jsonschema - name: "Check GitHub Workflows" - files: ^\.github/workflows/ - types: [yaml] - args: ["--schemafile", "https://json.schemastore.org/github-workflow"] From ca4284101680d699e1fdb53c14f5c3437c53977a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 29 Mar 2022 06:40:54 -0500 Subject: [PATCH 0778/1195] remove pre-commit job --- .github/workflows/ci.yml | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 728482ffa..8de7da368 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,30 +14,6 @@ defaults: shell: bash jobs: - # Run "pre-commit run --all-files" - pre-commit: - runs-on: ubuntu-20.04 - timeout-minutes: 2 - - steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - with: - python-version: 3.8 - - # ref: https://github.com/pre-commit/action - - uses: pre-commit/action@v2.0.0 - - name: Help message if pre-commit fail - if: ${{ failure() }} - run: | - echo "You can install pre-commit hooks to automatically run formatting" - echo "on each commit with:" - echo " pre-commit install" - echo "or you can run by hand on staged files with" - echo " pre-commit run" - echo "or after-the-fact on already committed files with" - echo " pre-commit run --all-files" - build: runs-on: ${{ matrix.os }} strategy: From c145be4cc43d88422d15edb9076785a0df5f69a4 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 29 Mar 2022 06:48:40 -0500 Subject: [PATCH 0779/1195] add ignore-revs file --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..5109a31ea --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Black formatting: https://github.com/ipython/ipykernel/pull/892 +c5bca730f82bbdfb005ab93969ff5a1d028c2341 From e73e41038da8f6fc4ad830b1f7f4bce4c427fccd Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 29 Mar 2022 07:01:11 -0500 Subject: [PATCH 0780/1195] run flake and remove deprecated import --- .pre-commit-config.yaml | 20 ++++++++++---------- docs/conf.py | 11 +---------- examples/embedding/internal_ipkernel.py | 2 +- ipykernel/__init__.py | 12 +++++------- ipykernel/comm/__init__.py | 4 ++-- ipykernel/compiler.py | 4 ++-- ipykernel/debugger.py | 1 - ipykernel/eventloops.py | 4 ++-- ipykernel/inprocess/__init__.py | 8 ++++---- ipykernel/inprocess/client.py | 4 ++-- ipykernel/inprocess/tests/test_kernel.py | 2 +- ipykernel/iostream.py | 20 ++++++++------------ ipykernel/ipkernel.py | 3 ++- ipykernel/kernelbase.py | 4 ++-- ipykernel/pickleutil.py | 6 +++--- ipykernel/pylab/config.py | 2 +- ipykernel/serialize.py | 2 +- ipykernel/tests/__init__.py | 3 --- ipykernel/tests/test_async.py | 4 ---- ipykernel/tests/test_connect.py | 2 +- ipykernel/tests/test_embed_kernel.py | 2 +- ipykernel/tests/test_eventloop.py | 2 -- ipykernel/tests/test_kernel.py | 8 ++++---- ipykernel/tests/test_message_spec.py | 2 +- ipykernel/tests/utils.py | 1 - ipykernel/trio_runner.py | 2 +- ipykernel/zmqshell.py | 4 ++-- 27 files changed, 57 insertions(+), 82 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fbc07722b..c7c8562af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,16 +34,16 @@ repos: hooks: - id: prettier - # - repo: https://github.com/pycqa/flake8 - # rev: 4.0.1 - # hooks: - # - id: flake8 - # additional_dependencies: - # [ - # "flake8-bugbear==20.1.4", - # "flake8-logging-format==0.6.0", - # "flake8-implicit-str-concat==0.2.0", - # ] + - repo: https://github.com/pycqa/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + additional_dependencies: + [ + "flake8-bugbear==20.1.4", + "flake8-logging-format==0.6.0", + "flake8-implicit-str-concat==0.2.0", + ] - repo: https://github.com/pre-commit/mirrors-eslint rev: v8.12.0 diff --git a/docs/conf.py b/docs/conf.py index e331f558a..a1ad70d12 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -217,16 +217,7 @@ # -- Options for LaTeX output --------------------------------------------- -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - #'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - #'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - #'preamble': '', - # Latex figure (float) alignment - #'figure_align': 'htbp', -} +latex_elements = {} # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, diff --git a/examples/embedding/internal_ipkernel.py b/examples/embedding/internal_ipkernel.py index 9538aacc9..46e97a5e6 100644 --- a/examples/embedding/internal_ipkernel.py +++ b/examples/embedding/internal_ipkernel.py @@ -19,7 +19,7 @@ def mpl_kernel(gui): [ "python", "--matplotlib=%s" % gui, - #'--log-level=10' + #'--log-level=10' # noqa ] ) return kernel diff --git a/ipykernel/__init__.py b/ipykernel/__init__.py index bc5fbb726..a6c1666d6 100644 --- a/ipykernel/__init__.py +++ b/ipykernel/__init__.py @@ -1,7 +1,5 @@ -from ._version import ( - __version__, - kernel_protocol_version, - kernel_protocol_version_info, - version_info, -) -from .connect import * +from ._version import __version__ # noqa +from ._version import kernel_protocol_version # noqa +from ._version import kernel_protocol_version_info # noqa +from ._version import version_info # noqa +from .connect import * # noqa diff --git a/ipykernel/comm/__init__.py b/ipykernel/comm/__init__.py index cf9c1e983..f82cf5448 100644 --- a/ipykernel/comm/__init__.py +++ b/ipykernel/comm/__init__.py @@ -1,2 +1,2 @@ -from .comm import * -from .manager import * +from .comm import * # noqa +from .manager import * # noqa diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 585261593..387e49b3e 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -43,7 +43,7 @@ def murmur2_x86(data, seed): return h -convert_to_long_pathname = lambda filename: filename +convert_to_long_pathname = lambda filename: filename # noqa if sys.platform == "win32": try: @@ -63,7 +63,7 @@ def _convert_to_long_pathname(filename): # test that it works so if there are any issues we fail just once here _convert_to_long_pathname(__file__) - except: + except Exception: pass else: convert_to_long_pathname = _convert_to_long_pathname diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 62082a3cd..8368acccc 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,7 +1,6 @@ import os import re import sys -import threading import zmq from IPython.core.getipython import get_ipython diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index eae3a9e59..23f5f632b 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -128,12 +128,12 @@ def loop_qt5(kernel): """Start a kernel with PyQt5 event loop integration.""" if os.environ.get("QT_API", None) is None: try: - import PyQt5 + import PyQt5 # noqa os.environ["QT_API"] = "pyqt5" except ImportError: try: - import PySide2 + import PySide2 # noqa os.environ["QT_API"] = "pyside2" except ImportError: diff --git a/ipykernel/inprocess/__init__.py b/ipykernel/inprocess/__init__.py index b10698910..5d5147172 100644 --- a/ipykernel/inprocess/__init__.py +++ b/ipykernel/inprocess/__init__.py @@ -1,4 +1,4 @@ -from .blocking import BlockingInProcessKernelClient -from .channels import InProcessChannel, InProcessHBChannel -from .client import InProcessKernelClient -from .manager import InProcessKernelManager +from .blocking import BlockingInProcessKernelClient # noqa +from .channels import InProcessChannel, InProcessHBChannel # noqa +from .client import InProcessKernelClient # noqa +from .manager import InProcessKernelManager # noqa diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index b8d57085f..807cad760 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -99,7 +99,7 @@ def hb_channel(self): # ------------------------------------- def execute( - self, code, silent=False, store_history=True, user_expressions={}, allow_stdin=None + self, code, silent=False, store_history=True, user_expressions=None, allow_stdin=None ): if allow_stdin is None: allow_stdin = self.allow_stdin @@ -107,7 +107,7 @@ def execute( code=code, silent=silent, store_history=store_history, - user_expressions=user_expressions, + user_expressions=user_expressions or {}, allow_stdin=allow_stdin, ) msg = self.session.msg("execute_request", content) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 28b176f80..83d1924b5 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -64,7 +64,7 @@ def setUp(self): def test_pylab(self): """Does %pylab work in the in-process kernel?""" - matplotlib = pytest.importorskip("matplotlib", reason="This test requires matplotlib") + _ = pytest.importorskip("matplotlib", reason="This test requires matplotlib") kc = self.kc kc.execute("%pylab") out, err = assemble_output(kc.get_iopub_msg) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index fa038359b..782ac5ad9 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -12,7 +12,6 @@ import warnings from binascii import b2a_hex from collections import deque -from imp import lock_held as import_lock_held from io import StringIO, TextIOBase from weakref import WeakSet @@ -122,7 +121,7 @@ def _handle_event(self, msg): # freeze event count so new writes don't extend the queue # while we are processing n_events = len(self._events) - for i in range(n_events): + for _ in range(n_events): event_f = self._events.popleft() event_f() @@ -475,16 +474,13 @@ def flush(self): # request flush on the background thread self.pub_thread.schedule(self._flush) # wait for flush to actually get through, if we can. - # waiting across threads during import can cause deadlocks - # so only wait if import lock is not held - if not import_lock_held(): - evt = threading.Event() - self.pub_thread.schedule(evt.set) - # and give a timeout to avoid - if not evt.wait(self.flush_timeout): - # write directly to __stderr__ instead of warning because - # if this is happening sys.stderr may be the problem. - print("IOStream.flush timed out", file=sys.__stderr__) + evt = threading.Event() + self.pub_thread.schedule(evt.set) + # and give a timeout to avoid + if not evt.wait(self.flush_timeout): + # write directly to __stderr__ instead of warning because + # if this is happening sys.stderr may be the problem. + print("IOStream.flush timed out", file=sys.__stderr__) else: self._flush() diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 350f7f9fd..1757b8503 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -307,9 +307,10 @@ async def do_execute( run_cell = shell.run_cell_async should_run_async = shell.should_run_async else: - should_run_async = lambda cell: False + should_run_async = lambda cell: False # noqa # older IPython, # use blocking run_cell and wrap it in coroutine + async def run_cell(*args, **kwargs): return shell.run_cell(*args, **kwargs) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 12a3d93b0..5ef16fb37 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -942,7 +942,7 @@ async def usage_request(self, stream, ident, parent): cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. - if cpu_percent != None and cpu_percent != 0.0: + if cpu_percent is not None and cpu_percent != 0.0: reply_content["host_cpu_percent"] = cpu_percent reply_content["host_virtual_memory"] = dict(psutil.virtual_memory()._asdict()) reply_msg = self.session.send(stream, "usage_reply", reply_content, parent, ident) @@ -1083,7 +1083,7 @@ def _no_raw_input(self): """Raise StdinNotImplementedError if active frontend doesn't support stdin.""" raise StdinNotImplementedError( - "raw_input was called, but this " "frontend does not support stdin." + "raw_input was called, but this frontend does not support stdin." ) def getpass(self, prompt="", stream=None): diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 82c588311..9ebd904d7 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -118,7 +118,7 @@ def use_cloudpickle(): class CannedObject: - def __init__(self, obj, keys=[], hook=None): + def __init__(self, obj, keys=None, hook=None): """can an object for safe pickling Parameters @@ -136,7 +136,7 @@ def __init__(self, obj, keys=[], hook=None): large data may be offloaded into the buffers list, used for zero-copy transfers. """ - self.keys = keys + self.keys = keys or [] self.obj = copy.copy(obj) self.hook = can(hook) for key in keys: @@ -326,7 +326,7 @@ def _import_mapping(mapping, original=None): """import any string-keys in a type mapping""" log = get_logger() log.debug("Importing canning map") - for key, value in list(mapping.items()): + for key, _ in list(mapping.items()): if isinstance(key, str): try: cls = import_item(key) diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index 09e2bef6a..7622f9e11 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -8,6 +8,6 @@ from matplotlib_inline.config import * # analysis: ignore # noqa F401 warnings.warn( - "`ipykernel.pylab.config` is deprecated, directly " "use `matplotlib_inline.config`", + "`ipykernel.pylab.config` is deprecated, directly use `matplotlib_inline.config`", DeprecationWarning, ) diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 43247d3ff..6a1b79f78 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -189,7 +189,7 @@ def unpack_apply_message(bufs, g=None, copy=True): arg_bufs, kwarg_bufs = bufs[: info["narg_bufs"]], bufs[info["narg_bufs"] :] args = [] - for i in range(info["nargs"]): + for _ in range(info["nargs"]): arg, arg_bufs = deserialize_object(arg_bufs, g) args.append(arg) args = tuple(args) diff --git a/ipykernel/tests/__init__.py b/ipykernel/tests/__init__.py index 6ca7492a0..d2606e108 100644 --- a/ipykernel/tests/__init__.py +++ b/ipykernel/tests/__init__.py @@ -7,9 +7,6 @@ import tempfile from unittest.mock import patch -from IPython import paths as ipaths -from jupyter_core import paths as jpaths - from ipykernel.kernelspec import install pjoin = os.path.join diff --git a/ipykernel/tests/test_async.py b/ipykernel/tests/test_async.py index b36fedff0..81a8e3d95 100644 --- a/ipykernel/tests/test_async.py +++ b/ipykernel/tests/test_async.py @@ -1,9 +1,5 @@ """Test async/await integration""" -import sys -from distutils.version import LooseVersion as V - -import IPython import pytest from .test_message_spec import validate_message diff --git a/ipykernel/tests/test_connect.py b/ipykernel/tests/test_connect.py index 8e584ce20..4bb998ae1 100644 --- a/ipykernel/tests/test_connect.py +++ b/ipykernel/tests/test_connect.py @@ -35,7 +35,7 @@ class DummyKernelApp(IPKernelApp): def _default_shell_port(self): return 0 - def initialize(self, argv=[]): + def initialize(self, argv=None): self.init_profile_dir() self.init_connection_file() diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 89c02565f..81eb234de 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -164,7 +164,7 @@ def test_embed_kernel_reentrant(): " embed_kernel()", " count = count + 1", "", - "while True:" " go()", + "while True: go()", "", ] ) diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index c0db7ff66..35818515e 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -1,7 +1,5 @@ """Test eventloop integration""" -import sys - import pytest import tornado diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 7118f0a4f..40b9471eb 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -336,7 +336,7 @@ def test_message_order(): cell = "a += 1\na" msg_ids = [] # submit N executions as fast as we can - for i in range(N): + for _ in range(N): msg_ids.append(kc.execute(cell)) # check message-handling order for i, msg_id in enumerate(msg_ids, offset): @@ -388,7 +388,7 @@ def test_shutdown(): execute("a = 1", kc=kc) wait_for_idle(kc) kc.shutdown() - for i in range(300): # 30s timeout + for _ in range(300): # 30s timeout if km.is_alive(): time.sleep(0.1) else: @@ -482,7 +482,7 @@ def test_control_thread_priority(): # now send N control messages control_msg_ids = [] - for i in range(N): + for _ in range(N): msg = kc.session.msg("kernel_info_request", {}) kc.control_channel.send(msg) control_msg_ids.append(msg["header"]["msg_id"]) @@ -557,7 +557,7 @@ def test_shutdown_subprocesses(): wait_for_idle(kc) kc.shutdown() - for i in range(300): # 30s timeout + for _ in range(300): # 30s timeout if km.is_alive(): time.sleep(0.1) else: diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 1a9cc58c1..07b3e6ac6 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -528,7 +528,7 @@ def test_single_payload(): """ flush_channels() msg_id, reply = execute( - code="ip = get_ipython()\n" "for i in range(3):\n" " ip.set_next_input('Hello There')\n" + code="ip = get_ipython()\n for i in range(3):\n ip.set_next_input('Hello There')\n" ) payload = reply["payload"] next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"] diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 711c45542..8ca55b1e7 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -5,7 +5,6 @@ import atexit import os -import platform import sys from contextlib import contextmanager from queue import Empty diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index b6d71183e..c72f6455e 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -17,7 +17,7 @@ def initialize(self, kernel, io_loop): kernel.shell.set_trio_runner(self) kernel.shell.run_line_magic("autoawait", "trio") kernel.shell.magics_manager.magics["line"]["autoawait"] = lambda _: warnings.warn( - "Autoawait isn't allowed in Trio " "background loop mode." + "Autoawait isn't allowed in Trio background loop mode." ) bg_thread = threading.Thread(target=io_loop.start, daemon=True, name="TornadoBackground") bg_thread.start() diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index c9ae6f769..1d6627cb4 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -205,7 +205,7 @@ class KernelMagics(Magics): _find_edit_target = CodeMagics._find_edit_target @line_magic - def edit(self, parameter_s="", last_call=["", ""]): + def edit(self, parameter_s="", last_call=None): """Bring up an editor and execute the resulting code. Usage: @@ -281,7 +281,7 @@ def edit(self, parameter_s="", last_call=["", ""]): Note that %edit is also available through the alias %ed. """ - + last_call = last_call or ["", ""] opts, args = self.parse_options(parameter_s, "prn:") try: From 3f63828a133e1bd98135d06e5c203d9e785ef0b1 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 29 Mar 2022 07:07:36 -0500 Subject: [PATCH 0781/1195] fix space --- ipykernel/tests/test_message_spec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 07b3e6ac6..94cf107f5 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -528,7 +528,7 @@ def test_single_payload(): """ flush_channels() msg_id, reply = execute( - code="ip = get_ipython()\n for i in range(3):\n ip.set_next_input('Hello There')\n" + code="ip = get_ipython()\nfor i in range(3):\n ip.set_next_input('Hello There')\n" ) payload = reply["payload"] next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"] From 881b6c8a232b025f019ee7d5a38fa9b18651801d Mon Sep 17 00:00:00 2001 From: Bago Amirbekian Date: Tue, 29 Mar 2022 17:00:16 -0700 Subject: [PATCH 0782/1195] Include signature in completions. --- ipykernel/ipkernel.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 1757b8503..0e72ccdbe 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -452,6 +452,7 @@ def _experimental_do_complete(self, code, cursor_pos): end=comp.end, text=comp.text, type=comp.type, + signature=comp.signature, ) ) From eb5436eed5bf597ff618fd0b28c06e22076a5200 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Mar 2022 05:27:25 -0500 Subject: [PATCH 0783/1195] Handle warnings --- ipykernel/eventloops.py | 2 +- ipykernel/tests/conftest.py | 21 +++++++++++++++++++++ ipykernel/tests/test_embed_kernel.py | 6 ++++++ ipykernel/tests/test_message_spec.py | 9 +++++---- ipykernel/tests/test_pickleutil.py | 6 +++++- pyproject.toml | 8 ++++++++ setup.py | 14 ++++++++------ 7 files changed, 54 insertions(+), 12 deletions(-) create mode 100644 ipykernel/tests/conftest.py diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 23f5f632b..d45c19590 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -6,7 +6,7 @@ import os import platform import sys -from distutils.version import LooseVersion as V +from pkg_resources import parse_version as V from functools import partial import zmq diff --git a/ipykernel/tests/conftest.py b/ipykernel/tests/conftest.py new file mode 100644 index 000000000..555d62573 --- /dev/null +++ b/ipykernel/tests/conftest.py @@ -0,0 +1,21 @@ + +try: + import resource +except ImportError: + # Windows + resource = None + + +# Handle resource limit +# Ensure a minimal soft limit of DEFAULT_SOFT if the current hard limit is at least that much. +if resource is not None: + soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) + + DEFAULT_SOFT = 4096 + if hard >= DEFAULT_SOFT: + soft = DEFAULT_SOFT + + if hard < soft: + hard = soft + + resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard)) diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 81eb234de..cab531179 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -75,6 +75,12 @@ def connection_file_ready(connection_file): client.stop_channels() finally: kernel.terminate() + kernel.wait() + # Make sure all the fds get closed. + for attr in ['stdout', 'stderr', 'stdin']: + fid = getattr(kernel, attr) + if fid: + fid.close() @flaky(max_runs=3) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 94cf107f5..8cd9176a9 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -5,12 +5,12 @@ import re import sys -from distutils.version import LooseVersion as V +from pkg_resources import parse_version as V from queue import Empty import jupyter_client import pytest -from traitlets import Bool, Dict, Enum, HasTraits, Integer, List, TraitError, Unicode +from traitlets import Bool, Dict, Enum, HasTraits, Integer, List, TraitError, Unicode, observe from .utils import TIMEOUT, execute, flush_channels, get_reply, start_global_kernel @@ -98,8 +98,9 @@ class MimeBundle(Reference): metadata = Dict() data = Dict() - def _data_changed(self, name, old, new): - for k, v in new.items(): + @observe('data') + def _on_data_changed(self, change): + for k, v in change['new'].items(): assert mime_pat.match(k) assert isinstance(v, str) diff --git a/ipykernel/tests/test_pickleutil.py b/ipykernel/tests/test_pickleutil.py index 1cba3cb99..46e859d81 100644 --- a/ipykernel/tests/test_pickleutil.py +++ b/ipykernel/tests/test_pickleutil.py @@ -1,6 +1,10 @@ import pickle +import warnings -from ipykernel.pickleutil import can, uncan + +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + from ipykernel.pickleutil import can, uncan def interactive(f): diff --git a/pyproject.toml b/pyproject.toml index 2efcc10fc..5dc711eb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,3 +37,11 @@ testpaths = [ timeout = 300 # Restore this setting to debug failures # timeout_method = "thread" +filterwarnings= [ + # Fail on warnings + "error", + + # Workarounds jupyter_client + "ignore:unclosed =1.0.0,<2.0", + "debugpy>=1.0", "ipython>=7.23.1", - "traitlets>=5.1.0,<6.0", - "jupyter_client<8.0", - "tornado>=5.0,<7.0", - "matplotlib-inline>=0.1.0,<0.2.0", + "traitlets>=5.1.0", + "jupyter_client>=6.1.12", + "tornado>=6.1", + "matplotlib-inline>=0.1", 'appnope;platform_system=="Darwin"', "psutil", "nest_asyncio", + "setuptools>=60" # for pkg_resources ], extras_require={ "test": [ - "pytest !=5.3.4", + "pytest>=6.0", "pytest-cov", "flaky", "ipyparallel", @@ -92,6 +93,7 @@ def run(self): "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", ], ) From cb4af0f40d7b8f89ce2879a9b9c0ad630ca9e99a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Mar 2022 08:20:11 -0500 Subject: [PATCH 0784/1195] Handle warnings --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5dc711eb6..a2b27f78f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,8 @@ filterwarnings= [ # Fail on warnings "error", - # Workarounds jupyter_client + # Ignore jupyter_client warnings "ignore:unclosed Date: Wed, 30 Mar 2022 09:04:11 -0500 Subject: [PATCH 0785/1195] ignore more warnings --- pyproject.toml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a2b27f78f..edc6ecc3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,5 +44,7 @@ filterwarnings= [ # Ignore jupyter_client warnings "ignore:unclosed Date: Wed, 30 Mar 2022 10:00:53 -0500 Subject: [PATCH 0786/1195] fix event loop policy on windows --- ipykernel/tests/conftest.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ipykernel/tests/conftest.py b/ipykernel/tests/conftest.py index 555d62573..a14511ef8 100644 --- a/ipykernel/tests/conftest.py +++ b/ipykernel/tests/conftest.py @@ -1,3 +1,5 @@ +import asyncio +import os try: import resource @@ -19,3 +21,8 @@ hard = soft resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard)) + + +# Enforce selector event loop on Windows. +if os.name == "nt": + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) From c0bfd45c1499ffd4ce5f642862c76b3aaf90e765 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 22 Mar 2022 15:26:13 +0100 Subject: [PATCH 0787/1195] Try to pass cell id to executing kernel. Is seem that few kernel want to make use of cell id for various reasons. One way to get the cell_id in the executing context is to make use of init_metadata but it is marked for removal. for example in [there](https://github.com/robots-from-jupyter/robotkernel/blob/19b28267406d1f8555ce73b96e7efb8af8417266/src/robotkernel/kernel.py#L196-L205) Another is to start passing cell_id down. This is technically a change of API as our consumer may not support more parameters so we take a peak at the signature, and pass cell_id only if it is : - an explicit parameter, - the function/method takes varkwargs. We could also start to refactor to pass a metadata object with multiple fields if this is the preferred route. One questions is whether and how we want to deprecated not receiving cell_id as a parameter. Related to https://github.com/ipython/ipython/issues/13579 and subsequent PR on IPython side to support cell_id in progress. --- ipykernel/inprocess/tests/test_kernel.py | 31 ++++++++++++++ ipykernel/ipkernel.py | 53 +++++++++++++++++++----- ipykernel/kernelbase.py | 42 +++++++++++++++---- 3 files changed, 107 insertions(+), 19 deletions(-) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 83d1924b5..5314f68c5 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -13,6 +13,12 @@ from ipykernel.inprocess.ipkernel import InProcessKernel from ipykernel.inprocess.manager import InProcessKernelManager from ipykernel.tests.utils import assemble_output +from IPython.utils.io import capture_output + +from jupyter_client.session import Session +from contextlib import contextmanager + +orig_msg = Session.msg def _init_asyncio_patch(): @@ -53,6 +59,26 @@ def _init_asyncio_patch(): asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) +def _inject_cell_id(_self, *args, **kwargs): + """ + This patch jupyter_client.session:Session.msg to add a cell_id to the return message metadata + """ + assert isinstance(_self, Session) + res = orig_msg(_self, *args, **kwargs) + assert "cellId" not in res["metadata"] + res["metadata"]["cellId"] = "test_cell_id" + return res + + +@contextmanager +def patch_cell_id(): + try: + Session.msg = _inject_cell_id + yield + finally: + Session.msg = orig_msg + + class InProcessKernelTestCase(unittest.TestCase): def setUp(self): _init_asyncio_patch() @@ -62,6 +88,11 @@ def setUp(self): self.kc.start_channels() self.kc.wait_for_ready() + def test_with_cell_id(self): + + with patch_cell_id(): + self.kc.execute("1+1") + def test_pylab(self): """Does %pylab work in the in-process kernel?""" _ = pytest.importorskip("matplotlib", reason="This test requires matplotlib") diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 0e72ccdbe..77ee7bfc9 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -18,6 +18,7 @@ from .debugger import Debugger, _is_debugpy_available from .eventloops import _use_appnope from .kernelbase import Kernel as KernelBase +from .kernelbase import _accepts_cell_id from .zmqshell import ZMQInteractiveShell try: @@ -296,7 +297,14 @@ def set_sigint_result(): signal.signal(signal.SIGINT, save_sigint) async def do_execute( - self, code, silent, store_history=True, user_expressions=None, allow_stdin=False + self, + code, + silent, + store_history=True, + user_expressions=None, + allow_stdin=False, + *, + cell_id, ): shell = self.shell # we'll need this a lot here @@ -306,6 +314,7 @@ async def do_execute( if hasattr(shell, "run_cell_async") and hasattr(shell, "should_run_async"): run_cell = shell.run_cell_async should_run_async = shell.should_run_async + with_cell_id = _accepts_cell_id(run_cell) else: should_run_async = lambda cell: False # noqa # older IPython, @@ -314,6 +323,7 @@ async def do_execute( async def run_cell(*args, **kwargs): return shell.run_cell(*args, **kwargs) + with_cell_id = _accepts_cell_id(shell.run_cell) try: # default case: runner is asyncio and asyncio is already running @@ -336,13 +346,24 @@ async def run_cell(*args, **kwargs): preprocessing_exc_tuple=preprocessing_exc_tuple, ) ): - coro = run_cell( - code, - store_history=store_history, - silent=silent, - transformed_cell=transformed_cell, - preprocessing_exc_tuple=preprocessing_exc_tuple, - ) + if with_cell_id: + coro = run_cell( + code, + store_history=store_history, + silent=silent, + transformed_cell=transformed_cell, + preprocessing_exc_tuple=preprocessing_exc_tuple, + cell_id=cell_id, + ) + else: + coro = run_cell( + code, + store_history=store_history, + silent=silent, + transformed_cell=transformed_cell, + preprocessing_exc_tuple=preprocessing_exc_tuple, + ) + coro_future = asyncio.ensure_future(coro) with self._cancel_on_sigint(coro_future): @@ -357,7 +378,15 @@ async def run_cell(*args, **kwargs): # runner isn't already running, # make synchronous call, # letting shell dispatch to loop runners - res = shell.run_cell(code, store_history=store_history, silent=silent) + if with_cell_id: + res = shell.run_cell( + code, + store_history=store_history, + silent=silent, + cell_id=cell_id, + ) + else: + res = shell.run_cell(code, store_history=store_history, silent=silent) finally: self._restore_input() @@ -388,7 +417,8 @@ async def run_cell(*args, **kwargs): if "traceback" in reply_content: self.log.info( - "Exception in execute request:\n%s", "\n".join(reply_content["traceback"]) + "Exception in execute request:\n%s", + "\n".join(reply_content["traceback"]), ) # At this point, we can tell whether the main code execution succeeded @@ -623,6 +653,7 @@ def __init__(self, *args, **kwargs): import warnings warnings.warn( - "Kernel is a deprecated alias of ipykernel.ipkernel.IPythonKernel", DeprecationWarning + "Kernel is a deprecated alias of ipykernel.ipkernel.IPythonKernel", + DeprecationWarning, ) super().__init__(*args, **kwargs) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 5ef16fb37..49224608f 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -58,6 +58,14 @@ from ._version import kernel_protocol_version +def _accepts_cell_id(meth): + parameters = inspect.signature(meth).parameters + cid_param = parameters.get("cell_id") + return (cid_param and cid_param.kind == cid_param.KEYWORD_ONLY) or any( + p.kind == p.VAR_KEYWORD for p in parameters.values() + ) + + class Kernel(SingletonConfigurable): # --------------------------------------------------------------------------- @@ -690,13 +698,26 @@ async def execute_request(self, stream, ident, parent): self.execution_count += 1 self._publish_execute_input(code, parent, self.execution_count) - reply_content = self.do_execute( - code, - silent, - store_history, - user_expressions, - allow_stdin, - ) + cell_id = (parent.get("metadata") or {}).get("cellId") + + if _accepts_cell_id(self.do_execute): + reply_content = self.do_execute( + code, + silent, + store_history, + user_expressions, + allow_stdin, + cell_id=cell_id, + ) + else: + reply_content = self.do_execute( + code, + silent, + store_history, + user_expressions, + allow_stdin, + ) + if inspect.isawaitable(reply_content): reply_content = await reply_content @@ -714,7 +735,12 @@ async def execute_request(self, stream, ident, parent): metadata = self.finish_metadata(parent, metadata, reply_content) reply_msg = self.session.send( - stream, "execute_reply", reply_content, parent, metadata=metadata, ident=ident + stream, + "execute_reply", + reply_content, + parent, + metadata=metadata, + ident=ident, ) self.log.debug("%s", reply_msg) From e17d5a79733022b41ffdf0338c5bcf6a1c7a4ec1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 30 Mar 2022 15:23:41 +0000 Subject: [PATCH 0788/1195] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ipykernel/inprocess/tests/test_kernel.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 5314f68c5..ed9a5aaa9 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -3,20 +3,18 @@ import sys import unittest +from contextlib import contextmanager from io import StringIO import pytest import tornado from IPython.utils.io import capture_output +from jupyter_client.session import Session from ipykernel.inprocess.blocking import BlockingInProcessKernelClient from ipykernel.inprocess.ipkernel import InProcessKernel from ipykernel.inprocess.manager import InProcessKernelManager from ipykernel.tests.utils import assemble_output -from IPython.utils.io import capture_output - -from jupyter_client.session import Session -from contextlib import contextmanager orig_msg = Session.msg From aba50c9fd5e3ab04c426e7d006b28e6cf3dc401c Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 30 Mar 2022 10:46:26 -0500 Subject: [PATCH 0789/1195] try with latest jupyter_client --- .github/workflows/ci.yml | 4 ++-- pyproject.toml | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8de7da368..a6c216b46 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -135,7 +135,7 @@ jobs: - name: Run the tests timeout-minutes: 10 run: | - cmd="python -m pytest -vv -raXxs" + cmd="python -m pytest -vv" $cmd || $cmd --lf test_miniumum_versions: @@ -152,7 +152,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1 - name: Run the unit tests run: | - cmd="python -m pytest -vv -raXxs" + cmd="python -m pytest -vv -W default" $cmd || $cmd --lf test_prereleases: diff --git a/pyproject.toml b/pyproject.toml index edc6ecc3d..ed605e2ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,5 @@ filterwarnings= [ # Ignore jupyter_client warnings "ignore:unclosed Date: Wed, 30 Mar 2022 10:47:07 -0500 Subject: [PATCH 0790/1195] try with latest jupyter_client --- ipykernel/eventloops.py | 2 +- ipykernel/tests/test_embed_kernel.py | 2 +- ipykernel/tests/test_message_spec.py | 18 ++++++++++++++---- ipykernel/tests/test_pickleutil.py | 1 - setup.py | 2 +- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index d45c19590..6f486ace1 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -6,10 +6,10 @@ import os import platform import sys -from pkg_resources import parse_version as V from functools import partial import zmq +from pkg_resources import parse_version as V from traitlets.config.application import Application diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index cab531179..2a9eaba98 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -77,7 +77,7 @@ def connection_file_ready(connection_file): kernel.terminate() kernel.wait() # Make sure all the fds get closed. - for attr in ['stdout', 'stderr', 'stdin']: + for attr in ["stdout", "stderr", "stdin"]: fid = getattr(kernel, attr) if fid: fid.close() diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 8cd9176a9..c5df456f8 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -5,12 +5,22 @@ import re import sys -from pkg_resources import parse_version as V from queue import Empty import jupyter_client import pytest -from traitlets import Bool, Dict, Enum, HasTraits, Integer, List, TraitError, Unicode, observe +from pkg_resources import parse_version as V +from traitlets import ( + Bool, + Dict, + Enum, + HasTraits, + Integer, + List, + TraitError, + Unicode, + observe, +) from .utils import TIMEOUT, execute, flush_channels, get_reply, start_global_kernel @@ -98,9 +108,9 @@ class MimeBundle(Reference): metadata = Dict() data = Dict() - @observe('data') + @observe("data") def _on_data_changed(self, change): - for k, v in change['new'].items(): + for k, v in change["new"].items(): assert mime_pat.match(k) assert isinstance(v, str) diff --git a/ipykernel/tests/test_pickleutil.py b/ipykernel/tests/test_pickleutil.py index 46e859d81..c48eadf77 100644 --- a/ipykernel/tests/test_pickleutil.py +++ b/ipykernel/tests/test_pickleutil.py @@ -1,7 +1,6 @@ import pickle import warnings - with warnings.catch_warnings(): warnings.simplefilter("ignore") from ipykernel.pickleutil import can, uncan diff --git a/setup.py b/setup.py index b1e958919..1401021cb 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ def run(self): 'appnope;platform_system=="Darwin"', "psutil", "nest_asyncio", - "setuptools>=60" # for pkg_resources + "setuptools>=60", # for pkg_resources ], extras_require={ "test": [ From eca976fcea9db56fd29daa261db7d64545255efb Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:23:25 +0000 Subject: [PATCH 0791/1195] Automated Changelog Entry for 6.11.0 on main --- CHANGELOG.md | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b11ad141..4f150baba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ +## 6.11.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.10.0...d8520c1c68e0e1c401ecc36e962cf369366c3707)) + +### Enhancements made + +- Include method signatures in experimental completion results [#895](https://github.com/ipython/ipykernel/pull/895) ([@MrBago](https://github.com/MrBago)) +- Try to pass cell id to executing kernel. [#886](https://github.com/ipython/ipykernel/pull/886) ([@Carreau](https://github.com/Carreau)) + +### Maintenance and upkeep improvements + +- Handle warnings in tests [#896](https://github.com/ipython/ipykernel/pull/896) ([@blink1073](https://github.com/blink1073)) +- Run flake and remove deprecated import [#894](https://github.com/ipython/ipykernel/pull/894) ([@blink1073](https://github.com/blink1073)) +- Add ignore-revs file [#893](https://github.com/ipython/ipykernel/pull/893) ([@blink1073](https://github.com/blink1073)) +- Autoformat with black and isort [#892](https://github.com/ipython/ipykernel/pull/892) ([@blink1073](https://github.com/blink1073)) +- Add pytest opts and pre-commit [#889](https://github.com/ipython/ipykernel/pull/889) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-03-28&to=2022-03-31&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-03-28..2022-03-31&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2022-03-28..2022-03-31&type=Issues) | [@MrBago](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AMrBago+updated%3A2022-03-28..2022-03-31&type=Issues) | [@SylvainCorlay](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2022-03-28..2022-03-31&type=Issues) + + + ## 6.10.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.9.2...3059fd97b7ccbd72e778f123bfb0ad92e7d9e9c8)) @@ -29,8 +54,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-03-14..2022-03-28&type=Issues) | [@jamadeo](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajamadeo+updated%3A2022-03-14..2022-03-28&type=Issues) | [@lesteve](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Alesteve+updated%3A2022-03-14..2022-03-28&type=Issues) | [@MrBago](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AMrBago+updated%3A2022-03-14..2022-03-28&type=Issues) | [@SylvainCorlay](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2022-03-14..2022-03-28&type=Issues) - - ## 6.9.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.9.1...d6744f9e423dacc6b317b1d31805304e89cbec5d)) From f405f58a781401c05afae3f9f2e33ff45960c406 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 31 Mar 2022 10:28:34 +0000 Subject: [PATCH 0792/1195] Publish 6.11.0 SHA256 hashes: ipykernel-6.11.0-py3-none-any.whl: 62ec17caff6e4fa1dc87ef0a6f9eff5a5d6588bb585ab1e06897e7bec9eb2819 ipykernel-6.11.0.tar.gz: 6712604531c96100f326440c11cb023da26819f2f34ba9d1ca0fb163401834e8 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 85e2e8aa1..40de43cb4 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = "6.10.0" +__version__ = "6.11.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/pyproject.toml b/pyproject.toml index ed605e2ae..bb62a3784 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.10.0" +current = "6.11.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 2e85d23d2e4af152e8011e7cf325222cdffd8e64 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 31 Mar 2022 08:34:13 -0500 Subject: [PATCH 0793/1195] Do not try to send if closed --- ipykernel/iostream.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 782ac5ad9..1cc4c8f16 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -226,6 +226,9 @@ def send_multipart(self, *args, **kwargs): def _really_send(self, msg, *args, **kwargs): """The callback that actually sends messages""" + if self.closed: + return + mp_mode = self._check_mp_mode() if mp_mode != CHILD: From edcac949bdebf0d3294abdb1e3c6fd09d0bc2281 Mon Sep 17 00:00:00 2001 From: Min RK Date: Fri, 1 Apr 2022 15:05:26 +0200 Subject: [PATCH 0794/1195] use packaging instead of pkg_resources to parse versions requiring setuptools 60 causes compatibility problems with e.g. pandas --- ipykernel/eventloops.py | 2 +- ipykernel/tests/test_message_spec.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 6f486ace1..fbf0c4cae 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -9,7 +9,7 @@ from functools import partial import zmq -from pkg_resources import parse_version as V +from packaging.version import Version as V from traitlets.config.application import Application diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index c5df456f8..c2195c099 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -9,7 +9,7 @@ import jupyter_client import pytest -from pkg_resources import parse_version as V +from packaging.version import Version as V from traitlets import ( Bool, Dict, diff --git a/setup.py b/setup.py index 1401021cb..9c626e4de 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ def run(self): 'appnope;platform_system=="Darwin"', "psutil", "nest_asyncio", - "setuptools>=60", # for pkg_resources + "packaging", ], extras_require={ "test": [ From bfac3fcd571c57d0a766ec791aa07fc4005fd434 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 4 Apr 2022 03:56:23 -0500 Subject: [PATCH 0795/1195] make cell_id optional --- ipykernel/ipkernel.py | 2 +- ipykernel/kernelbase.py | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 77ee7bfc9..3c695975f 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -304,7 +304,7 @@ async def do_execute( user_expressions=None, allow_stdin=False, *, - cell_id, + cell_id=None, ): shell = self.shell # we'll need this a lot here diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 49224608f..7c80afda1 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -749,7 +749,14 @@ async def execute_request(self, stream, ident, parent): self._abort_queues() def do_execute( - self, code, silent, store_history=True, user_expressions=None, allow_stdin=False + self, + code, + silent, + store_history=True, + user_expressions=None, + allow_stdin=False, + *, + cell_id=None, ): """Execute user code. Must be overridden by subclasses.""" raise NotImplementedError From 13c887ca6d39a302ed792d4d32d9ea9034c9f468 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 4 Apr 2022 04:33:14 -0500 Subject: [PATCH 0796/1195] clean up tests and add do_execute test --- ipykernel/inprocess/tests/test_kernel.py | 6 ++++++ pyproject.toml | 7 +++++-- setup.py | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index ed9a5aaa9..2a2311fe0 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -1,6 +1,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import asyncio import sys import unittest from contextlib import contextmanager @@ -148,3 +149,8 @@ def test_getpass_stream(self): kernel._input_request = lambda *args, **kwargs: None kernel.getpass(stream="non empty") + + def test_do_execute(self): + kernel = InProcessKernel() + asyncio.run(kernel.do_execute("a=1", True)) + assert kernel.shell.user_ns["a"] == 1 diff --git a/pyproject.toml b/pyproject.toml index bb62a3784..44bf15116 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,9 +30,9 @@ tag_template = "v{new_version}" src = "ipykernel/_version.py" [tool.pytest.ini_options] -addopts = "-raXs --durations 10 --color=yes --doctest-modules" +addopts = "-raXs --durations 10 --color=yes --doctest-modules --ignore=ipykernel/pylab/backend_inline.py --ignore=ipykernel/pylab/config.py --ignore=ipykernel/gui/gtk3embed.py --ignore=ipykernel/gui/gtkembed.py --ignore=ipykernel/datapub.py --ignore=ipykernel/log.py --ignore=ipykernel/pickleutil.py --ignore=ipykernel/serialize.py" testpaths = [ - "ipykernel/tests/" + "ipykernel/" ] timeout = 300 # Restore this setting to debug failures @@ -41,6 +41,9 @@ filterwarnings= [ # Fail on warnings "error", + # https://github.com/minrk/appnope/issues/13 + "ignore:distutils Version classes are deprecated:DeprecationWarning:appnope", + # Ignore jupyter_client warnings "ignore:unclosed Date: Mon, 4 Apr 2022 04:37:00 -0500 Subject: [PATCH 0797/1195] add another ignore --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 44bf15116..c785da6b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ tag_template = "v{new_version}" src = "ipykernel/_version.py" [tool.pytest.ini_options] -addopts = "-raXs --durations 10 --color=yes --doctest-modules --ignore=ipykernel/pylab/backend_inline.py --ignore=ipykernel/pylab/config.py --ignore=ipykernel/gui/gtk3embed.py --ignore=ipykernel/gui/gtkembed.py --ignore=ipykernel/datapub.py --ignore=ipykernel/log.py --ignore=ipykernel/pickleutil.py --ignore=ipykernel/serialize.py" +addopts = "-raXs --durations 10 --color=yes --doctest-modules --ignore=ipykernel/pylab/backend_inline.py --ignore=ipykernel/pylab/config.py --ignore=ipykernel/gui/gtk3embed.py --ignore=ipykernel/gui/gtkembed.py --ignore=ipykernel/datapub.py --ignore=ipykernel/log.py --ignore=ipykernel/pickleutil.py --ignore=ipykernel/serialize.py --ignore=ipykernel/_eventloop_macos.py" testpaths = [ "ipykernel/" ] From f313e446941caad18188a0ba8ffab1f8e08f7c8f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 4 Apr 2022 04:41:34 -0500 Subject: [PATCH 0798/1195] add another ignore --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c785da6b4..7690548eb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,6 +41,9 @@ filterwarnings= [ # Fail on warnings "error", + # Ignore our own warnings + "ignore:The `stream` parameter of `getpass.getpass` will have no effect:UserWarning", + # https://github.com/minrk/appnope/issues/13 "ignore:distutils Version classes are deprecated:DeprecationWarning:appnope", From 2beb6a11550e927cf1d231c361831197e42bd84c Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 09:53:46 +0000 Subject: [PATCH 0799/1195] Automated Changelog Entry for 6.12.0 on main --- CHANGELOG.md | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f150baba..251bf350e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ +## 6.12.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.11.0...70073edbdae17be396093be96bf880da069e7e52)) + +### Enhancements made + +- use packaging instead of pkg_resources to parse versions [#900](https://github.com/ipython/ipykernel/pull/900) ([@minrk](https://github.com/minrk)) + +### Bugs fixed + +- Make cell_id optional [#902](https://github.com/ipython/ipykernel/pull/902) ([@blink1073](https://github.com/blink1073)) +- Do not try to send on iostream if closed [#899](https://github.com/ipython/ipykernel/pull/899) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-03-31&to=2022-04-04&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-03-31..2022-04-04&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abollwyvl+updated%3A2022-03-31..2022-04-04&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2022-03-31..2022-04-04&type=Issues) + + + ## 6.11.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.10.0...d8520c1c68e0e1c401ecc36e962cf369366c3707)) @@ -25,8 +46,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-03-28..2022-03-31&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2022-03-28..2022-03-31&type=Issues) | [@MrBago](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AMrBago+updated%3A2022-03-28..2022-03-31&type=Issues) | [@SylvainCorlay](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2022-03-28..2022-03-31&type=Issues) - - ## 6.10.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.9.2...3059fd97b7ccbd72e778f123bfb0ad92e7d9e9c8)) From 83a2ede7a0f510b30e9fac950016296db0a4c016 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 09:59:52 +0000 Subject: [PATCH 0800/1195] Publish 6.12.0 SHA256 hashes: ipykernel-6.12.0-py3-none-any.whl: 27df441a61eac1ee6bbf9094789525e331cc176a633959a1ba409f583c5940c2 ipykernel-6.12.0.tar.gz: 84fae3617b70b1219d99a53888d6c404e0e0bc251d13d35dbb84fbf83cf5f894 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 40de43cb4..5a8c10a47 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = "6.11.0" +__version__ = "6.12.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/pyproject.toml b/pyproject.toml index 7690548eb..4fe287b5f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.11.0" +current = "6.12.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 223e7b9cc078457c330f6d7371819f2b89e021a9 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 4 Apr 2022 07:25:41 -0500 Subject: [PATCH 0801/1195] clean up test deps and test setup --- pyproject.toml | 9 ++------- setup.py | 2 -- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4fe287b5f..1b7494ade 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,8 @@ src = "ipykernel/_version.py" [tool.pytest.ini_options] addopts = "-raXs --durations 10 --color=yes --doctest-modules --ignore=ipykernel/pylab/backend_inline.py --ignore=ipykernel/pylab/config.py --ignore=ipykernel/gui/gtk3embed.py --ignore=ipykernel/gui/gtkembed.py --ignore=ipykernel/datapub.py --ignore=ipykernel/log.py --ignore=ipykernel/pickleutil.py --ignore=ipykernel/serialize.py --ignore=ipykernel/_eventloop_macos.py" testpaths = [ - "ipykernel/" + "ipykernel/tests", + "ipykernel/inprocess/tests" ] timeout = 300 # Restore this setting to debug failures @@ -41,12 +42,6 @@ filterwarnings= [ # Fail on warnings "error", - # Ignore our own warnings - "ignore:The `stream` parameter of `getpass.getpass` will have no effect:UserWarning", - - # https://github.com/minrk/appnope/issues/13 - "ignore:distutils Version classes are deprecated:DeprecationWarning:appnope", - # Ignore jupyter_client warnings "ignore:unclosed Date: Mon, 4 Apr 2022 07:29:06 -0500 Subject: [PATCH 0802/1195] add another ignore --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1b7494ade..4d1ca26cf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,9 @@ filterwarnings= [ # Fail on warnings "error", + # Ignore our own warnings + "ignore:The `stream` parameter of `getpass.getpass` will have no effect:UserWarning", + # Ignore jupyter_client warnings "ignore:unclosed Date: Mon, 4 Apr 2022 13:04:16 +0000 Subject: [PATCH 0803/1195] Automated Changelog Entry for 6.12.1 on main --- CHANGELOG.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 251bf350e..7df0273b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.12.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.12.0...3a04ea3fa50d01bcc09f10e3de8bb5570c2cd619)) + +### Maintenance and upkeep improvements + +- Clean up test deps and test setup [#904](https://github.com/ipython/ipykernel/pull/904) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-04-04&to=2022-04-04&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-04-04..2022-04-04&type=Issues) + + + ## 6.12.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.11.0...70073edbdae17be396093be96bf880da069e7e52)) @@ -21,8 +37,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-03-31..2022-04-04&type=Issues) | [@bollwyvl](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abollwyvl+updated%3A2022-03-31..2022-04-04&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2022-03-31..2022-04-04&type=Issues) - - ## 6.11.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.10.0...d8520c1c68e0e1c401ecc36e962cf369366c3707)) From cb5fbaa274137d042f8a35a0d615e47d84d168ef Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 13:15:55 +0000 Subject: [PATCH 0804/1195] Publish 6.12.1 SHA256 hashes: ipykernel-6.12.1-py3-none-any.whl: d840e3bf1c4b23bf6939f78dcdae639c9f6962e41d17e1c084a18c3c7f972d3a ipykernel-6.12.1.tar.gz: 0868f5561729ade444011f8ca7d3502dc9f27f7f44e20f1d5fee7e1f2b7183a1 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 5a8c10a47..7b545dd0d 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for tbump versioning -__version__ = "6.12.0" +__version__ = "6.12.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/pyproject.toml b/pyproject.toml index 4d1ca26cf..79d9375d3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.12.0" +current = "6.12.1" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 1c027bae58a4720bea88aaaa16d5138523a2dca0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Apr 2022 20:29:52 +0000 Subject: [PATCH 0805/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-prettier: v2.6.1 → v2.6.2](https://github.com/pre-commit/mirrors-prettier/compare/v2.6.1...v2.6.2) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c7c8562af..a1b3593a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: args: [--profile=black] - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.6.1 + rev: v2.6.2 hooks: - id: prettier From 51c8f5ece34ffd8fee5d57341cef727d5dc3b7cf Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 5 Apr 2022 09:19:33 +0200 Subject: [PATCH 0806/1195] Add the PID to the resource usage reply --- ipykernel/kernelbase.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 5ef16fb37..2fba5d905 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -929,7 +929,10 @@ def get_process_metric_value(self, process, name, attribute=None): return None async def usage_request(self, stream, ident, parent): - reply_content = {"hostname": socket.gethostname()} + reply_content = { + "hostname": socket.gethostname(), + "pid": os.getpid() + } current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) process_metric_value = self.get_process_metric_value From a274c4d84df8adca47b1e1e096f42b217a7dead7 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 5 Apr 2022 14:08:20 +0200 Subject: [PATCH 0807/1195] format with black --- ipykernel/kernelbase.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 2fba5d905..050aeffba 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -929,10 +929,7 @@ def get_process_metric_value(self, process, name, attribute=None): return None async def usage_request(self, stream, ident, parent): - reply_content = { - "hostname": socket.gethostname(), - "pid": os.getpid() - } + reply_content = {"hostname": socket.gethostname(), "pid": os.getpid()} current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) process_metric_value = self.get_process_metric_value From d0008b9cba45a6d9ebb78ef54bdb674ee4aa7c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Linhart?= Date: Tue, 5 Apr 2022 14:42:10 +0200 Subject: [PATCH 0808/1195] Update setup.py Add project URLs to the `setup.py`. Especially adding the source URL is beneficial since it allows tools like Renovate (https://github.com/renovatebot/renovate) handle the dependencies with features like grouping the packages based on source URLs or URL prefixes (see https://docs.renovatebot.com/configuration-options/#matchsourceurlprefixes). --- setup.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/setup.py b/setup.py index 9c626e4de..395909532 100644 --- a/setup.py +++ b/setup.py @@ -60,6 +60,12 @@ def run(self): long_description=LONG_DESCRIPTION, platforms="Linux, Mac OS X, Windows", keywords=["Interactive", "Interpreter", "Shell", "Web"], + project_urls={ + "Documentation": "https://ipython.readthedocs.io/", + "Funding": "https://numfocus.org/", + "Source": "https://github.com/ipython/ipykernel", + "Tracker": "https://github.com/ipython/ipykernel/issues", + } python_requires=">=3.7", install_requires=[ "debugpy>=1.0", From bfb7504b5caa220f8b24b1d16c7b58e1806790f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Linhart?= Date: Tue, 5 Apr 2022 14:44:41 +0200 Subject: [PATCH 0809/1195] Update setup.py Fix typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 395909532..54dcaa762 100644 --- a/setup.py +++ b/setup.py @@ -65,7 +65,7 @@ def run(self): "Funding": "https://numfocus.org/", "Source": "https://github.com/ipython/ipykernel", "Tracker": "https://github.com/ipython/ipykernel/issues", - } + }, python_requires=">=3.7", install_requires=[ "debugpy>=1.0", From 4ede2d2bf7aa3ec842c457fe79cb81c784b1ac59 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 7 Apr 2022 21:35:08 -0500 Subject: [PATCH 0810/1195] clean up pre-commit --- .github/workflows/ci.yml | 20 ++++++++++++++++++++ .pre-commit-config.yaml | 19 +++++++++++++++++++ CONTRIBUTING.md | 3 +++ 3 files changed, 42 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6c216b46..151dd3e9f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,6 +77,26 @@ jobs: run: | codecov + pre-commit: + name: pre-commit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + - uses: pre-commit/action@v2.0.0 + with: + extra_args: --all-files --hook-stage=manual + - name: Help message if pre-commit fail + if: ${{ failure() }} + run: | + echo "You can install pre-commit hooks to automatically run formatting" + echo "on each commit with:" + echo " pre-commit install" + echo "or you can run by hand on staged files with" + echo " pre-commit run" + echo "or after-the-fact on already committed files with" + echo " pre-commit run --all-files --hook-stage=manual" + test_docs: runs-on: ${{ matrix.os }} strategy: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a1b3593a7..c121ded00 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,6 +34,13 @@ repos: hooks: - id: prettier + - repo: https://github.com/PyCQA/doc8 + rev: 0.11.1 + hooks: + - id: doc8 + args: [--max-line-length=200] + stages: [manual] + - repo: https://github.com/pycqa/flake8 rev: 4.0.1 hooks: @@ -44,8 +51,20 @@ repos: "flake8-logging-format==0.6.0", "flake8-implicit-str-concat==0.2.0", ] + stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint rev: v8.12.0 hooks: - id: eslint + stages: [manual] + + - repo: https://github.com/sirosen/check-jsonschema + rev: 0.14.2 + hooks: + - id: check-jsonschema + name: "Check GitHub Workflows" + files: ^\.github/workflows/ + types: [yaml] + args: ["--schemafile", "https://json.schemastore.org/github-workflow"] + stages: [manual] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e54fe77f5..5091d692d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -42,6 +42,9 @@ hook with `pre-commit install`, you can fix everything up using `pre-commit run --all-files`. You need to make the fixing commit yourself after that. +Some of the hooks only run on CI by default, but you can invoke them by +running with the `--hook-stage manual` argument. + ## Releasing ipykernel Releasing ipykernel is _almost_ standard for a Python package: From ca30c8079ce28a0cf6b2fc23d717d8f5f6abe406 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 10 Apr 2022 06:34:45 -0500 Subject: [PATCH 0811/1195] Add basic mypy support --- .pre-commit-config.yaml | 9 ++++++++ MANIFEST.in | 1 + docs/conf.py | 6 ++--- ipykernel/_eventloop_macos.py | 4 ++-- ipykernel/_version.py | 3 ++- ipykernel/connect.py | 11 +++++---- ipykernel/debugger.py | 7 +++--- ipykernel/displayhook.py | 2 +- ipykernel/eventloops.py | 10 ++++----- ipykernel/gui/gtk3embed.py | 7 +++--- ipykernel/gui/gtkembed.py | 7 +++--- ipykernel/heartbeat.py | 2 +- ipykernel/inprocess/blocking.py | 2 +- ipykernel/inprocess/channels.py | 2 +- ipykernel/inprocess/socket.py | 2 +- ipykernel/iostream.py | 24 +++++++++++--------- ipykernel/ipkernel.py | 9 ++++---- ipykernel/jsonutil.py | 2 +- ipykernel/kernelapp.py | 8 ++++--- ipykernel/kernelbase.py | 10 +++++---- ipykernel/log.py | 4 ++-- ipykernel/parentpoller.py | 8 +++---- ipykernel/pickleutil.py | 18 ++++++++------- ipykernel/py.typed | 0 ipykernel/pylab/backend_inline.py | 2 +- ipykernel/pylab/config.py | 2 +- ipykernel/serialize.py | 6 ++--- ipykernel/trio_runner.py | 2 +- ipykernel/zmqshell.py | 4 ++-- pyproject.toml | 37 +++++++++++++++++++++++++++++++ setup.py | 8 +++---- 31 files changed, 142 insertions(+), 77 deletions(-) create mode 100644 ipykernel/py.typed diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c121ded00..0ff9a5bff 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -41,6 +41,15 @@ repos: args: [--max-line-length=200] stages: [manual] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.942 + hooks: + - id: mypy + exclude: 'ipykernel.*tests' + args: ["--config-file", "pyproject.toml"] + additional_dependencies: [tornado, jupyter_client, pytest] + stages: [manual] + - repo: https://github.com/pycqa/flake8 rev: 4.0.1 hooks: diff --git a/MANIFEST.in b/MANIFEST.in index e75739460..1fb3d59ee 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,6 @@ include *.md include pyproject.toml +include ipykernel/py.typed # Documentation graft docs diff --git a/docs/conf.py b/docs/conf.py index a1ad70d12..95f7219ec 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,7 +61,7 @@ # built documents. # -version_ns = {} +version_ns: dict = {} here = os.path.dirname(__file__) version_py = os.path.join(here, os.pardir, "ipykernel", "_version.py") with open(version_py) as f: @@ -150,7 +150,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = [] +html_static_path: list = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -217,7 +217,7 @@ # -- Options for LaTeX output --------------------------------------------- -latex_elements = {} +latex_elements: dict = {} # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, diff --git a/ipykernel/_eventloop_macos.py b/ipykernel/_eventloop_macos.py index 94762b65d..7598f8f6e 100644 --- a/ipykernel/_eventloop_macos.py +++ b/ipykernel/_eventloop_macos.py @@ -10,7 +10,7 @@ import ctypes.util from threading import Event -objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) +objc = ctypes.cdll.LoadLibrary(ctypes.util.find_library("objc")) # type:ignore[arg-type] void_p = ctypes.c_void_p @@ -42,7 +42,7 @@ def C(classname): # end obj-c boilerplate from appnope # CoreFoundation C-API calls we will use: -CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library("CoreFoundation")) +CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library("CoreFoundation")) # type:ignore[arg-type] CFAbsoluteTimeGetCurrent = CoreFoundation.CFAbsoluteTimeGetCurrent CFAbsoluteTimeGetCurrent.restype = ctypes.c_double diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7b545dd0d..31dc1d3c5 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -9,7 +9,8 @@ # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" match = re.match(pattern, __version__) -parts = [int(match[part]) for part in ["major", "minor", "patch"]] +assert match is not None +parts: list = [int(match[part]) for part in ["major", "minor", "patch"]] if match["rest"]: parts.append(match["rest"]) version_info = tuple(parts) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index b56b9b8ff..39e00e25a 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -6,6 +6,7 @@ import json import sys from subprocess import PIPE, Popen +from typing import Any import jupyter_client from jupyter_client import write_connection_file @@ -68,13 +69,15 @@ def get_connection_info(connection_file=None, unpack=False): cf = _find_connection_file(connection_file) with open(cf) as f: - info = f.read() + info_str = f.read() if unpack: - info = json.loads(info) + info = json.loads(info_str) # ensure key is bytes: info["key"] = info.get("key", "").encode() - return info + return info + + return info_str def connect_qtconsole(connection_file=None, argv=None): @@ -105,7 +108,7 @@ def connect_qtconsole(connection_file=None, argv=None): cmd = ";".join(["from IPython.qt.console import qtconsoleapp", "qtconsoleapp.main()"]) - kwargs = {} + kwargs: dict[str, Any] = {} # Launch the Qt console in a separate session & process group, so # interrupting the kernel doesn't kill it. kwargs["start_new_session"] = True diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 8368acccc..da275ed21 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,6 +1,7 @@ import os import re import sys +import typing as t import zmq from IPython.core.getipython import get_ipython @@ -89,7 +90,7 @@ def __init__(self, event_callback, log): self.tcp_buffer = "" self._reset_tcp_pos() self.event_callback = event_callback - self.message_queue = Queue() + self.message_queue: Queue = Queue() self.log = log def _reset_tcp_pos(self): @@ -100,7 +101,7 @@ def _reset_tcp_pos(self): def _put_message(self, raw_msg): self.log.debug("QUEUE - _put_message:") - msg = jsonapi.loads(raw_msg) + msg = t.cast(dict, jsonapi.loads(raw_msg)) if msg["type"] == "event": self.log.debug("QUEUE - received event:") self.log.debug(msg) @@ -290,7 +291,7 @@ def __init__( self.is_started = False self.event_callback = event_callback self.just_my_code = just_my_code - self.stopped_queue = Queue() + self.stopped_queue: Queue = Queue() self.started_debug_handlers = {} for msg_type in Debugger.started_debug_msg_types: diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index 2ba2c08c3..e7c98c01d 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -32,7 +32,7 @@ def __call__(self, obj): if obj is None: return - builtins._ = obj + builtins._ = obj # type:ignore[attr-defined] sys.stdout.flush() sys.stderr.flush() contents = { diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index fbf0c4cae..fdc5ddd60 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -248,7 +248,7 @@ def process_stream_events(stream, *a, **kw): notifier = partial(process_stream_events, kernel.shell_stream) # seems to be needed for tk - notifier.__name__ = "notifier" + notifier.__name__ = "notifier" # type:ignore[attr-defined] app.tk.createfilehandler(kernel.shell_stream.getsockopt(zmq.FD), READABLE, notifier) # schedule initial call after start app.after(0, notifier) @@ -386,7 +386,7 @@ def loop_asyncio(kernel): # main loop is closed, create a new one loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - loop._should_close = False + loop._should_close = False # type:ignore[attr-defined] # pause eventloop when there's an event on a zmq socket def process_stream_events(stream): @@ -406,7 +406,7 @@ def process_stream_events(stream): continue except Exception as e: error = e - if loop._should_close: + if loop._should_close: # type:ignore[attr-defined] loop.close() if error is not None: raise error @@ -424,14 +424,14 @@ def loop_asyncio_exit(kernel): def close_loop(): if hasattr(loop, "shutdown_asyncgens"): yield from loop.shutdown_asyncgens() - loop._should_close = True + loop._should_close = True # type:ignore[attr-defined] loop.stop() if loop.is_running(): close_loop() elif not loop.is_closed(): - loop.run_until_complete(close_loop) + loop.run_until_complete(close_loop) # type:ignore[call-overload] loop.close() diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index 198f8cacd..1a9542a9b 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -14,11 +14,11 @@ import sys # Third-party -import gi +import gi # type:ignore[import] gi.require_version("Gdk", "3.0") gi.require_version("Gtk", "3.0") -from gi.repository import GObject, Gtk +from gi.repository import GObject, Gtk # type:ignore[import] # ----------------------------------------------------------------------------- # Classes and functions @@ -63,7 +63,8 @@ def stop(self): # FIXME: this one isn't getting called because we have no reliable # kernel shutdown. We need to fix that: once the kernel has a # shutdown mechanism, it can call this. - self.gtk_main_quit() + if self.gtk_main_quit: + self.gtk_main_quit() sys.exit() def _hijack_gtk(self): diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index 19522c441..47cf48421 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -14,8 +14,8 @@ import sys # Third-party -import gobject -import gtk +import gobject # type:ignore[import] +import gtk # type:ignore[import] # ----------------------------------------------------------------------------- # Classes and functions @@ -60,7 +60,8 @@ def stop(self): # FIXME: this one isn't getting called because we have no reliable # kernel shutdown. We need to fix that: once the kernel has a # shutdown mechanism, it can call this. - self.gtk_main_quit() + if self.gtk_main_quit: + self.gtk_main_quit() sys.exit() def _hijack_gtk(self): diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 6f5ddbec3..6f3bd9092 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -64,7 +64,7 @@ def _try_bind_socket(self): def _bind_socket(self): try: - win_in_use = errno.WSAEADDRINUSE + win_in_use = errno.WSAEADDRINUSE # type:ignore[attr-defined] except AttributeError: win_in_use = None diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index 269eb4158..1ac7829fc 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -23,7 +23,7 @@ class BlockingInProcessChannel(InProcessChannel): def __init__(self, *args, **kwds): super().__init__(*args, **kwds) - self._in_queue = Queue() + self._in_queue: Queue = Queue() def call_handlers(self, msg): self._in_queue.put(msg) diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index b81d69321..6648a0f37 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -13,7 +13,7 @@ class InProcessChannel: """Base class for in-process channels.""" - proxy_methods = [] + proxy_methods: list = [] def __init__(self, client=None): super().__init__() diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index 477c36a47..ff826ea0c 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -31,7 +31,7 @@ def recv_multipart(self, flags=0, copy=True, track=False): return self.queue.get_nowait() def send_multipart(self, msg_parts, flags=0, copy=True, track=False): - msg_parts = list(map(zmq.Message, msg_parts)) + msg_parts = list(map(zmq.Message, msg_parts)) # type:ignore[arg-type] self.queue.put_nowait(msg_parts) self.message_sent += 1 diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 1cc4c8f16..f2737ebc1 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -13,6 +13,7 @@ from binascii import b2a_hex from collections import deque from io import StringIO, TextIOBase +from typing import Any, Optional from weakref import WeakSet import zmq @@ -66,13 +67,13 @@ def __init__(self, socket, pipe=False): if pipe: self._setup_pipe_in() self._local = threading.local() - self._events = deque() - self._event_pipes = WeakSet() + self._events: deque = deque() + self._event_pipes: WeakSet = WeakSet() self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main, name="IOPub") self.thread.daemon = True - self.thread.pydev_do_not_trace = True - self.thread.is_pydev_daemon_thread = True + self.thread.pydev_do_not_trace = True # type:ignore[attr-defined] + self.thread.is_pydev_daemon_thread = True # type:ignore[attr-defined] self.thread.name = "IOPub" def _thread_main(self): @@ -256,7 +257,8 @@ def __getattr__(self, attr): """Wrap socket attr access for backward-compatibility""" if attr.startswith("__") and attr.endswith("__"): # don't wrap magic methods - super().__getattr__(attr) + super().__getattr__(attr) # type:ignore[misc] + assert self.io_thread is not None if hasattr(self.io_thread.socket, attr): warnings.warn( f"Accessing zmq Socket attribute {attr} on BackgroundSocket" @@ -266,7 +268,7 @@ def __getattr__(self, attr): stacklevel=2, ) return getattr(self.io_thread.socket, attr) - super().__getattr__(attr) + super().__getattr__(attr) # type:ignore[misc] def __setattr__(self, attr, value): if attr == "io_thread" or (attr.startswith("__" and attr.endswith("__"))): @@ -279,6 +281,7 @@ def __setattr__(self, attr, value): DeprecationWarning, stacklevel=2, ) + assert self.io_thread is not None setattr(self.io_thread.socket, attr, value) def send(self, msg, *args, **kwargs): @@ -286,6 +289,7 @@ def send(self, msg, *args, **kwargs): def send_multipart(self, *args, **kwargs): """Schedule send in IO thread""" + assert self.io_thread is not None return self.io_thread.send_multipart(*args, **kwargs) @@ -302,6 +306,7 @@ class OutStream(TextIOBase): flush_interval = 0.2 topic = None encoding = "UTF-8" + _exc: Optional[Any] = None def fileno(self): """ @@ -362,8 +367,7 @@ def __init__( """ if pipe is not None: warnings.warn( - "pipe argument to OutStream is deprecated and ignored", - " since ipykernel 4.2.3.", + "pipe argument to OutStream is deprecated and ignored since ipykernel 4.2.3.", DeprecationWarning, stacklevel=2, ) @@ -518,7 +522,7 @@ def _flush(self): ident=self.topic, ) - def write(self, string: str) -> int: + def write(self, string: str) -> Optional[int]: # type:ignore[override] """Write to current stream after encoding if necessary Returns @@ -550,7 +554,7 @@ def write(self, string: str) -> int: # mp.Pool cannot be trusted to flush promptly (or ever), # and this helps. if self._subprocess_flush_pending: - return + return None self._subprocess_flush_pending = True # We can not rely on self._io_loop.call_later from a subprocess self.pub_thread.schedule(self._flush) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 3c695975f..889782cc7 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -5,6 +5,7 @@ import getpass import signal import sys +import typing as t from contextlib import contextmanager from functools import partial @@ -242,7 +243,7 @@ def _restore_input(self): getpass.getpass = self._save_getpass - @property + @property # type:ignore[override] def execution_count(self): return self.shell.execution_count @@ -263,7 +264,7 @@ def _cancel_on_sigint(self, future): but this turns it into a CancelledError. At least it gets a decent traceback to the user. """ - sigint_future = asyncio.Future() + sigint_future: asyncio.Future[int] = asyncio.Future() # whichever future finishes first, # cancel the other one @@ -310,7 +311,7 @@ async def do_execute( self._forward_input(allow_stdin) - reply_content = {} + reply_content: dict[str, t.Any] = {} if hasattr(shell, "run_cell_async") and hasattr(shell, "should_run_async"): run_cell = shell.run_cell_async should_run_async = shell.should_run_async @@ -506,7 +507,7 @@ def _experimental_do_complete(self, code, cursor_pos): def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): name = token_at_cursor(code, cursor_pos) - reply_content = {"status": "ok"} + reply_content: dict[str, t.Any] = {"status": "ok"} reply_content["data"] = {} reply_content["metadata"] = {} try: diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 36565a842..eafc171c3 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -98,7 +98,7 @@ def json_clean(obj): it simply sanitizes it so that there will be no encoding errors later. """ - if JUPYTER_CLIENT_MAJOR_VERSION >= 7: + if int(JUPYTER_CLIENT_MAJOR_VERSION) >= 7: return obj # types that are 'atomic' and ok in json as-is. diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 6eba8f50c..87bf36c82 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -232,7 +232,7 @@ def _try_bind_socket(self, s, port): def _bind_socket(self, s, port): try: - win_in_use = errno.WSAEADDRINUSE + win_in_use = errno.WSAEADDRINUSE # type:ignore[attr-defined] except AttributeError: win_in_use = None @@ -464,7 +464,7 @@ def init_io(self): self.log.debug("Seeing logger to stderr, rerouting to raw filedescriptor.") handler.stream = TextIOWrapper( - FileIO(sys.stderr._original_stdstream_copy, "w") + FileIO(sys.stderr._original_stdstream_copy, "w") # type:ignore[attr-defined] ) if self.displayhook_class: displayhook_factory = import_item(str(self.displayhook_class)) @@ -560,11 +560,13 @@ def init_gui_pylab(self): # is not associated with any execute request. shell = self.shell + assert shell is not None _showtraceback = shell._showtraceback try: # replace error-sending traceback with stderr def print_tb(etype, evalue, stb): print("GUI event loop or pylab initialization failed", file=sys.stderr) + assert shell is not None print(shell.InteractiveTB.stb2text(stb), file=sys.stderr) shell._showtraceback = print_tb @@ -644,7 +646,7 @@ def init_pdb(self): if hasattr(debugger, "InterruptiblePdb"): # Only available in newer IPython releases: debugger.Pdb = debugger.InterruptiblePdb - pdb.Pdb = debugger.Pdb + pdb.Pdb = debugger.Pdb # type:ignore[misc] pdb.set_trace = debugger.set_trace @catch_config_error diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 62059ef2b..80cdae5cd 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -12,6 +12,7 @@ import socket import sys import time +import typing as t import uuid import warnings from datetime import datetime @@ -142,7 +143,7 @@ def _default_ident(self): # This should be overridden by wrapper kernels that implement any real # language. - language_info = {} + language_info: dict[str, object] = {} # any links that should go in the help menu help_links = List() @@ -262,7 +263,7 @@ def __init__(self, **kwargs): for msg_type in self.control_msg_types: self.control_handlers[msg_type] = getattr(self, msg_type) - self.control_queue = Queue() + self.control_queue: Queue = Queue() def dispatch_control(self, msg): self.control_queue.put_nowait(msg) @@ -278,6 +279,7 @@ async def poll_control_queue(self): async def _flush_control_queue(self): """Flush the control queue, wait for processing of any pending messages""" + tracer_future: t.Union[concurrent.futures.Future, asyncio.Future] if self.control_thread: control_loop = self.control_thread.io_loop # concurrent.futures.Futures are threadsafe @@ -529,7 +531,7 @@ def schedule_dispatch(self, dispatch, *args): def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() - self.msg_queue = Queue() + self.msg_queue: Queue = Queue() self.io_loop.add_callback(self.dispatch_queue) self.control_stream.on_recv(self.dispatch_control, copy=False) @@ -1191,7 +1193,7 @@ def _input_request(self, prompt, ident, parent, password=False): # zmq.select() is also uninterruptible, but at least this # way reads get noticed immediately and KeyboardInterrupts # get noticed fairly quickly by human response time standards. - rlist, _, xlist = zmq.select([self.stdin_socket], [], [self.stdin_socket], 0.01) + rlist, _, xlist = zmq.select([self.stdin_socket], [], [self.stdin_socket], 0.01) # type:ignore[arg-type] if rlist or xlist: ident, reply = self.session.recv(self.stdin_socket) if (ident, reply) != (None, None): diff --git a/ipykernel/log.py b/ipykernel/log.py index 66bb6722c..aca21d25b 100644 --- a/ipykernel/log.py +++ b/ipykernel/log.py @@ -18,11 +18,11 @@ def __init__(self, engine, *args, **kwargs): PUBHandler.__init__(self, *args, **kwargs) self.engine = engine - @property + @property # type:ignore[misc] def root_topic(self): """this is a property, in case the handler is created before the engine gets registered with an id""" if isinstance(getattr(self.engine, "id", None), int): - return "engine.%i" % self.engine.id + return "engine.%i" % self.engine.id # type:ignore[union-attr] else: return "engine" diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index d120f5000..a18f0440b 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -4,7 +4,7 @@ try: import ctypes except ImportError: - ctypes = None + ctypes = None # type:ignore[assignment] import os import platform import signal @@ -71,9 +71,9 @@ def __init__(self, interrupt_handle=None, parent_handle=None): def run(self): """Run the poll loop. This method never returns.""" try: - from _winapi import INFINITE, WAIT_OBJECT_0 + from _winapi import INFINITE, WAIT_OBJECT_0 # type:ignore[attr-defined] except ImportError: - from _subprocess import INFINITE, WAIT_OBJECT_0 + from _subprocess import INFINITE, WAIT_OBJECT_0 # type:ignore[import] # Build the list of handle to listen on. handles = [] @@ -86,7 +86,7 @@ def run(self): # Listen forever. while True: - result = ctypes.windll.kernel32.WaitForMultipleObjects( + result = ctypes.windll.kernel32.WaitForMultipleObjects( # type:ignore[attr-defined] len(handles), # nCount (c_int * len(handles))(*handles), # lpHandles False, # bWaitAll diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 9ebd904d7..8f612e65a 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -35,7 +35,7 @@ def _get_cell_type(a=None): def inner(): return a - return type(inner.__closure__[0]) + return type(inner.__closure__[0]) # type:ignore[index] cell_type = _get_cell_type() @@ -72,7 +72,7 @@ def use_dill(): adds support for object methods and closures to serialization. """ # import dill causes most of the magic - import dill + import dill # type:ignore[import] # dill doesn't work with cPickle, # tell the two relevant modules to use plain pickle @@ -96,7 +96,7 @@ def use_cloudpickle(): adds support for object methods and closures to serialization. """ - import cloudpickle + import cloudpickle # type:ignore[import] global pickle pickle = cloudpickle @@ -188,18 +188,20 @@ def get_object(self, g=None): def inner(): return cell_contents - return inner.__closure__[0] + return inner.__closure__[0] # type:ignore[index] class CannedFunction(CannedObject): def __init__(self, f): self._check_type(f) self.code = f.__code__ + self.defaults: typing.Optional[list] if f.__defaults__: self.defaults = [can(fd) for fd in f.__defaults__] else: self.defaults = None + self.closure: typing.Optional[tuple] closure = f.__closure__ if closure: self.closure = tuple(can(cell) for cell in closure) @@ -260,7 +262,7 @@ def get_object(self, g=None): class CannedArray(CannedObject): def __init__(self, obj): - from numpy import ascontiguousarray + from numpy import ascontiguousarray # type:ignore[import] self.shape = obj.shape self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str @@ -310,11 +312,11 @@ def get_object(self, g=None): class CannedBuffer(CannedBytes): - wrap = buffer + wrap = buffer # type:ignore[assignment] class CannedMemoryView(CannedBytes): - wrap = memoryview + wrap = memoryview # type:ignore[assignment] # ------------------------------------------------------------------------------- @@ -459,7 +461,7 @@ def uncan_sequence(obj, g=None): if buffer is not memoryview: can_map[buffer] = CannedBuffer -uncan_map = { +uncan_map: dict[type, typing.Callable] = { CannedObject: lambda obj, g: obj.get_object(g), dict: uncan_dict, } diff --git a/ipykernel/py.typed b/ipykernel/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index b1627cac5..1466c9d34 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.backend_inline import * # analysis: ignore # noqa F401 +from matplotlib_inline.backend_inline import * # type:ignore[import] # analysis: ignore # noqa F401 warnings.warn( "`ipykernel.pylab.backend_inline` is deprecated, directly " diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index 7622f9e11..8fbb53bae 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.config import * # analysis: ignore # noqa F401 +from matplotlib_inline.config import * # type:ignore[import] # analysis: ignore # noqa F401 warnings.warn( "`ipykernel.pylab.config` is deprecated, directly use `matplotlib_inline.config`", diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 6a1b79f78..6b77d8536 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -188,11 +188,11 @@ def unpack_apply_message(bufs, g=None, copy=True): info = pickle.loads(pinfo) arg_bufs, kwarg_bufs = bufs[: info["narg_bufs"]], bufs[info["narg_bufs"] :] - args = [] + args_list = [] for _ in range(info["nargs"]): arg, arg_bufs = deserialize_object(arg_bufs, g) - args.append(arg) - args = tuple(args) + args_list.append(arg) + args = tuple(args_list) assert not arg_bufs, "Shouldn't be any arg bufs left over" kwargs = {} diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index c72f6455e..7b8f2166c 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -41,7 +41,7 @@ async def trio_main(): # TODO This hack prevents the nursery from cancelling all child # tasks when an uncaught exception occurs, but it's ugly. nursery._add_exc = log_nursery_exc - builtins.GLOBAL_NURSERY = nursery + builtins.GLOBAL_NURSERY = nursery # type:ignore[attr-defined] await trio.sleep_forever() trio.run(trio_main) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 1d6627cb4..45ac57507 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -575,11 +575,11 @@ def set_parent(self, parent): if hasattr(self, "_data_pub"): self.data_pub.set_parent(parent) try: - sys.stdout.set_parent(parent) + sys.stdout.set_parent(parent) # type:ignore[attr-defined] except AttributeError: pass try: - sys.stderr.set_parent(parent) + sys.stderr.set_parent(parent) # type:ignore[attr-defined] except AttributeError: pass diff --git a/pyproject.toml b/pyproject.toml index 79d9375d3..6bbf2801e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,43 @@ tag_template = "v{new_version}" [[tool.tbump.file]] src = "ipykernel/_version.py" +[tool.mypy] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +pretty = true +show_error_context = true +show_error_codes = true +strict_equality = true +warn_unused_configs = true +warn_unused_ignores = true +warn_redundant_casts = true + +[[tool.mypy.overrides]] +module = [ + "appnope", + "traitlets.*", + "jupyter_core.*", + "jupyter_console.*", + "_pydevd_bundle.*", + "debugpy.*", + "nest_asyncio", + "flaky", + "ipykernel.*", + "ipyparallel.*", + "IPython.*", + "entrypoints", + "trio", + "qtconsole.*", + "psutil", + "wx", + "PyQt4", + "PyQt5", + "PySide2" +] +ignore_missing_imports = true + [tool.pytest.ini_options] addopts = "-raXs --durations 10 --color=yes --doctest-modules --ignore=ipykernel/pylab/backend_inline.py --ignore=ipykernel/pylab/config.py --ignore=ipykernel/gui/gtk3embed.py --ignore=ipykernel/gui/gtkembed.py --ignore=ipykernel/datapub.py --ignore=ipykernel/log.py --ignore=ipykernel/pickleutil.py --ignore=ipykernel/serialize.py --ignore=ipykernel/_eventloop_macos.py" testpaths = [ diff --git a/setup.py b/setup.py index 54dcaa762..e28c54b78 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ import sys from glob import glob -from setuptools import setup -from setuptools.command.bdist_egg import bdist_egg +from setuptools import setup # type:ignore[import] +from setuptools.command.bdist_egg import bdist_egg # type:ignore[import] # the name of the package name = "ipykernel" @@ -36,13 +36,13 @@ def run(self): packages.append(d[len(here) + 1 :].replace(os.path.sep, ".")) package_data = { - "ipykernel": ["resources/*.*"], + "ipykernel": ["resources/*.*", "py.typed"], } with open(pjoin(here, "README.md")) as fid: LONG_DESCRIPTION = fid.read() -setup_args = dict( +setup_args: dict[str, object] = dict( name=name, cmdclass={ "bdist_egg": bdist_egg if "bdist_egg" in sys.argv else bdist_egg_disabled, From 29eb387d36605a9fe154075aec36babebebc0cf4 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 10 Apr 2022 06:39:41 -0500 Subject: [PATCH 0812/1195] clean up list --- pyproject.toml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6bbf2801e..3f20fd84a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,25 +44,25 @@ warn_redundant_casts = true [[tool.mypy.overrides]] module = [ - "appnope", - "traitlets.*", - "jupyter_core.*", - "jupyter_console.*", "_pydevd_bundle.*", + "appnope", "debugpy.*", - "nest_asyncio", + "entrypoints", "flaky", "ipykernel.*", "ipyparallel.*", "IPython.*", - "entrypoints", - "trio", - "qtconsole.*", + "jupyter_console.*", + "jupyter_core.*", + "nest_asyncio", "psutil", - "wx", + "PySide2", "PyQt4", "PyQt5", - "PySide2" + "qtconsole.*", + "traitlets.*", + "trio", + "wx", ] ignore_missing_imports = true From 537604ab9471d9f6a3565e7e12bd616c2d977571 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 10 Apr 2022 06:40:04 -0500 Subject: [PATCH 0813/1195] clean up list --- .pre-commit-config.yaml | 2 +- ipykernel/_eventloop_macos.py | 4 +++- ipykernel/kernelapp.py | 4 +++- ipykernel/kernelbase.py | 4 +++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0ff9a5bff..41a7de4c2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,7 +45,7 @@ repos: rev: v0.942 hooks: - id: mypy - exclude: 'ipykernel.*tests' + exclude: "ipykernel.*tests" args: ["--config-file", "pyproject.toml"] additional_dependencies: [tornado, jupyter_client, pytest] stages: [manual] diff --git a/ipykernel/_eventloop_macos.py b/ipykernel/_eventloop_macos.py index 7598f8f6e..896d991c3 100644 --- a/ipykernel/_eventloop_macos.py +++ b/ipykernel/_eventloop_macos.py @@ -42,7 +42,9 @@ def C(classname): # end obj-c boilerplate from appnope # CoreFoundation C-API calls we will use: -CoreFoundation = ctypes.cdll.LoadLibrary(ctypes.util.find_library("CoreFoundation")) # type:ignore[arg-type] +CoreFoundation = ctypes.cdll.LoadLibrary( + ctypes.util.find_library("CoreFoundation") +) # type:ignore[arg-type] CFAbsoluteTimeGetCurrent = CoreFoundation.CFAbsoluteTimeGetCurrent CFAbsoluteTimeGetCurrent.restype = ctypes.c_double diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 87bf36c82..14432e117 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -464,7 +464,9 @@ def init_io(self): self.log.debug("Seeing logger to stderr, rerouting to raw filedescriptor.") handler.stream = TextIOWrapper( - FileIO(sys.stderr._original_stdstream_copy, "w") # type:ignore[attr-defined] + FileIO( + sys.stderr._original_stdstream_copy, "w" + ) # type:ignore[attr-defined] ) if self.displayhook_class: displayhook_factory = import_item(str(self.displayhook_class)) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 80cdae5cd..33a5e30ec 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1193,7 +1193,9 @@ def _input_request(self, prompt, ident, parent, password=False): # zmq.select() is also uninterruptible, but at least this # way reads get noticed immediately and KeyboardInterrupts # get noticed fairly quickly by human response time standards. - rlist, _, xlist = zmq.select([self.stdin_socket], [], [self.stdin_socket], 0.01) # type:ignore[arg-type] + rlist, _, xlist = zmq.select( + [self.stdin_socket], [], [self.stdin_socket], 0.01 + ) # type:ignore[arg-type] if rlist or xlist: ident, reply = self.session.recv(self.stdin_socket) if (ident, reply) != (None, None): From 42f6ae345bd27a56205903bde5faae7db3080196 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 10 Apr 2022 06:45:10 -0500 Subject: [PATCH 0814/1195] fix ignores --- ipykernel/_eventloop_macos.py | 4 ++-- ipykernel/kernelapp.py | 4 ++-- ipykernel/kernelbase.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ipykernel/_eventloop_macos.py b/ipykernel/_eventloop_macos.py index 896d991c3..3a6692fce 100644 --- a/ipykernel/_eventloop_macos.py +++ b/ipykernel/_eventloop_macos.py @@ -43,8 +43,8 @@ def C(classname): # CoreFoundation C-API calls we will use: CoreFoundation = ctypes.cdll.LoadLibrary( - ctypes.util.find_library("CoreFoundation") -) # type:ignore[arg-type] + ctypes.util.find_library("CoreFoundation") # type:ignore[arg-type] +) CFAbsoluteTimeGetCurrent = CoreFoundation.CFAbsoluteTimeGetCurrent CFAbsoluteTimeGetCurrent.restype = ctypes.c_double diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 14432e117..72043d6a0 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -465,8 +465,8 @@ def init_io(self): handler.stream = TextIOWrapper( FileIO( - sys.stderr._original_stdstream_copy, "w" - ) # type:ignore[attr-defined] + sys.stderr._original_stdstream_copy, "w" # type:ignore[attr-defined] + ) ) if self.displayhook_class: displayhook_factory = import_item(str(self.displayhook_class)) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 33a5e30ec..e448e6544 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1194,8 +1194,8 @@ def _input_request(self, prompt, ident, parent, password=False): # way reads get noticed immediately and KeyboardInterrupts # get noticed fairly quickly by human response time standards. rlist, _, xlist = zmq.select( - [self.stdin_socket], [], [self.stdin_socket], 0.01 - ) # type:ignore[arg-type] + [self.stdin_socket], [], [self.stdin_socket], 0.01 # type:ignore[arg-type] + ) if rlist or xlist: ident, reply = self.session.recv(self.stdin_socket) if (ident, reply) != (None, None): From b51eb0048089505920ebdfe182541c169de7962f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 10 Apr 2022 06:57:42 -0500 Subject: [PATCH 0815/1195] more cleanup --- docs/conf.py | 7 +++--- examples/embedding/inprocess_terminal.py | 2 +- ipykernel/_version.py | 2 +- ipykernel/debugger.py | 6 ++--- ipykernel/gui/gtk3embed.py | 4 +-- ipykernel/gui/gtkembed.py | 4 +-- ipykernel/inprocess/blocking.py | 2 +- ipykernel/inprocess/channels.py | 2 +- ipykernel/iostream.py | 6 ++--- ipykernel/kernelbase.py | 6 ++--- ipykernel/parentpoller.py | 2 +- ipykernel/pickleutil.py | 16 ++++++------ ipykernel/pylab/backend_inline.py | 2 +- ipykernel/pylab/config.py | 2 +- pyproject.toml | 32 ++++++------------------ setup.py | 4 +-- 16 files changed, 41 insertions(+), 58 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 95f7219ec..33e1c2dba 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,6 +14,7 @@ import os import shutil +from typing import Any # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -61,7 +62,7 @@ # built documents. # -version_ns: dict = {} +version_ns: dict[str, Any] = {} here = os.path.dirname(__file__) version_py = os.path.join(here, os.pardir, "ipykernel", "_version.py") with open(version_py) as f: @@ -150,7 +151,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path: list = [] +html_static_path: list[str] = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -217,7 +218,7 @@ # -- Options for LaTeX output --------------------------------------------- -latex_elements: dict = {} +latex_elements: dict[str, object] = {} # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index 543adba64..4121619d0 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -4,7 +4,7 @@ import tornado from jupyter_console.ptshell import ZMQTerminalInteractiveShell -from ipykernel.inprocess import InProcessKernelManager +from ipykernel.inprocess.manager import InProcessKernelManager def print_process_id(): diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 31dc1d3c5..32c639edc 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -10,7 +10,7 @@ pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" match = re.match(pattern, __version__) assert match is not None -parts: list = [int(match[part]) for part in ["major", "minor", "patch"]] +parts: list[object] = [int(match[part]) for part in ["major", "minor", "patch"]] if match["rest"]: parts.append(match["rest"]) version_info = tuple(parts) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index da275ed21..e025de24e 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -90,7 +90,7 @@ def __init__(self, event_callback, log): self.tcp_buffer = "" self._reset_tcp_pos() self.event_callback = event_callback - self.message_queue: Queue = Queue() + self.message_queue: Queue[t.Any] = Queue() self.log = log def _reset_tcp_pos(self): @@ -101,7 +101,7 @@ def _reset_tcp_pos(self): def _put_message(self, raw_msg): self.log.debug("QUEUE - _put_message:") - msg = t.cast(dict, jsonapi.loads(raw_msg)) + msg = t.cast(dict[str, t.Any], jsonapi.loads(raw_msg)) if msg["type"] == "event": self.log.debug("QUEUE - received event:") self.log.debug(msg) @@ -291,7 +291,7 @@ def __init__( self.is_started = False self.event_callback = event_callback self.just_my_code = just_my_code - self.stopped_queue: Queue = Queue() + self.stopped_queue: Queue[t.Any] = Queue() self.started_debug_handlers = {} for msg_type in Debugger.started_debug_msg_types: diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index 1a9542a9b..baef9fd71 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -14,11 +14,11 @@ import sys # Third-party -import gi # type:ignore[import] +import gi gi.require_version("Gdk", "3.0") gi.require_version("Gtk", "3.0") -from gi.repository import GObject, Gtk # type:ignore[import] +from gi.repository import GObject, Gtk # ----------------------------------------------------------------------------- # Classes and functions diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index 47cf48421..ed6647226 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -14,8 +14,8 @@ import sys # Third-party -import gobject # type:ignore[import] -import gtk # type:ignore[import] +import gobject +import gtk # ----------------------------------------------------------------------------- # Classes and functions diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index 1ac7829fc..17f334f48 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -23,7 +23,7 @@ class BlockingInProcessChannel(InProcessChannel): def __init__(self, *args, **kwds): super().__init__(*args, **kwds) - self._in_queue: Queue = Queue() + self._in_queue: Queue[object] = Queue() def call_handlers(self, msg): self._in_queue.put(msg) diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 6648a0f37..a0dff04f7 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -13,7 +13,7 @@ class InProcessChannel: """Base class for in-process channels.""" - proxy_methods: list = [] + proxy_methods: list[object] = [] def __init__(self, client=None): super().__init__() diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index f2737ebc1..6255d717e 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -13,7 +13,7 @@ from binascii import b2a_hex from collections import deque from io import StringIO, TextIOBase -from typing import Any, Optional +from typing import Any, Optional, Callable from weakref import WeakSet import zmq @@ -67,8 +67,8 @@ def __init__(self, socket, pipe=False): if pipe: self._setup_pipe_in() self._local = threading.local() - self._events: deque = deque() - self._event_pipes: WeakSet = WeakSet() + self._events: deque[Callable[..., Any]] = deque() + self._event_pipes: WeakSet[Any] = WeakSet() self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main, name="IOPub") self.thread.daemon = True diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e448e6544..d4530be64 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -263,7 +263,7 @@ def __init__(self, **kwargs): for msg_type in self.control_msg_types: self.control_handlers[msg_type] = getattr(self, msg_type) - self.control_queue: Queue = Queue() + self.control_queue: Queue[Any] = Queue() def dispatch_control(self, msg): self.control_queue.put_nowait(msg) @@ -279,7 +279,7 @@ async def poll_control_queue(self): async def _flush_control_queue(self): """Flush the control queue, wait for processing of any pending messages""" - tracer_future: t.Union[concurrent.futures.Future, asyncio.Future] + tracer_future: t.Union[concurrent.futures.Future[object], asyncio.Future[object]] if self.control_thread: control_loop = self.control_thread.io_loop # concurrent.futures.Futures are threadsafe @@ -531,7 +531,7 @@ def schedule_dispatch(self, dispatch, *args): def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() - self.msg_queue: Queue = Queue() + self.msg_queue: Queue[Any] = Queue() self.io_loop.add_callback(self.dispatch_queue) self.control_stream.on_recv(self.dispatch_control, copy=False) diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index a18f0440b..139b833e4 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -73,7 +73,7 @@ def run(self): try: from _winapi import INFINITE, WAIT_OBJECT_0 # type:ignore[attr-defined] except ImportError: - from _subprocess import INFINITE, WAIT_OBJECT_0 # type:ignore[import] + from _subprocess import INFINITE, WAIT_OBJECT_0 # Build the list of handle to listen on. handles = [] diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 8f612e65a..70c9bede4 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -72,7 +72,7 @@ def use_dill(): adds support for object methods and closures to serialization. """ # import dill causes most of the magic - import dill # type:ignore[import] + import dill # dill doesn't work with cPickle, # tell the two relevant modules to use plain pickle @@ -85,7 +85,7 @@ def use_dill(): except ImportError: pass else: - serialize.pickle = dill + serialize.pickle = dill # type:ignore[attr-defined] # disable special function handling, let dill take care of it can_map.pop(FunctionType, None) @@ -96,7 +96,7 @@ def use_cloudpickle(): adds support for object methods and closures to serialization. """ - import cloudpickle # type:ignore[import] + import cloudpickle global pickle pickle = cloudpickle @@ -106,7 +106,7 @@ def use_cloudpickle(): except ImportError: pass else: - serialize.pickle = cloudpickle + serialize.pickle = cloudpickle # type:ignore[attr-defined] # disable special function handling, let cloudpickle take care of it can_map.pop(FunctionType, None) @@ -195,13 +195,13 @@ class CannedFunction(CannedObject): def __init__(self, f): self._check_type(f) self.code = f.__code__ - self.defaults: typing.Optional[list] + self.defaults: typing.Optional[list[typing.Any]] if f.__defaults__: self.defaults = [can(fd) for fd in f.__defaults__] else: self.defaults = None - self.closure: typing.Optional[tuple] + self.closure: typing.Any closure = f.__closure__ if closure: self.closure = tuple(can(cell) for cell in closure) @@ -262,7 +262,7 @@ def get_object(self, g=None): class CannedArray(CannedObject): def __init__(self, obj): - from numpy import ascontiguousarray # type:ignore[import] + from numpy import ascontiguousarray self.shape = obj.shape self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str @@ -461,7 +461,7 @@ def uncan_sequence(obj, g=None): if buffer is not memoryview: can_map[buffer] = CannedBuffer -uncan_map: dict[type, typing.Callable] = { +uncan_map: dict[type, typing.Any] = { CannedObject: lambda obj, g: obj.get_object(g), dict: uncan_dict, } diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index 1466c9d34..b1627cac5 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.backend_inline import * # type:ignore[import] # analysis: ignore # noqa F401 +from matplotlib_inline.backend_inline import * # analysis: ignore # noqa F401 warnings.warn( "`ipykernel.pylab.backend_inline` is deprecated, directly " diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index 8fbb53bae..7622f9e11 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.config import * # type:ignore[import] # analysis: ignore # noqa F401 +from matplotlib_inline.config import * # analysis: ignore # noqa F401 warnings.warn( "`ipykernel.pylab.config` is deprecated, directly use `matplotlib_inline.config`", diff --git a/pyproject.toml b/pyproject.toml index 3f20fd84a..a3e7541a6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,40 +31,22 @@ src = "ipykernel/_version.py" [tool.mypy] check_untyped_defs = true +disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_decorators = true +follow_imports = "normal" +ignore_missing_imports = true no_implicit_optional = true +no_implicit_reexport = true pretty = true show_error_context = true show_error_codes = true strict_equality = true +strict_optional = true warn_unused_configs = true -warn_unused_ignores = true warn_redundant_casts = true - -[[tool.mypy.overrides]] -module = [ - "_pydevd_bundle.*", - "appnope", - "debugpy.*", - "entrypoints", - "flaky", - "ipykernel.*", - "ipyparallel.*", - "IPython.*", - "jupyter_console.*", - "jupyter_core.*", - "nest_asyncio", - "psutil", - "PySide2", - "PyQt4", - "PyQt5", - "qtconsole.*", - "traitlets.*", - "trio", - "wx", -] -ignore_missing_imports = true +warn_return_any = true +warn_unused_ignores = true [tool.pytest.ini_options] addopts = "-raXs --durations 10 --color=yes --doctest-modules --ignore=ipykernel/pylab/backend_inline.py --ignore=ipykernel/pylab/config.py --ignore=ipykernel/gui/gtk3embed.py --ignore=ipykernel/gui/gtkembed.py --ignore=ipykernel/datapub.py --ignore=ipykernel/log.py --ignore=ipykernel/pickleutil.py --ignore=ipykernel/serialize.py --ignore=ipykernel/_eventloop_macos.py" diff --git a/setup.py b/setup.py index e28c54b78..d6f4d352a 100644 --- a/setup.py +++ b/setup.py @@ -8,8 +8,8 @@ import sys from glob import glob -from setuptools import setup # type:ignore[import] -from setuptools.command.bdist_egg import bdist_egg # type:ignore[import] +from setuptools import setup +from setuptools.command.bdist_egg import bdist_egg # the name of the package name = "ipykernel" From e81d6028d6804132a4ea997bba8d865481479540 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 10 Apr 2022 07:01:07 -0500 Subject: [PATCH 0816/1195] fix py37 syntax --- docs/conf.py | 8 ++++---- ipykernel/_version.py | 3 ++- ipykernel/connect.py | 4 ++-- ipykernel/debugger.py | 2 +- ipykernel/inprocess/channels.py | 3 ++- ipykernel/ipkernel.py | 4 ++-- ipykernel/kernelbase.py | 2 +- ipykernel/pickleutil.py | 4 ++-- setup.py | 3 ++- 9 files changed, 18 insertions(+), 15 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 33e1c2dba..2b23af52a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,7 @@ import os import shutil -from typing import Any +from typing import Dict, Any, List # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -62,7 +62,7 @@ # built documents. # -version_ns: dict[str, Any] = {} +version_ns: Dict[str, Any] = {} here = os.path.dirname(__file__) version_py = os.path.join(here, os.pardir, "ipykernel", "_version.py") with open(version_py) as f: @@ -151,7 +151,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path: list[str] = [] +html_static_path: List[str] = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -218,7 +218,7 @@ # -- Options for LaTeX output --------------------------------------------- -latex_elements: dict[str, object] = {} +latex_elements: Dict[str, object] = {} # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 32c639edc..7ea05f2a4 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -2,6 +2,7 @@ store the current version info of the server. """ import re +from typing import List # Version string must appear intact for tbump versioning __version__ = "6.12.1" @@ -10,7 +11,7 @@ pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" match = re.match(pattern, __version__) assert match is not None -parts: list[object] = [int(match[part]) for part in ["major", "minor", "patch"]] +parts: List[object] = [int(match[part]) for part in ["major", "minor", "patch"]] if match["rest"]: parts.append(match["rest"]) version_info = tuple(parts) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 39e00e25a..b9ee76e4b 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -6,7 +6,7 @@ import json import sys from subprocess import PIPE, Popen -from typing import Any +from typing import Any, Dict import jupyter_client from jupyter_client import write_connection_file @@ -108,7 +108,7 @@ def connect_qtconsole(connection_file=None, argv=None): cmd = ";".join(["from IPython.qt.console import qtconsoleapp", "qtconsoleapp.main()"]) - kwargs: dict[str, Any] = {} + kwargs: Dict[str, Any] = {} # Launch the Qt console in a separate session & process group, so # interrupting the kernel doesn't kill it. kwargs["start_new_session"] = True diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index e025de24e..92021352d 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -101,7 +101,7 @@ def _reset_tcp_pos(self): def _put_message(self, raw_msg): self.log.debug("QUEUE - _put_message:") - msg = t.cast(dict[str, t.Any], jsonapi.loads(raw_msg)) + msg = t.cast(t.Dict[str, t.Any], jsonapi.loads(raw_msg)) if msg["type"] == "event": self.log.debug("QUEUE - received event:") self.log.debug(msg) diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index a0dff04f7..93bf2a7e1 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -4,6 +4,7 @@ # Distributed under the terms of the Modified BSD License. from jupyter_client.channelsabc import HBChannelABC +from typing import List # ----------------------------------------------------------------------------- # Channel classes @@ -13,7 +14,7 @@ class InProcessChannel: """Base class for in-process channels.""" - proxy_methods: list[object] = [] + proxy_methods: List[object] = [] def __init__(self, client=None): super().__init__() diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 889782cc7..c4ef68d3d 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -311,7 +311,7 @@ async def do_execute( self._forward_input(allow_stdin) - reply_content: dict[str, t.Any] = {} + reply_content: t.Dict[str, t.Any] = {} if hasattr(shell, "run_cell_async") and hasattr(shell, "should_run_async"): run_cell = shell.run_cell_async should_run_async = shell.should_run_async @@ -507,7 +507,7 @@ def _experimental_do_complete(self, code, cursor_pos): def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): name = token_at_cursor(code, cursor_pos) - reply_content: dict[str, t.Any] = {"status": "ok"} + reply_content: t.Dict[str, t.Any] = {"status": "ok"} reply_content["data"] = {} reply_content["metadata"] = {} try: diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index d4530be64..74e0ba71e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -143,7 +143,7 @@ def _default_ident(self): # This should be overridden by wrapper kernels that implement any real # language. - language_info: dict[str, object] = {} + language_info: t.Dict[str, object] = {} # any links that should go in the help menu help_links = List() diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 70c9bede4..ed7d4a7b8 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -195,7 +195,7 @@ class CannedFunction(CannedObject): def __init__(self, f): self._check_type(f) self.code = f.__code__ - self.defaults: typing.Optional[list[typing.Any]] + self.defaults: typing.Optional[typing.List[typing.Any]] if f.__defaults__: self.defaults = [can(fd) for fd in f.__defaults__] else: @@ -461,7 +461,7 @@ def uncan_sequence(obj, g=None): if buffer is not memoryview: can_map[buffer] = CannedBuffer -uncan_map: dict[type, typing.Any] = { +uncan_map: typing.Dict[type, typing.Any] = { CannedObject: lambda obj, g: obj.get_object(g), dict: uncan_dict, } diff --git a/setup.py b/setup.py index d6f4d352a..9bb5e05ce 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ import shutil import sys from glob import glob +from typing import Dict from setuptools import setup from setuptools.command.bdist_egg import bdist_egg @@ -42,7 +43,7 @@ def run(self): with open(pjoin(here, "README.md")) as fid: LONG_DESCRIPTION = fid.read() -setup_args: dict[str, object] = dict( +setup_args: Dict[str, object] = dict( name=name, cmdclass={ "bdist_egg": bdist_egg if "bdist_egg" in sys.argv else bdist_egg_disabled, From 99d97213757bc969b4a2fd9875abc7ad8815844e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 10 Apr 2022 07:01:24 -0500 Subject: [PATCH 0817/1195] fix py37 syntax --- docs/conf.py | 2 +- ipykernel/inprocess/channels.py | 3 ++- ipykernel/iostream.py | 2 +- ipykernel/kernelapp.py | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 2b23af52a..21c16dda6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,7 +14,7 @@ import os import shutil -from typing import Dict, Any, List +from typing import Any, Dict, List # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 93bf2a7e1..84629ff5d 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -3,9 +3,10 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from jupyter_client.channelsabc import HBChannelABC from typing import List +from jupyter_client.channelsabc import HBChannelABC + # ----------------------------------------------------------------------------- # Channel classes # ----------------------------------------------------------------------------- diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 6255d717e..3d66a7239 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -13,7 +13,7 @@ from binascii import b2a_hex from collections import deque from io import StringIO, TextIOBase -from typing import Any, Optional, Callable +from typing import Any, Callable, Optional from weakref import WeakSet import zmq diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 72043d6a0..944b01bc2 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -465,7 +465,8 @@ def init_io(self): handler.stream = TextIOWrapper( FileIO( - sys.stderr._original_stdstream_copy, "w" # type:ignore[attr-defined] + sys.stderr._original_stdstream_copy, + "w", # type:ignore[attr-defined] ) ) if self.displayhook_class: From 71b926551f6bc725524a1160dad3411ec020ae39 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 10 Apr 2022 07:20:32 -0500 Subject: [PATCH 0818/1195] pre-commit --- ipykernel/debugger.py | 6 +++--- ipykernel/kernelapp.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 92021352d..841966a72 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -358,9 +358,9 @@ async def handle_stopped_event(self): event = await self.stopped_queue.get() req = {"seq": event["seq"] + 1, "type": "request", "command": "threads"} rep = await self._forward_message(req) - for t in rep["body"]["threads"]: - if self._accept_stopped_thread(t["name"]): - self.stopped_threads.add(t["id"]) + for thread in rep["body"]["threads"]: + if self._accept_stopped_thread(thread["name"]): + self.stopped_threads.add(thread["id"]) self.event_callback(event) @property diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 944b01bc2..1f5804871 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -465,8 +465,8 @@ def init_io(self): handler.stream = TextIOWrapper( FileIO( - sys.stderr._original_stdstream_copy, - "w", # type:ignore[attr-defined] + sys.stderr._original_stdstream_copy, # type:ignore[attr-defined] + "w", ) ) if self.displayhook_class: From d01ac28349397731c58c9eb7b173e13d11f875c1 Mon Sep 17 00:00:00 2001 From: andreas Date: Mon, 11 Apr 2022 10:47:03 +0200 Subject: [PATCH 0819/1195] fixes module IPython.qt not found error on spawning qtconsole in notebook. Fixes #914 --- ipykernel/connect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index b9ee76e4b..6f1287708 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -106,7 +106,7 @@ def connect_qtconsole(connection_file=None, argv=None): cf = _find_connection_file(connection_file) - cmd = ";".join(["from IPython.qt.console import qtconsoleapp", "qtconsoleapp.main()"]) + cmd = ";".join(["from qtconsole import qtconsoleapp", "qtconsoleapp.main()"]) kwargs: Dict[str, Any] = {} # Launch the Qt console in a separate session & process group, so From d6f9971e1798c342c1964f487df54eaf39eb45dc Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 09:07:59 +0000 Subject: [PATCH 0820/1195] Automated Changelog Entry for 6.13.0 on main --- CHANGELOG.md | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7df0273b6..1752da2f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ +## 6.13.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.12.1...05c6e655e497a944fd738d9b744fad90bc78b70a)) + +### Enhancements made + +- Add the PID to the resource usage reply [#908](https://github.com/ipython/ipykernel/pull/908) ([@echarles](https://github.com/echarles)) + +### Bugs fixed + +- Fix qtconsole spawn [#915](https://github.com/ipython/ipykernel/pull/915) ([@andia89](https://github.com/andia89)) + +### Maintenance and upkeep improvements + +- Add basic mypy support [#913](https://github.com/ipython/ipykernel/pull/913) ([@blink1073](https://github.com/blink1073)) +- Clean up pre-commit [#911](https://github.com/ipython/ipykernel/pull/911) ([@blink1073](https://github.com/blink1073)) +- Update setup.py [#909](https://github.com/ipython/ipykernel/pull/909) ([@tlinhart](https://github.com/tlinhart)) +- [pre-commit.ci] pre-commit autoupdate [#906](https://github.com/ipython/ipykernel/pull/906) ([@pre-commit-ci](https://github.com/pre-commit-ci)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-04-04&to=2022-04-11&type=c)) + +[@andia89](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aandia89+updated%3A2022-04-04..2022-04-11&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-04-04..2022-04-11&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-04-04..2022-04-11&type=Issues) | [@meeseeksdev](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ameeseeksdev+updated%3A2022-04-04..2022-04-11&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-04-04..2022-04-11&type=Issues) | [@tlinhart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Atlinhart+updated%3A2022-04-04..2022-04-11&type=Issues) + + + ## 6.12.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.12.0...3a04ea3fa50d01bcc09f10e3de8bb5570c2cd619)) @@ -16,8 +43,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-04-04..2022-04-04&type=Issues) - - ## 6.12.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.11.0...70073edbdae17be396093be96bf880da069e7e52)) From c5183c0eaa7873a9478d84620f22aae01765c2bf Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 09:20:55 +0000 Subject: [PATCH 0821/1195] Publish 6.13.0 SHA256 hashes: ipykernel-6.13.0-py3-none-any.whl: 2b0987af43c0d4b62cecb13c592755f599f96f29aafe36c01731aaa96df30d39 ipykernel-6.13.0.tar.gz: 0e28273e290858393e86e152b104e5506a79c13d25b951ac6eca220051b4be60 --- ipykernel/_version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7ea05f2a4..0bcd7a470 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for tbump versioning -__version__ = "6.12.1" +__version__ = "6.13.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/pyproject.toml b/pyproject.toml index a3e7541a6..4da63a645 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ ignore = [] skip = ["check-links"] [tool.tbump.version] -current = "6.12.1" +current = "6.13.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 4d522429a0091fc6c52065072eb5e016a47e93fe Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Apr 2022 21:32:42 +0000 Subject: [PATCH 0822/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.1.0 → v4.2.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.1.0...v4.2.0) - [github.com/pre-commit/mirrors-eslint: v8.12.0 → v8.13.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.12.0...v8.13.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41a7de4c2..8300c45d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.2.0 hooks: - id: end-of-file-fixer - id: check-case-conflict @@ -63,7 +63,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.12.0 + rev: v8.13.0 hooks: - id: eslint stages: [manual] From 6312bce47fce481591646ebdfecd53326c41c790 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Apr 2022 20:17:58 +0000 Subject: [PATCH 0823/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/sirosen/check-jsonschema: 0.14.2 → 0.14.3](https://github.com/sirosen/check-jsonschema/compare/0.14.2...0.14.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8300c45d8..d0cc1e96b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -69,7 +69,7 @@ repos: stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.14.2 + rev: 0.14.3 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From a3f7a292bfdc31a542a7ce8028c73c6bdeaa2900 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Apr 2022 20:18:31 +0000 Subject: [PATCH 0824/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-eslint: v8.13.0 → v8.14.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.13.0...v8.14.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d0cc1e96b..3682b08a4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -63,7 +63,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.13.0 + rev: v8.14.0 hooks: - id: eslint stages: [manual] From b8499a59019fa07f790157de6ce9303c21670110 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 26 Apr 2022 08:05:24 -0500 Subject: [PATCH 0825/1195] Allow enforce PR label workflow to add labels --- .github/workflows/enforce-label.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/enforce-label.yml b/.github/workflows/enforce-label.yml index 8296e75d4..75432d5f8 100644 --- a/.github/workflows/enforce-label.yml +++ b/.github/workflows/enforce-label.yml @@ -10,6 +10,8 @@ on: jobs: enforce-label: runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - name: enforce-triage-label uses: jupyterlab/maintainer-tools/.github/actions/enforce-label@v1 From bba10e9258433fdc372d9174a9b787525712e0cc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 May 2022 20:49:51 +0000 Subject: [PATCH 0826/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v0.942 → v0.950](https://github.com/pre-commit/mirrors-mypy/compare/v0.942...v0.950) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3682b08a4..ff556c28f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.942 + rev: v0.950 hooks: - id: mypy exclude: "ipykernel.*tests" From c80a8543a0c271fe8f86e381947543b48dbaae92 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 21:01:00 +0000 Subject: [PATCH 0827/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-eslint: v8.14.0 → v8.15.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.14.0...v8.15.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff556c28f..351529941 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -63,7 +63,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.14.0 + rev: v8.15.0 hooks: - id: eslint stages: [manual] From 76e3bd6e24e0e3bc4f95df47ca87feb6f8881905 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 13 May 2022 06:57:30 -0500 Subject: [PATCH 0828/1195] Switch to hatch backend --- .flake8 | 17 ++++++++++ hatch_build.py | 26 +++++++++++++++ ipykernel/kernelspec.py | 5 ++- pyproject.toml | 66 +++++++++++++++++++++++++++++++------ setup.cfg | 63 ++++++++++++++++++++++++++---------- setup.py | 72 ++++++++++++++--------------------------- 6 files changed, 173 insertions(+), 76 deletions(-) create mode 100644 .flake8 create mode 100644 hatch_build.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 000000000..6792947f9 --- /dev/null +++ b/.flake8 @@ -0,0 +1,17 @@ +[flake8] +ignore = E501, W503, E402 +builtins = c, get_config +exclude = + .cache, + .github, + docs, + setup.py +enable-extensions = G +extend-ignore = + G001, G002, G004, G200, G201, G202, + # black adds spaces around ':' + E203, +per-file-ignores = + # B011: Do not call assert False since python -O removes these calls + # F841 local variable 'foo' is assigned to but never used + ipykernel/tests/*: B011, F841 diff --git a/hatch_build.py b/hatch_build.py new file mode 100644 index 000000000..31be2ff1e --- /dev/null +++ b/hatch_build.py @@ -0,0 +1,26 @@ +import os +import shutil +import sys + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + + +class CustomHook(BuildHookInterface): + def initialize(self, version, build_data): + + here = os.path.abspath(os.path.dirname(__file__)) + sys.path.insert(0, here) + from ipykernel.kernelspec import make_ipkernel_cmd, write_kernel_spec + + # When building a standard wheel, the executable specified in the kernelspec is simply 'python'. + if version == "standard": + argv = make_ipkernel_cmd(executable="python") + + # When installing an editable wheel, the full `sys.executable` can be used. + else: + argv = make_ipkernel_cmd() + + dest = os.path.join(here, "data_kernelspec") + if os.path.exists(dest): + shutil.rmtree(dest) + write_kernel_spec(dest, overrides={"argv": argv}) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 585d87127..ac2594d78 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -13,7 +13,10 @@ from jupyter_client.kernelspec import KernelSpecManager -from .debugger import _is_debugpy_available +try: + from .debugger import _is_debugpy_available +except ImportError: + _is_debugpy_available = False pjoin = os.path.join diff --git a/pyproject.toml b/pyproject.toml index 4da63a645..ed9630e77 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,62 @@ [build-system] -build-backend = "setuptools.build_meta" -requires=[ - "setuptools", - "wheel", - "debugpy", - "ipython>=5", - "jupyter_core>=4.2", - "jupyter_client", +requires = ["hatchling", "jupyter_client"] +build-backend = "hatchling.build" + +[project] +name = "ipykernel" +authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] +license = {text = "BSD"} +description = "IPython Kernel for Jupyter" +keywords = ["Interactive", "Interpreter", "Shell", "Web"] +classifiers = [ + "Intended Audience :: Developers", + "Intended Audience :: System Administrators", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: BSD License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", +] +urls = {Homepage = "https://ipython.org"} +requires-python = ">=3.7" +dependencies = [ + "debugpy>=1.0", + "ipython>=7.23.1", + "traitlets>=5.1.0", + "jupyter_client>=6.1.12", + "tornado>=6.1", + "matplotlib-inline>=0.1", + 'appnope;platform_system=="Darwin"', + "psutil", + "nest_asyncio", + "packaging", +] +dynamic = ["version"] + +[project.optional-dependencies] +test = [ + "pytest>=6.0", + "pytest-cov", + "flaky", + "ipyparallel", + "pre-commit", + "pytest-timeout", ] -[tool.check-manifest] -ignore = [] +# Used to call hatch_build.py +[tool.hatch.build.targets.wheel.hooks.custom] + +[tool.hatch.build.targets.wheel.shared-data] +"data_kernelspec" = "share/jupyter/kernels/python3" + +[tool.hatch.build] +include = ["ipykernel/**", "ipykernel_launcher.py"] + +[tool.hatch.version] +path = "ipykernel/_version.py" [tool.jupyter-releaser] skip = ["check-links"] diff --git a/setup.cfg b/setup.cfg index 2ee9451f5..f91a2d114 100644 --- a/setup.cfg +++ b/setup.cfg @@ -5,21 +5,50 @@ universal=0 [metadata] license_file = COPYING.md version = attr: ipykernel._version.__version__ +name = ipykernel +author = IPython Development Team +author_email = ipython-dev@scipy.org +license = BSD +description = IPython Kernel for Jupyter +keywords = Interactive, Interpreter, Shell, Web +url = https://ipython.org +long_description_content_type = text/markdown +classifiers = + Intended Audience :: Developers + Intended Audience :: System Administrators + Intended Audience :: Science/Research + License :: OSI Approved :: BSD License + Programming Language :: Python + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 +platforms = Linux,, Mac, OS, X,, Windows -[flake8] -ignore = E501, W503, E402 -builtins = c, get_config -exclude = - .cache, - .github, - docs, - setup.py -enable-extensions = G -extend-ignore = - G001, G002, G004, G200, G201, G202, - # black adds spaces around ':' - E203, -per-file-ignores = - # B011: Do not call assert False since python -O removes these calls - # F841 local variable 'foo' is assigned to but never used - ipykernel/tests/*: B011, F841 +[options] +py_modules = ipykernel_launcher +install_requires = + debugpy>=1.0 + ipython>=7.23.1 + traitlets>=5.1.0 + jupyter_client>=6.1.12 + tornado>=6.1 + matplotlib-inline>=0.1 + appnope;platform_system=="Darwin" + psutil + nest_asyncio + packaging +python_requires = >=3.7 +scripts = + +[options.extras_require] +test = + pytest>=6.0 + pytest-cov + flaky + ipyparallel + pre-commit + pytest-timeout + +[options.package_data] diff --git a/setup.py b/setup.py index 9bb5e05ce..4740bb89b 100644 --- a/setup.py +++ b/setup.py @@ -10,46 +10,22 @@ from typing import Dict from setuptools import setup -from setuptools.command.bdist_egg import bdist_egg # the name of the package name = "ipykernel" -class bdist_egg_disabled(bdist_egg): - """Disabled version of bdist_egg - - Prevents setup.py install from performing setuptools' default easy_install, - which it should never ever do. - """ - - def run(self): - sys.exit("Aborting implicit building of eggs. Use `pip install .` to install from source.") - - -pjoin = os.path.join -here = os.path.abspath(os.path.dirname(__file__)) -pkg_root = pjoin(here, name) - -packages = [] -for d, _, _ in os.walk(pjoin(here, name)): - if os.path.exists(pjoin(d, "__init__.py")): - packages.append(d[len(here) + 1 :].replace(os.path.sep, ".")) +from os.path import join as pjoin package_data = { "ipykernel": ["resources/*.*", "py.typed"], } -with open(pjoin(here, "README.md")) as fid: - LONG_DESCRIPTION = fid.read() setup_args: Dict[str, object] = dict( name=name, - cmdclass={ - "bdist_egg": bdist_egg if "bdist_egg" in sys.argv else bdist_egg_disabled, - }, scripts=glob(pjoin("scripts", "*")), - packages=packages, + # packages=packages, py_modules=["ipykernel_launcher"], package_data=package_data, description="IPython Kernel for Jupyter", @@ -58,7 +34,7 @@ def run(self): author_email="ipython-dev@scipy.org", url="https://ipython.org", license="BSD", - long_description=LONG_DESCRIPTION, + # long_description=LONG_DESCRIPTION, platforms="Linux, Mac OS X, Windows", keywords=["Interactive", "Interpreter", "Shell", "Web"], project_urls={ @@ -105,28 +81,28 @@ def run(self): ) -if any(a.startswith(("bdist", "install")) for a in sys.argv): - sys.path.insert(0, here) - from ipykernel.kernelspec import KERNEL_NAME, make_ipkernel_cmd, write_kernel_spec +# if any(a.startswith(("bdist", "install")) for a in sys.argv): +# sys.path.insert(0, here) +# from ipykernel.kernelspec import KERNEL_NAME, make_ipkernel_cmd, write_kernel_spec - # When building a wheel, the executable specified in the kernelspec is simply 'python'. - if any(a.startswith("bdist") for a in sys.argv): - argv = make_ipkernel_cmd(executable="python") - # When installing from source, the full `sys.executable` can be used. - if any(a.startswith("install") for a in sys.argv): - argv = make_ipkernel_cmd() - dest = os.path.join(here, "data_kernelspec") - if os.path.exists(dest): - shutil.rmtree(dest) - write_kernel_spec(dest, overrides={"argv": argv}) +# # When building a wheel, the executable specified in the kernelspec is simply 'python'. +# if any(a.startswith("bdist") for a in sys.argv): +# argv = make_ipkernel_cmd(executable="python") +# # When installing from source, the full `sys.executable` can be used. +# if any(a.startswith("install") for a in sys.argv): +# argv = make_ipkernel_cmd() +# dest = os.path.join(here, "data_kernelspec") +# if os.path.exists(dest): +# shutil.rmtree(dest) +# write_kernel_spec(dest, overrides={"argv": argv}) - setup_args["data_files"] = [ - ( - pjoin("share", "jupyter", "kernels", KERNEL_NAME), - glob(pjoin("data_kernelspec", "*")), - ) - ] +# setup_args["data_files"] = [ +# ( +# pjoin("share", "jupyter", "kernels", KERNEL_NAME), +# glob(pjoin("data_kernelspec", "*")), +# ) +# ] -if __name__ == "__main__": - setup(**setup_args) +# if __name__ == "__main__": +setup(**setup_args) From edff326dbb9693002221f2832c527fee786eebe8 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 13 May 2022 06:58:38 -0500 Subject: [PATCH 0829/1195] Remove setuptools files --- setup.cfg | 54 --------------------------- setup.py | 108 ------------------------------------------------------ 2 files changed, 162 deletions(-) delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index f91a2d114..000000000 --- a/setup.cfg +++ /dev/null @@ -1,54 +0,0 @@ - -[bdist_wheel] -universal=0 - -[metadata] -license_file = COPYING.md -version = attr: ipykernel._version.__version__ -name = ipykernel -author = IPython Development Team -author_email = ipython-dev@scipy.org -license = BSD -description = IPython Kernel for Jupyter -keywords = Interactive, Interpreter, Shell, Web -url = https://ipython.org -long_description_content_type = text/markdown -classifiers = - Intended Audience :: Developers - Intended Audience :: System Administrators - Intended Audience :: Science/Research - License :: OSI Approved :: BSD License - Programming Language :: Python - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 -platforms = Linux,, Mac, OS, X,, Windows - -[options] -py_modules = ipykernel_launcher -install_requires = - debugpy>=1.0 - ipython>=7.23.1 - traitlets>=5.1.0 - jupyter_client>=6.1.12 - tornado>=6.1 - matplotlib-inline>=0.1 - appnope;platform_system=="Darwin" - psutil - nest_asyncio - packaging -python_requires = >=3.7 -scripts = - -[options.extras_require] -test = - pytest>=6.0 - pytest-cov - flaky - ipyparallel - pre-commit - pytest-timeout - -[options.package_data] diff --git a/setup.py b/setup.py deleted file mode 100644 index 4740bb89b..000000000 --- a/setup.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import os -import shutil -import sys -from glob import glob -from typing import Dict - -from setuptools import setup - -# the name of the package -name = "ipykernel" - - -from os.path import join as pjoin - -package_data = { - "ipykernel": ["resources/*.*", "py.typed"], -} - - -setup_args: Dict[str, object] = dict( - name=name, - scripts=glob(pjoin("scripts", "*")), - # packages=packages, - py_modules=["ipykernel_launcher"], - package_data=package_data, - description="IPython Kernel for Jupyter", - long_description_content_type="text/markdown", - author="IPython Development Team", - author_email="ipython-dev@scipy.org", - url="https://ipython.org", - license="BSD", - # long_description=LONG_DESCRIPTION, - platforms="Linux, Mac OS X, Windows", - keywords=["Interactive", "Interpreter", "Shell", "Web"], - project_urls={ - "Documentation": "https://ipython.readthedocs.io/", - "Funding": "https://numfocus.org/", - "Source": "https://github.com/ipython/ipykernel", - "Tracker": "https://github.com/ipython/ipykernel/issues", - }, - python_requires=">=3.7", - install_requires=[ - "debugpy>=1.0", - "ipython>=7.23.1", - "traitlets>=5.1.0", - "jupyter_client>=6.1.12", - "tornado>=6.1", - "matplotlib-inline>=0.1", - 'appnope;platform_system=="Darwin"', - "psutil", - "nest_asyncio", - "packaging", - ], - extras_require={ - "test": [ - "pytest>=6.0", - "pytest-cov", - "flaky", - "ipyparallel", - "pre-commit", - "pytest-timeout", - ], - }, - classifiers=[ - "Intended Audience :: Developers", - "Intended Audience :: System Administrators", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: BSD License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - ], -) - - -# if any(a.startswith(("bdist", "install")) for a in sys.argv): -# sys.path.insert(0, here) -# from ipykernel.kernelspec import KERNEL_NAME, make_ipkernel_cmd, write_kernel_spec - -# # When building a wheel, the executable specified in the kernelspec is simply 'python'. -# if any(a.startswith("bdist") for a in sys.argv): -# argv = make_ipkernel_cmd(executable="python") -# # When installing from source, the full `sys.executable` can be used. -# if any(a.startswith("install") for a in sys.argv): -# argv = make_ipkernel_cmd() -# dest = os.path.join(here, "data_kernelspec") -# if os.path.exists(dest): -# shutil.rmtree(dest) -# write_kernel_spec(dest, overrides={"argv": argv}) - -# setup_args["data_files"] = [ -# ( -# pjoin("share", "jupyter", "kernels", KERNEL_NAME), -# glob(pjoin("data_kernelspec", "*")), -# ) -# ] - - -# if __name__ == "__main__": -setup(**setup_args) From ecb21e304d228ac719091d866851cb3df85f8dde Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 14 May 2022 19:45:49 -0500 Subject: [PATCH 0830/1195] add sdist handling --- hatch_build.py | 1 - pyproject.toml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/hatch_build.py b/hatch_build.py index 31be2ff1e..fca67e502 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -7,7 +7,6 @@ class CustomHook(BuildHookInterface): def initialize(self, version, build_data): - here = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, here) from ipykernel.kernelspec import make_ipkernel_cmd, write_kernel_spec diff --git a/pyproject.toml b/pyproject.toml index ed9630e77..a4ab188b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,7 @@ test = [ # Used to call hatch_build.py [tool.hatch.build.targets.wheel.hooks.custom] +[tool.hatch.build.targets.sdist.hooks.custom] [tool.hatch.build.targets.wheel.shared-data] "data_kernelspec" = "share/jupyter/kernels/python3" From 56f7d551c28e4a5255dc18dcbad6d1e4cdda7300 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 14 May 2022 22:29:32 -0500 Subject: [PATCH 0831/1195] cleanup --- pyproject.toml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a4ab188b7..f34a60402 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,14 +47,13 @@ test = [ ] # Used to call hatch_build.py -[tool.hatch.build.targets.wheel.hooks.custom] -[tool.hatch.build.targets.sdist.hooks.custom] +[tool.hatch.build.hooks.custom] [tool.hatch.build.targets.wheel.shared-data] "data_kernelspec" = "share/jupyter/kernels/python3" -[tool.hatch.build] -include = ["ipykernel/**", "ipykernel_launcher.py"] +[tool.hatch.build.force-include] +"./ipykernel_launcher.py" = "ipykernel_launcher.py" [tool.hatch.version] path = "ipykernel/_version.py" From 0e57c3e7d9c02ede2d079a80519eb2f64a73c7e2 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 16 May 2022 05:49:06 -0500 Subject: [PATCH 0832/1195] cleanup --- pyproject.toml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f34a60402..5b76a7c7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,13 @@ [build-system] -requires = ["hatchling", "jupyter_client"] +requires = ["hatchling>=0.25", "jupyter_client>=6"] build-backend = "hatchling.build" [project] name = "ipykernel" +version = "6.13.0" authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] -license = {text = "BSD"} +license = {file = "COPYING.md"} +readme = "README.md" description = "IPython Kernel for Jupyter" keywords = ["Interactive", "Interpreter", "Shell", "Web"] classifiers = [ @@ -34,7 +36,6 @@ dependencies = [ "nest_asyncio", "packaging", ] -dynamic = ["version"] [project.optional-dependencies] test = [ @@ -52,11 +53,8 @@ test = [ [tool.hatch.build.targets.wheel.shared-data] "data_kernelspec" = "share/jupyter/kernels/python3" -[tool.hatch.build.force-include] -"./ipykernel_launcher.py" = "ipykernel_launcher.py" - -[tool.hatch.version] -path = "ipykernel/_version.py" +[tool.hatch.build] +artifacts = ["ipykernel_launcher.py"] [tool.jupyter-releaser] skip = ["check-links"] @@ -75,6 +73,9 @@ tag_template = "v{new_version}" [[tool.tbump.file]] src = "ipykernel/_version.py" +[[tool.tbump.file]] +src = "pyproject.toml" + [tool.mypy] check_untyped_defs = true disallow_any_generics = true From 56597805ff370f9bd517fa7cc4589ca96b4d86fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 16 May 2022 22:09:39 +0000 Subject: [PATCH 0833/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/sirosen/check-jsonschema: 0.14.3 → 0.15.0](https://github.com/sirosen/check-jsonschema/compare/0.14.3...0.15.0) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 351529941..5e2e5fae5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -69,7 +69,7 @@ repos: stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.14.3 + rev: 0.15.0 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From dbdd3a85fef857eca23ff2bc1a6c802d9827ff66 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 17 May 2022 20:44:07 -0500 Subject: [PATCH 0834/1195] Clean up types --- .pre-commit-config.yaml | 3 ++- ipykernel/inprocess/ipkernel.py | 11 +++++++---- ipykernel/kernelapp.py | 2 +- ipykernel/kernelbase.py | 16 ++++++++++------ ipykernel/kernelspec.py | 3 ++- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5e2e5fae5..33dee0d65 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -47,7 +47,8 @@ repos: - id: mypy exclude: "ipykernel.*tests" args: ["--config-file", "pyproject.toml"] - additional_dependencies: [tornado, jupyter_client, pytest] + additional_dependencies: + [tornado, jupyter_client, pytest, traitlets, jupyter_core] stages: [manual] - repo: https://github.com/pycqa/flake8 diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index d2b2e8f32..aaaa5099f 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -48,7 +48,7 @@ class InProcessKernel(IPythonKernel): shell_class = Type(allow_none=True) _underlying_iopub_socket = Instance(DummySocket, ()) - iopub_thread = Instance(IOPubThread) + iopub_thread: IOPubThread = Instance(IOPubThread) # type:ignore[assignment] shell_stream = Instance(DummySocket, ()) @@ -58,13 +58,13 @@ def _default_iopub_thread(self): thread.start() return thread - iopub_socket = Instance(BackgroundSocket) + iopub_socket: BackgroundSocket = Instance(BackgroundSocket) # type:ignore[assignment] @default("iopub_socket") def _default_iopub_socket(self): return self.iopub_thread.background_socket - stdin_socket = Instance(DummySocket, ()) + stdin_socket = Instance(DummySocket, ()) # type:ignore[assignment] def __init__(self, **traits): super().__init__(**traits) @@ -127,6 +127,7 @@ def _redirected_io(self): def _io_dispatch(self, change): """Called when a message is sent to the IO socket.""" + assert self.iopub_socket.io_thread is not None ident, msg = self.session.recv(self.iopub_socket.io_thread.socket, copy=False) for frontend in self.frontends: frontend.iopub_channel.call_handlers(msg) @@ -163,7 +164,9 @@ def _default_stderr(self): class InProcessInteractiveShell(ZMQInteractiveShell): - kernel = Instance("ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True) + kernel: InProcessKernel = Instance( + "ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True + ) # type:ignore[assignment] # ------------------------------------------------------------------------- # InteractiveShell interface diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 1f5804871..8e6084671 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -28,7 +28,7 @@ from jupyter_client.session import Session, session_aliases, session_flags from jupyter_core.paths import jupyter_runtime_dir from tornado import ioloop -from traitlets import ( +from traitlets.traitlets import ( Any, Bool, Dict, diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 74e0ba71e..617729cf8 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -38,7 +38,8 @@ from jupyter_client.session import Session from tornado import ioloop from tornado.queues import Queue, QueueEmpty -from traitlets import ( +from traitlets.config.configurable import SingletonConfigurable +from traitlets.traitlets import ( Any, Bool, Dict, @@ -51,7 +52,6 @@ default, observe, ) -from traitlets.config.configurable import SingletonConfigurable from zmq.eventloop.zmqstream import ZMQStream from ipykernel.jsonutil import json_clean @@ -95,6 +95,10 @@ def _update_eventloop(self, change): """ ) + implementation: str + implementation_version: str + banner: str + @default("shell_streams") def _shell_streams_default(self): warnings.warn( @@ -131,7 +135,7 @@ def _shell_streams_changed(self, change): iopub_socket = Any() iopub_thread = Any() stdin_socket = Any() - log = Instance(logging.Logger, allow_none=True) + log: logging.Logger = Instance(logging.Logger, allow_none=True) # type:ignore[assignment] # identities: int_id = Integer(-1) @@ -263,7 +267,7 @@ def __init__(self, **kwargs): for msg_type in self.control_msg_types: self.control_handlers[msg_type] = getattr(self, msg_type) - self.control_queue: Queue[Any] = Queue() + self.control_queue: Queue[t.Any] = Queue() def dispatch_control(self, msg): self.control_queue.put_nowait(msg) @@ -531,7 +535,7 @@ def schedule_dispatch(self, dispatch, *args): def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() - self.msg_queue: Queue[Any] = Queue() + self.msg_queue: Queue[t.Any] = Queue() self.io_loop.add_callback(self.dispatch_queue) self.control_stream.on_recv(self.dispatch_control, copy=False) @@ -866,7 +870,7 @@ async def comm_info_request(self, stream, ident, parent): if hasattr(self, "comm_manager"): comms = { k: dict(target_name=v.target_name) - for (k, v) in self.comm_manager.comms.items() + for (k, v) in self.comm_manager.comms.items() # type:ignore[attr-defined] if v.target_name == target_name or target_name is None } else: diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 585d87127..ac8552afe 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -12,6 +12,7 @@ import tempfile from jupyter_client.kernelspec import KernelSpecManager +from traitlets import Unicode from .debugger import _is_debugpy_available @@ -161,7 +162,7 @@ def install( class InstallIPythonKernelSpecApp(Application): """Dummy app wrapping argparse""" - name = "ipython-kernel-install" + name = Unicode("ipython-kernel-install") def initialize(self, argv=None): if argv is None: From 8e7df3eb2e68465f5569e503ce4888e735b0ada2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 23 May 2022 20:37:56 +0000 Subject: [PATCH 0835/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/PyCQA/doc8: 0.11.1 → 0.11.2](https://github.com/PyCQA/doc8/compare/0.11.1...0.11.2) - [github.com/pre-commit/mirrors-eslint: v8.15.0 → v8.16.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.15.0...v8.16.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 33dee0d65..1840dfcad 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,7 +35,7 @@ repos: - id: prettier - repo: https://github.com/PyCQA/doc8 - rev: 0.11.1 + rev: 0.11.2 hooks: - id: doc8 args: [--max-line-length=200] @@ -64,7 +64,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.15.0 + rev: v8.16.0 hooks: - id: eslint stages: [manual] From 944bfd9dfa4aad904566d99203229dff9bf04877 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 23 May 2022 15:54:05 -0500 Subject: [PATCH 0836/1195] update types --- ipykernel/inprocess/socket.py | 2 +- ipykernel/iostream.py | 4 ++-- ipykernel/kernelbase.py | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index ff826ea0c..477c36a47 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -31,7 +31,7 @@ def recv_multipart(self, flags=0, copy=True, track=False): return self.queue.get_nowait() def send_multipart(self, msg_parts, flags=0, copy=True, track=False): - msg_parts = list(map(zmq.Message, msg_parts)) # type:ignore[arg-type] + msg_parts = list(map(zmq.Message, msg_parts)) self.queue.put_nowait(msg_parts) self.message_sent += 1 diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 3d66a7239..419d092c3 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -13,7 +13,7 @@ from binascii import b2a_hex from collections import deque from io import StringIO, TextIOBase -from typing import Any, Callable, Optional +from typing import Any, Callable, Deque, Optional from weakref import WeakSet import zmq @@ -67,7 +67,7 @@ def __init__(self, socket, pipe=False): if pipe: self._setup_pipe_in() self._local = threading.local() - self._events: deque[Callable[..., Any]] = deque() + self._events: Deque[Callable[..., Any]] = deque() self._event_pipes: WeakSet[Any] = WeakSet() self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main, name="IOPub") diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 617729cf8..482688bed 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1197,9 +1197,7 @@ def _input_request(self, prompt, ident, parent, password=False): # zmq.select() is also uninterruptible, but at least this # way reads get noticed immediately and KeyboardInterrupts # get noticed fairly quickly by human response time standards. - rlist, _, xlist = zmq.select( - [self.stdin_socket], [], [self.stdin_socket], 0.01 # type:ignore[arg-type] - ) + rlist, _, xlist = zmq.select([self.stdin_socket], [], [self.stdin_socket], 0.01) if rlist or xlist: ident, reply = self.session.recv(self.stdin_socket) if (ident, reply) != (None, None): From 64d63a0c47467278d07de9e27a5abd8cf4cd914e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 25 May 2022 14:24:34 -0500 Subject: [PATCH 0837/1195] force debugger:true in built wheel --- hatch_build.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/hatch_build.py b/hatch_build.py index fca67e502..deb21ff47 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -11,15 +11,21 @@ def initialize(self, version, build_data): sys.path.insert(0, here) from ipykernel.kernelspec import make_ipkernel_cmd, write_kernel_spec + overrides = {} + # When building a standard wheel, the executable specified in the kernelspec is simply 'python'. if version == "standard": + overrides["metadata"] = dict(debugger=True) argv = make_ipkernel_cmd(executable="python") # When installing an editable wheel, the full `sys.executable` can be used. else: argv = make_ipkernel_cmd() + overrides["argv"] = argv + dest = os.path.join(here, "data_kernelspec") if os.path.exists(dest): shutil.rmtree(dest) - write_kernel_spec(dest, overrides={"argv": argv}) + + write_kernel_spec(dest, overrides=overrides) From 60ac65c71249738eb11acff060746c05833f6fcc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 30 May 2022 21:03:08 +0000 Subject: [PATCH 0838/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-mypy: v0.950 → v0.960](https://github.com/pre-commit/mirrors-mypy/compare/v0.950...v0.960) - [github.com/sirosen/check-jsonschema: 0.15.0 → 0.15.1](https://github.com/sirosen/check-jsonschema/compare/0.15.0...0.15.1) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1840dfcad..b57ca4ffb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.950 + rev: v0.960 hooks: - id: mypy exclude: "ipykernel.*tests" @@ -70,7 +70,7 @@ repos: stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.15.0 + rev: 0.15.1 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From 16a603c81b6ad9d6095de246ee177609f5026123 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Mon, 30 May 2022 12:07:00 +0200 Subject: [PATCH 0839/1195] Fix richInspectVariables --- ipykernel/debugger.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 841966a72..11ccfb68c 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -26,7 +26,10 @@ SuspendedFramesManager, _FramesTracker, ) + from _pydevd_bundle.pydevd_safe_repr import SafeRepr # isort: skip + SafeRepr.maxstring_inner = 2**16 + SafeRepr.maxother_inner = 2**16 _is_debugpy_available = True except ImportError: _is_debugpy_available = False From e50ee827a03b8c4bb07ac529081fc2e6f085636d Mon Sep 17 00:00:00 2001 From: David Brochart Date: Thu, 2 Jun 2022 18:46:00 +0200 Subject: [PATCH 0840/1195] Use 'context':'clipboard' to prevent string stripping --- ipykernel/debugger.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 11ccfb68c..7d0f3054e 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -26,10 +26,7 @@ SuspendedFramesManager, _FramesTracker, ) - from _pydevd_bundle.pydevd_safe_repr import SafeRepr # isort: skip - SafeRepr.maxstring_inner = 2**16 - SafeRepr.maxother_inner = 2**16 _is_debugpy_available = True except ImportError: _is_debugpy_available = False @@ -598,7 +595,7 @@ async def richInspectVariables(self, message): "type": "request", "command": "evaluate", "seq": seq + 1, - "arguments": {"expression": code, "frameId": frame_id}, + "arguments": {"expression": code, "frameId": frame_id, "context": "clipboard"}, } ) if reply["success"]: From 448e16bbb8903540352ef1b03a4fd054271b84e6 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 3 Jun 2022 21:26:45 +0200 Subject: [PATCH 0841/1195] - --- ipykernel/debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 7d0f3054e..841966a72 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -595,7 +595,7 @@ async def richInspectVariables(self, message): "type": "request", "command": "evaluate", "seq": seq + 1, - "arguments": {"expression": code, "frameId": frame_id, "context": "clipboard"}, + "arguments": {"expression": code, "frameId": frame_id}, } ) if reply["success"]: From 9e4fa350b8ef0c74bcab977b3e0dd07d6c635687 Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 3 Jun 2022 21:44:15 +0200 Subject: [PATCH 0842/1195] Re-apply changes --- ipykernel/debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 841966a72..7d0f3054e 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -595,7 +595,7 @@ async def richInspectVariables(self, message): "type": "request", "command": "evaluate", "seq": seq + 1, - "arguments": {"expression": code, "frameId": frame_id}, + "arguments": {"expression": code, "frameId": frame_id, "context": "clipboard"}, } ) if reply["success"]: From a4ee39b9385836c8949a3efc4f595f5e7fd77924 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Jun 2022 16:38:33 +0000 Subject: [PATCH 0843/1195] Automated Changelog Entry for 6.13.1 on main --- CHANGELOG.md | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1752da2f6..a1106aebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,38 @@ +## 6.13.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.13.0...82179ef8ae4e9bdcd99a4a4c3807e8f773f1e92c)) + +### Bugs fixed + +- Fix richInspectVariables [#943](https://github.com/ipython/ipykernel/pull/943) ([@davidbrochart](https://github.com/davidbrochart)) +- Force debugger metadata in built wheel [#941](https://github.com/ipython/ipykernel/pull/941) ([@blink1073](https://github.com/blink1073)) + +### Maintenance and upkeep improvements + +- [pre-commit.ci] pre-commit autoupdate [#945](https://github.com/ipython/ipykernel/pull/945) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Clean up typings [#939](https://github.com/ipython/ipykernel/pull/939) ([@blink1073](https://github.com/blink1073)) +- [pre-commit.ci] pre-commit autoupdate [#938](https://github.com/ipython/ipykernel/pull/938) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Clean up types [#933](https://github.com/ipython/ipykernel/pull/933) ([@blink1073](https://github.com/blink1073)) +- [pre-commit.ci] pre-commit autoupdate [#932](https://github.com/ipython/ipykernel/pull/932) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Switch to hatch backend [#931](https://github.com/ipython/ipykernel/pull/931) ([@blink1073](https://github.com/blink1073)) +- [pre-commit.ci] pre-commit autoupdate [#928](https://github.com/ipython/ipykernel/pull/928) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#926](https://github.com/ipython/ipykernel/pull/926) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Allow enforce PR label workflow to add labels [#921](https://github.com/ipython/ipykernel/pull/921) ([@blink1073](https://github.com/blink1073)) +- [pre-commit.ci] pre-commit autoupdate [#920](https://github.com/ipython/ipykernel/pull/920) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#919](https://github.com/ipython/ipykernel/pull/919) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#917](https://github.com/ipython/ipykernel/pull/917) ([@pre-commit-ci](https://github.com/pre-commit-ci)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-04-11&to=2022-06-06&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-04-11..2022-06-06&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2022-04-11..2022-06-06&type=Issues) | [@fabioz](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afabioz+updated%3A2022-04-11..2022-06-06&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afcollonval+updated%3A2022-04-11..2022-06-06&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-04-11..2022-06-06&type=Issues) + + + ## 6.13.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.12.1...05c6e655e497a944fd738d9b744fad90bc78b70a)) @@ -27,8 +59,6 @@ [@andia89](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aandia89+updated%3A2022-04-04..2022-04-11&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-04-04..2022-04-11&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-04-04..2022-04-11&type=Issues) | [@meeseeksdev](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ameeseeksdev+updated%3A2022-04-04..2022-04-11&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-04-04..2022-04-11&type=Issues) | [@tlinhart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Atlinhart+updated%3A2022-04-04..2022-04-11&type=Issues) - - ## 6.12.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.12.0...3a04ea3fa50d01bcc09f10e3de8bb5570c2cd619)) From b76b4860381febd65429dd44bde2ff52b69769cc Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 6 Jun 2022 18:47:30 +0000 Subject: [PATCH 0844/1195] Publish 6.13.1 SHA256 hashes: ipykernel-6.13.1-py3-none-any.whl: fedc79bebd8a438162d056e0c7662d5ac8a47d1f6ef33a702e8460248dc4517f ipykernel-6.13.1.tar.gz: 6f42070a5d87ecbf4a2fc27a7faae8d690fd3794825a090ddf6b00b9678a5b69 --- ipykernel/_version.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 0bcd7a470..40af1ecdf 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for tbump versioning -__version__ = "6.13.0" +__version__ = "6.13.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/pyproject.toml b/pyproject.toml index 5b76a7c7f..54cd0ba2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ipykernel" -version = "6.13.0" +version = "6.13.1" authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] license = {file = "COPYING.md"} readme = "README.md" @@ -60,7 +60,7 @@ artifacts = ["ipykernel_launcher.py"] skip = ["check-links"] [tool.tbump.version] -current = "6.13.0" +current = "6.13.1" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From cdc1eefb5ada31a97e38b24bbf338e716e5205f7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Jun 2022 20:48:53 +0000 Subject: [PATCH 0845/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-eslint: v8.16.0 → v8.17.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.16.0...v8.17.0) - [github.com/sirosen/check-jsonschema: 0.15.1 → 0.16.0](https://github.com/sirosen/check-jsonschema/compare/0.15.1...0.16.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b57ca4ffb..0f108bf19 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -64,13 +64,13 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.16.0 + rev: v8.17.0 hooks: - id: eslint stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.15.1 + rev: 0.16.0 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From e4569b0c63fb483f31a9425763945e1c6f8b7f0a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 8 Jun 2022 15:55:45 -0400 Subject: [PATCH 0846/1195] fix sphinx 5.0 support --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 21c16dda6..75d9fa784 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -78,7 +78,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: From 0bed9b4108385feaa25d9943e4d8cbb84cbd1e50 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Thu, 9 Jun 2022 05:25:09 +0200 Subject: [PATCH 0847/1195] add cpu_count to the usage_reply --- ipykernel/kernelbase.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 482688bed..59f69bc00 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -983,6 +983,7 @@ async def usage_request(self, stream, ident, parent): # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. if cpu_percent is not None and cpu_percent != 0.0: reply_content["host_cpu_percent"] = cpu_percent + reply_content["cpu_count"] = psutil.cpu_count(logical=True) reply_content["host_virtual_memory"] = dict(psutil.virtual_memory()._asdict()) reply_msg = self.session.send(stream, "usage_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) From 473eee9171e800f5d18eb5d373abd17e2c18fb90 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 23 May 2022 16:26:09 +0200 Subject: [PATCH 0848/1195] ensure pstutil for the process is accurate --- ipykernel/control.py | 25 +++++++++++++++++++++++++ ipykernel/kernelbase.py | 19 ++----------------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index bb5f7b5f9..92e1f9eb6 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -1,6 +1,7 @@ from threading import Thread import zmq +import psutil if zmq.pyzmq_version_info() >= (17, 0): from tornado.ioloop import IOLoop @@ -8,13 +9,19 @@ # deprecated since pyzmq 17 from zmq.eventloop.ioloop import IOLoop +import logging +logger = logging.getLogger() +logger.setLevel(logging.INFO) class ControlThread(Thread): + processes = {} + def __init__(self, **kwargs): Thread.__init__(self, name="Control", **kwargs) self.io_loop = IOLoop(make_current=False) self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True + self.cpu_percent = psutil.cpu_percent() def run(self): self.name = "Control" @@ -24,6 +31,24 @@ def run(self): finally: self.io_loop.close() + def get_process_metric_value(self, process, name, attribute=None): + try: + # psutil.Process methods will either return... + pid = process.pid + p = self.processes.get(pid, None) + if not p: + self.processes[process.pid] = process + p = self.processes.get(pid) + metric_value = getattr(p, name)() + if attribute is not None: # ... a named tuple + return getattr(metric_value, attribute) + else: # ... or a number + return metric_value + # Avoid littering logs with stack traces + # complaining about dead processes + except BaseException: + return None + def stop(self): """Stop the thread. diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 59f69bc00..c7007ba6b 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -953,30 +953,15 @@ async def debug_request(self, stream, ident, parent): reply_msg = self.session.send(stream, "debug_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) - # Taken from https://github.com/jupyter-server/jupyter-resource-usage/blob/e6ec53fa69fdb6de8e878974bcff006310658408/jupyter_resource_usage/metrics.py#L16 - def get_process_metric_value(self, process, name, attribute=None): - try: - # psutil.Process methods will either return... - metric_value = getattr(process, name)() - if attribute is not None: # ... a named tuple - return getattr(metric_value, attribute) - else: # ... or a number - return metric_value - # Avoid littering logs with stack traces - # complaining about dead processes - except BaseException: - return None - async def usage_request(self, stream, ident, parent): reply_content = {"hostname": socket.gethostname(), "pid": os.getpid()} current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) - process_metric_value = self.get_process_metric_value reply_content["kernel_cpu"] = sum( - [process_metric_value(process, "cpu_percent", None) for process in all_processes] + [self.control_thread.get_process_metric_value(process, "cpu_percent", None) for process in all_processes] ) reply_content["kernel_memory"] = sum( - [process_metric_value(process, "memory_info", "rss") for process in all_processes] + [self.control_thread.get_process_metric_value(process, "memory_info", "rss") for process in all_processes] ) cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent From 3812b54dd0f7032b2e4e45950c6112017abf5479 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 24 May 2022 06:28:07 +0200 Subject: [PATCH 0849/1195] maintain the process map in kernelbase --- ipykernel/control.py | 20 -------------------- ipykernel/kernelbase.py | 24 ++++++++++++++++++++++-- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index 92e1f9eb6..689306638 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -14,14 +14,12 @@ logger.setLevel(logging.INFO) class ControlThread(Thread): - processes = {} def __init__(self, **kwargs): Thread.__init__(self, name="Control", **kwargs) self.io_loop = IOLoop(make_current=False) self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True - self.cpu_percent = psutil.cpu_percent() def run(self): self.name = "Control" @@ -31,24 +29,6 @@ def run(self): finally: self.io_loop.close() - def get_process_metric_value(self, process, name, attribute=None): - try: - # psutil.Process methods will either return... - pid = process.pid - p = self.processes.get(pid, None) - if not p: - self.processes[process.pid] = process - p = self.processes.get(pid) - metric_value = getattr(p, name)() - if attribute is not None: # ... a named tuple - return getattr(metric_value, attribute) - else: # ... or a number - return metric_value - # Avoid littering logs with stack traces - # complaining about dead processes - except BaseException: - return None - def stop(self): """Stop the thread. diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index c7007ba6b..e8b2efa4d 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -76,6 +76,8 @@ class Kernel(SingletonConfigurable): # attribute to override with a GUI eventloop = Any(None) + processes = {} + @observe("eventloop") def _update_eventloop(self, change): """schedule call to eventloop from IOLoop""" @@ -953,15 +955,33 @@ async def debug_request(self, stream, ident, parent): reply_msg = self.session.send(stream, "debug_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) + def get_process_metric_value(self, process, name, attribute=None): + try: + # psutil.Process methods will either return... + pid = process.pid + p = self.processes.get(pid, None) + if not p: + self.processes[pid] = process + p = self.processes.get(pid) + metric_value = getattr(p, name)() + if attribute is not None: # ... a named tuple + return getattr(metric_value, attribute) + else: # ... or a number + return metric_value + # Avoid littering logs with stack traces + # complaining about dead processes + except BaseException: + return None + async def usage_request(self, stream, ident, parent): reply_content = {"hostname": socket.gethostname(), "pid": os.getpid()} current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) reply_content["kernel_cpu"] = sum( - [self.control_thread.get_process_metric_value(process, "cpu_percent", None) for process in all_processes] + [self.get_process_metric_value(process, "cpu_percent", None) for process in all_processes] ) reply_content["kernel_memory"] = sum( - [self.control_thread.get_process_metric_value(process, "memory_info", "rss") for process in all_processes] + [self.get_process_metric_value(process, "memory_info", "rss") for process in all_processes] ) cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent From add40d25bf2641dd4960f97d6f876bea98930191 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 24 May 2022 06:29:06 +0200 Subject: [PATCH 0850/1195] remove unneeded imports --- ipykernel/control.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index 689306638..c1de9d15d 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -1,7 +1,6 @@ from threading import Thread import zmq -import psutil if zmq.pyzmq_version_info() >= (17, 0): from tornado.ioloop import IOLoop @@ -9,10 +8,6 @@ # deprecated since pyzmq 17 from zmq.eventloop.ioloop import IOLoop -import logging -logger = logging.getLogger() -logger.setLevel(logging.INFO) - class ControlThread(Thread): def __init__(self, **kwargs): From 676b1b1bb6b0aa928854f4c6788d9a722de18a44 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 24 May 2022 06:29:49 +0200 Subject: [PATCH 0851/1195] remove unneeded imports --- ipykernel/control.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/control.py b/ipykernel/control.py index c1de9d15d..fe1645284 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -8,6 +8,7 @@ # deprecated since pyzmq 17 from zmq.eventloop.ioloop import IOLoop + class ControlThread(Thread): def __init__(self, **kwargs): From d08c94a95e2cffa64e76f31b7b8e5b5d687aa7d7 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Tue, 24 May 2022 06:30:08 +0200 Subject: [PATCH 0852/1195] remove unneeded imports --- ipykernel/control.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index fe1645284..bb5f7b5f9 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -10,7 +10,6 @@ class ControlThread(Thread): - def __init__(self, **kwargs): Thread.__init__(self, name="Control", **kwargs) self.io_loop = IOLoop(make_current=False) From d82ad7203e066e4f63095796a40f6f8efe63f57b Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 6 Jun 2022 15:19:57 +0200 Subject: [PATCH 0853/1195] prune the unexisting processes --- ipykernel/kernelbase.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e8b2efa4d..07672ee0a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -977,6 +977,12 @@ async def usage_request(self, stream, ident, parent): reply_content = {"hostname": socket.gethostname(), "pid": os.getpid()} current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) + pruned_processes = {} + for process in all_processes: + p = self.processes.get(process.pid) + if p is not None: + pruned_processes[process.pid] = p + self.processes = pruned_processes reply_content["kernel_cpu"] = sum( [self.get_process_metric_value(process, "cpu_percent", None) for process in all_processes] ) From a7b2b27645b3d26808545488725590d10452992a Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Wed, 8 Jun 2022 06:18:14 +0200 Subject: [PATCH 0854/1195] do not creazte intermediate structure to prune the proceses --- ipykernel/kernelbase.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 07672ee0a..303f0a7af 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -977,12 +977,12 @@ async def usage_request(self, stream, ident, parent): reply_content = {"hostname": socket.gethostname(), "pid": os.getpid()} current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) - pruned_processes = {} - for process in all_processes: - p = self.processes.get(process.pid) - if p is not None: - pruned_processes[process.pid] = p - self.processes = pruned_processes + # Ensure 1) self.processes is updated to only current subprocesses + # and 2) we reuse processes when possible (needed for accurate CPU) + self.processes = {process.pid: self.processes.get(process.pid, process) for process in all_processes} + self.processes = { + process.pid: self.processes.get(process.pid, process) for process in all_processes + } reply_content["kernel_cpu"] = sum( [self.get_process_metric_value(process, "cpu_percent", None) for process in all_processes] ) From 2b28ce81e2ade1efb78bf32dfac6b31957bb9e02 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Wed, 8 Jun 2022 15:52:13 +0200 Subject: [PATCH 0855/1195] correctly maange the process map --- ipykernel/kernelbase.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 303f0a7af..872f8111d 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -957,13 +957,7 @@ async def debug_request(self, stream, ident, parent): def get_process_metric_value(self, process, name, attribute=None): try: - # psutil.Process methods will either return... - pid = process.pid - p = self.processes.get(pid, None) - if not p: - self.processes[pid] = process - p = self.processes.get(pid) - metric_value = getattr(p, name)() + metric_value = getattr(process, name)() if attribute is not None: # ... a named tuple return getattr(metric_value, attribute) else: # ... or a number @@ -979,15 +973,14 @@ async def usage_request(self, stream, ident, parent): all_processes = [current_process] + current_process.children(recursive=True) # Ensure 1) self.processes is updated to only current subprocesses # and 2) we reuse processes when possible (needed for accurate CPU) - self.processes = {process.pid: self.processes.get(process.pid, process) for process in all_processes} self.processes = { process.pid: self.processes.get(process.pid, process) for process in all_processes } reply_content["kernel_cpu"] = sum( - [self.get_process_metric_value(process, "cpu_percent", None) for process in all_processes] + [self.get_process_metric_value(process, "cpu_percent", None) for process in self.processes.values()] ) reply_content["kernel_memory"] = sum( - [self.get_process_metric_value(process, "memory_info", "rss") for process in all_processes] + [self.get_process_metric_value(process, "memory_info", "rss") for process in self.processes.values()] ) cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent From 3a9d9de7907fbf0495c3a08e35714e2980a620a2 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Thu, 9 Jun 2022 04:59:56 +0200 Subject: [PATCH 0856/1195] lint --- ipykernel/kernelbase.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 872f8111d..cebaa41f5 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -977,10 +977,16 @@ async def usage_request(self, stream, ident, parent): process.pid: self.processes.get(process.pid, process) for process in all_processes } reply_content["kernel_cpu"] = sum( - [self.get_process_metric_value(process, "cpu_percent", None) for process in self.processes.values()] + [ + self.get_process_metric_value(process, "cpu_percent", None) + for process in self.processes.values() + ] ) reply_content["kernel_memory"] = sum( - [self.get_process_metric_value(process, "memory_info", "rss") for process in self.processes.values()] + [ + self.get_process_metric_value(process, "memory_info", "rss") + for process in self.processes.values() + ] ) cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent From 3ceabee3da357769d190b31d1652ad2e369ac6e3 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Thu, 9 Jun 2022 05:08:45 +0200 Subject: [PATCH 0857/1195] var annotation for the processes class field --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index cebaa41f5..4c9fee52a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -76,7 +76,7 @@ class Kernel(SingletonConfigurable): # attribute to override with a GUI eventloop = Any(None) - processes = {} + processes: dict[str, psutil.Process] = {} @observe("eventloop") def _update_eventloop(self, change): From 361bd90b8381a7aecc2b998ac04880a2a612fff9 Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Thu, 9 Jun 2022 16:37:35 +0200 Subject: [PATCH 0858/1195] use typing package for python 3.8 --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 4c9fee52a..30ab173f6 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -76,7 +76,7 @@ class Kernel(SingletonConfigurable): # attribute to override with a GUI eventloop = Any(None) - processes: dict[str, psutil.Process] = {} + processes: t.Dict[str, psutil.Process] = {} @observe("eventloop") def _update_eventloop(self, change): From 14c38531820140158b7bf5ca16e5319795a5535f Mon Sep 17 00:00:00 2001 From: Eric Charles Date: Mon, 13 Jun 2022 17:44:27 +0200 Subject: [PATCH 0859/1195] use pss memory info type if available --- ipykernel/kernelbase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 30ab173f6..e1cc99d71 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -982,9 +982,10 @@ async def usage_request(self, stream, ident, parent): for process in self.processes.values() ] ) + mem_info_type = "pss" if hasattr(current_process.memory_full_info(), "pss") else "rss" reply_content["kernel_memory"] = sum( [ - self.get_process_metric_value(process, "memory_info", "rss") + self.get_process_metric_value(process, "memory_full_info", mem_info_type) for process in self.processes.values() ] ) From 0fb35bf12d6ba915de31ec2001e6222326341a75 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 16:05:36 +0000 Subject: [PATCH 0860/1195] Automated Changelog Entry for 6.14.0 on main --- CHANGELOG.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1106aebf..09f9d47f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ +## 6.14.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.13.1...269569787419a47da562ed69fbe6363619f3b7e5)) + +### Enhancements made + +- Add cpu_count to the usage_reply [#952](https://github.com/ipython/ipykernel/pull/952) ([@echarles](https://github.com/echarles)) + +### Bugs fixed + +- use pss memory info type if available for the resource usage reply [#948](https://github.com/ipython/ipykernel/pull/948) ([@echarles](https://github.com/echarles)) +- Ensure psutil for the process is accurate [#937](https://github.com/ipython/ipykernel/pull/937) ([@echarles](https://github.com/echarles)) + +### Maintenance and upkeep improvements + +- Fix sphinx 5.0 support [#951](https://github.com/ipython/ipykernel/pull/951) ([@blink1073](https://github.com/blink1073)) +- [pre-commit.ci] pre-commit autoupdate [#950](https://github.com/ipython/ipykernel/pull/950) ([@pre-commit-ci](https://github.com/pre-commit-ci)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-06-06&to=2022-06-13&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-06-06..2022-06-13&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-06-06..2022-06-13&type=Issues) | [@nishikantparmariam](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anishikantparmariam+updated%3A2022-06-06..2022-06-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-06-06..2022-06-13&type=Issues) + + + ## 6.13.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.13.0...82179ef8ae4e9bdcd99a4a4c3807e8f773f1e92c)) @@ -32,8 +58,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-04-11..2022-06-06&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2022-04-11..2022-06-06&type=Issues) | [@fabioz](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afabioz+updated%3A2022-04-11..2022-06-06&type=Issues) | [@fcollonval](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afcollonval+updated%3A2022-04-11..2022-06-06&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-04-11..2022-06-06&type=Issues) - - ## 6.13.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.12.1...05c6e655e497a944fd738d9b744fad90bc78b70a)) From cbca2a5e561bd4f45c84f20ccda7ad71085473bb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 16:05:51 +0000 Subject: [PATCH 0861/1195] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09f9d47f9..0cbd0d52b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Bugs fixed - use pss memory info type if available for the resource usage reply [#948](https://github.com/ipython/ipykernel/pull/948) ([@echarles](https://github.com/echarles)) -- Ensure psutil for the process is accurate [#937](https://github.com/ipython/ipykernel/pull/937) ([@echarles](https://github.com/echarles)) +- Ensure psutil for the process is accurate [#937](https://github.com/ipython/ipykernel/pull/937) ([@echarles](https://github.com/echarles)) ### Maintenance and upkeep improvements From 121c1a09530aeb5071ca5f91871554f5bb183d55 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 16:28:46 +0000 Subject: [PATCH 0862/1195] Publish 6.14.0 SHA256 hashes: ipykernel-6.14.0-py3-none-any.whl: cbf50ed3dc9998d4077d8cd263275f232cbe67dbd2e79cd6d2644f3a4418148b ipykernel-6.14.0.tar.gz: 720f2a07aadf70ca05b034f2cdd0590a8bd56581ddf3b7ec4a37075366270e89 --- ipykernel/_version.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 40af1ecdf..96f5c1540 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for tbump versioning -__version__ = "6.13.1" +__version__ = "6.14.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/pyproject.toml b/pyproject.toml index 54cd0ba2b..1d9e9d000 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ipykernel" -version = "6.13.1" +version = "6.14.0" authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] license = {file = "COPYING.md"} readme = "README.md" @@ -60,7 +60,7 @@ artifacts = ["ipykernel_launcher.py"] skip = ["check-links"] [tool.tbump.version] -current = "6.13.1" +current = "6.14.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 6afbbf0ad264f90f849a19dc3c5b4c52da590860 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 13 Jun 2022 22:34:58 +0000 Subject: [PATCH 0863/1195] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/pre-commit-hooks: v4.2.0 → v4.3.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.2.0...v4.3.0) - [github.com/pre-commit/mirrors-mypy: v0.960 → v0.961](https://github.com/pre-commit/mirrors-mypy/compare/v0.960...v0.961) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0f108bf19..7a02a9d96 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: end-of-file-fixer - id: check-case-conflict @@ -42,7 +42,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.960 + rev: v0.961 hooks: - id: mypy exclude: "ipykernel.*tests" From 4036788aaf8461a9efb42293e4aad2a6d3776223 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 14 Jun 2022 23:07:37 +0200 Subject: [PATCH 0864/1195] Fix compatibility with tornado 6.2 beta (#956) Co-authored-by: Steven Silvester --- ipykernel/control.py | 11 ++--------- ipykernel/iostream.py | 14 +++++--------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index bb5f7b5f9..ee475188d 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -1,24 +1,17 @@ from threading import Thread -import zmq - -if zmq.pyzmq_version_info() >= (17, 0): - from tornado.ioloop import IOLoop -else: - # deprecated since pyzmq 17 - from zmq.eventloop.ioloop import IOLoop +from tornado.platform.asyncio import AsyncIOLoop class ControlThread(Thread): def __init__(self, **kwargs): Thread.__init__(self, name="Control", **kwargs) - self.io_loop = IOLoop(make_current=False) + self.io_loop = AsyncIOLoop(make_current=False) self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True def run(self): self.name = "Control" - self.io_loop.make_current() try: self.io_loop.start() finally: diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 419d092c3..43644c224 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -17,14 +17,11 @@ from weakref import WeakSet import zmq - -if zmq.pyzmq_version_info() >= (17, 0): - from tornado.ioloop import IOLoop -else: - # deprecated since pyzmq 17 - from zmq.eventloop.ioloop import IOLoop - from jupyter_client.session import extract_header + +# AsyncIOLoop always creates a new asyncio event loop, +# rather than the default AsyncIOMainLoop +from tornado.platform.asyncio import AsyncIOLoop from zmq.eventloop.zmqstream import ZMQStream # ----------------------------------------------------------------------------- @@ -63,7 +60,7 @@ def __init__(self, socket, pipe=False): self.background_socket = BackgroundSocket(self) self._master_pid = os.getpid() self._pipe_flag = pipe - self.io_loop = IOLoop(make_current=False) + self.io_loop = AsyncIOLoop(make_current=False) if pipe: self._setup_pipe_in() self._local = threading.local() @@ -78,7 +75,6 @@ def __init__(self, socket, pipe=False): def _thread_main(self): """The inner loop that's actually run in a thread""" - self.io_loop.make_current() self.io_loop.start() self.io_loop.close(all_fds=True) From d5eb5139d36d4494ff1d7ac0754f189cd649ec81 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 15 Jun 2022 15:04:43 +0200 Subject: [PATCH 0865/1195] Explicitly require pyzmq >= 17 (#957) --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 1d9e9d000..c9c717ffe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,6 +32,7 @@ dependencies = [ "tornado>=6.1", "matplotlib-inline>=0.1", 'appnope;platform_system=="Darwin"', + "pyzmq>=17", "psutil", "nest_asyncio", "packaging", From 5c1adcae929d8b4d28bf2b7849fe0e220c729b26 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 15 Jun 2022 15:05:32 +0200 Subject: [PATCH 0866/1195] Back to top-level tornado IOLoop (#958) --- ipykernel/control.py | 4 ++-- ipykernel/iostream.py | 7 ++----- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/ipykernel/control.py b/ipykernel/control.py index ee475188d..1aaf9a7e8 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -1,12 +1,12 @@ from threading import Thread -from tornado.platform.asyncio import AsyncIOLoop +from tornado.ioloop import IOLoop class ControlThread(Thread): def __init__(self, **kwargs): Thread.__init__(self, name="Control", **kwargs) - self.io_loop = AsyncIOLoop(make_current=False) + self.io_loop = IOLoop(make_current=False) self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 43644c224..aeaf38e02 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -18,10 +18,7 @@ import zmq from jupyter_client.session import extract_header - -# AsyncIOLoop always creates a new asyncio event loop, -# rather than the default AsyncIOMainLoop -from tornado.platform.asyncio import AsyncIOLoop +from tornado.ioloop import IOLoop from zmq.eventloop.zmqstream import ZMQStream # ----------------------------------------------------------------------------- @@ -60,7 +57,7 @@ def __init__(self, socket, pipe=False): self.background_socket = BackgroundSocket(self) self._master_pid = os.getpid() self._pipe_flag = pipe - self.io_loop = AsyncIOLoop(make_current=False) + self.io_loop = IOLoop(make_current=False) if pipe: self._setup_pipe_in() self._local = threading.local() From df810657a9758bb9ebca8e21f08f5137e514b86b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 15 Jun 2022 11:46:56 -0500 Subject: [PATCH 0867/1195] Automated Changelog Entry for 6.15.0 on main (#959) Co-authored-by: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cbd0d52b..23c26a857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ +## 6.15.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.14.0...5c1adcae929d8b4d28bf2b7849fe0e220c729b26)) + +### Bugs fixed + +- Fix compatibility with tornado 6.2 beta [#956](https://github.com/ipython/ipykernel/pull/956) ([@minrk](https://github.com/minrk)) + +### Maintenance and upkeep improvements + +- Back to top-level tornado IOLoop [#958](https://github.com/ipython/ipykernel/pull/958) ([@minrk](https://github.com/minrk)) +- Explicitly require pyzmq >= 17 [#957](https://github.com/ipython/ipykernel/pull/957) ([@minrk](https://github.com/minrk)) +- [pre-commit.ci] pre-commit autoupdate [#954](https://github.com/ipython/ipykernel/pull/954) ([@pre-commit-ci](https://github.com/pre-commit-ci)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-06-13&to=2022-06-15&type=c)) + +[@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2022-06-13..2022-06-15&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-06-13..2022-06-15&type=Issues) + + + ## 6.14.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.13.1...269569787419a47da562ed69fbe6363619f3b7e5)) @@ -26,8 +48,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-06-06..2022-06-13&type=Issues) | [@echarles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aecharles+updated%3A2022-06-06..2022-06-13&type=Issues) | [@nishikantparmariam](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anishikantparmariam+updated%3A2022-06-06..2022-06-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-06-06..2022-06-13&type=Issues) - - ## 6.13.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.13.0...82179ef8ae4e9bdcd99a4a4c3807e8f773f1e92c)) From b1da6d0298a8b79490917bf761f8a5e0fe36f8fe Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 15 Jun 2022 16:49:21 +0000 Subject: [PATCH 0868/1195] Publish 6.15.0 SHA256 hashes: ipykernel-6.15.0-py3-none-any.whl: b9ed519a29eb819eb82e87e0d3754088237b233e5c647b8bb0ff23c8c70ed16f ipykernel-6.15.0.tar.gz: b59f9d9672c3a483494bb75915a2b315e78b833a38b039b1ee36dc28683f0d89 --- ipykernel/_version.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 96f5c1540..d3cddd7a3 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for tbump versioning -__version__ = "6.14.0" +__version__ = "6.15.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/pyproject.toml b/pyproject.toml index c9c717ffe..3c6487ab1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ipykernel" -version = "6.14.0" +version = "6.15.0" authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] license = {file = "COPYING.md"} readme = "README.md" @@ -61,7 +61,7 @@ artifacts = ["ipykernel_launcher.py"] skip = ["check-links"] [tool.tbump.version] -current = "6.14.0" +current = "6.15.0" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 988aafb5298f9798c002669fc27452675b0d3537 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 20 Jun 2022 16:25:35 -0500 Subject: [PATCH 0869/1195] [pre-commit.ci] pre-commit autoupdate (#960) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7a02a9d96..30de9a4bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: args: [--profile=black] - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.6.2 + rev: v2.7.1 hooks: - id: prettier @@ -64,7 +64,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.17.0 + rev: v8.18.0 hooks: - id: eslint stages: [manual] From f03856014d5fdf86779a72b8d7f85f412f446696 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 Jun 2022 16:24:07 -0500 Subject: [PATCH 0870/1195] [pre-commit.ci] pre-commit autoupdate (#961) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 30de9a4bc..e5f901e60 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -70,7 +70,7 @@ repos: stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.16.0 + rev: 0.16.2 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From 6b1054e2cd4e625f7cd5fa1a6a90ecb99d118a4f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Jul 2022 08:50:15 +0200 Subject: [PATCH 0871/1195] [pre-commit.ci] pre-commit autoupdate (#962) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.3.0 → 22.6.0](https://github.com/psf/black/compare/22.3.0...22.6.0) - [github.com/pre-commit/mirrors-eslint: v8.18.0 → v8.19.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.18.0...v8.19.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e5f901e60..a845a8e23 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 22.3.0 + rev: 22.6.0 hooks: - id: black args: ["--line-length", "100"] @@ -64,7 +64,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.18.0 + rev: v8.19.0 hooks: - id: eslint stages: [manual] From d9a8578ab2864b4ee636b12252e04a9b70047d0b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 8 Jul 2022 05:46:26 -0500 Subject: [PATCH 0872/1195] Fix inclusion of launcher file and check in CI (#964) --- .github/workflows/ci.yml | 5 +++++ pyproject.toml | 6 +++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 151dd3e9f..8464830c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,6 +77,11 @@ jobs: run: | codecov + - name: Check Launcher + run: | + cd $HOME + python -m ipykernel_launcher --help + pre-commit: name: pre-commit runs-on: ubuntu-latest diff --git a/pyproject.toml b/pyproject.toml index 3c6487ab1..5de353fb5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["hatchling>=0.25", "jupyter_client>=6"] +requires = ["hatchling>=1.4", "jupyter_client>=6"] build-backend = "hatchling.build" [project] @@ -54,8 +54,8 @@ test = [ [tool.hatch.build.targets.wheel.shared-data] "data_kernelspec" = "share/jupyter/kernels/python3" -[tool.hatch.build] -artifacts = ["ipykernel_launcher.py"] +[tool.hatch.build.force-include] +"./ipykernel_launcher.py" = "ipykernel_launcher.py" [tool.jupyter-releaser] skip = ["check-links"] From f4074a848f43eb77171e9b9641d1fdc54ad6f152 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 8 Jul 2022 05:55:20 -0500 Subject: [PATCH 0873/1195] Automated Changelog Entry for 6.15.1 on main (#965) Co-authored-by: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23c26a857..09db750a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ +## 6.15.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.0...d9a8578ab2864b4ee636b12252e04a9b70047d0b)) + +### Bugs fixed + +- Fix inclusion of launcher file and check in CI [#964](https://github.com/ipython/ipykernel/pull/964) ([@blink1073](https://github.com/blink1073)) + +### Maintenance and upkeep improvements + +- [pre-commit.ci] pre-commit autoupdate [#962](https://github.com/ipython/ipykernel/pull/962) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#961](https://github.com/ipython/ipykernel/pull/961) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#960](https://github.com/ipython/ipykernel/pull/960) ([@pre-commit-ci](https://github.com/pre-commit-ci)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-06-15&to=2022-07-08&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-06-15..2022-07-08&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-06-15..2022-07-08&type=Issues) + + + ## 6.15.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.14.0...5c1adcae929d8b4d28bf2b7849fe0e220c729b26)) @@ -22,8 +44,6 @@ [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2022-06-13..2022-06-15&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-06-13..2022-06-15&type=Issues) - - ## 6.14.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.13.1...269569787419a47da562ed69fbe6363619f3b7e5)) From ca7589412bd24e365076186c4ed948566ac011a3 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 8 Jul 2022 10:57:18 +0000 Subject: [PATCH 0874/1195] Publish 6.15.1 SHA256 hashes: ipykernel-6.15.1-py3-none-any.whl: d8969c5b23b0e453a23166da5a669c954db399789293fcb03fec5cb25367e43c ipykernel-6.15.1.tar.gz: 37acc3254caa8a0dafcddddc8dc863a60ad1b46487b68aee361d9a15bda98112 --- ipykernel/_version.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index d3cddd7a3..26625cb8b 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for tbump versioning -__version__ = "6.15.0" +__version__ = "6.15.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/pyproject.toml b/pyproject.toml index 5de353fb5..13390298f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ipykernel" -version = "6.15.0" +version = "6.15.1" authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] license = {file = "COPYING.md"} readme = "README.md" @@ -61,7 +61,7 @@ test = [ skip = ["check-links"] [tool.tbump.version] -current = "6.15.0" +current = "6.15.1" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From dd117d5666a8a97dbf459b24c5df93109b16142d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 11 Jul 2022 17:37:24 -0500 Subject: [PATCH 0875/1195] [pre-commit.ci] pre-commit autoupdate (#966) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a845a8e23..a3c451352 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -70,7 +70,7 @@ repos: stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.16.2 + rev: 0.17.0 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From 4fcee471531645771a4c1e8b32fb0b85b5b95a2c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 20 Jul 2022 11:05:16 +0200 Subject: [PATCH 0876/1195] [pre-commit.ci] pre-commit autoupdate (#968) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pre-commit/mirrors-eslint: v8.19.0 → v8.20.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.19.0...v8.20.0) - [github.com/sirosen/check-jsonschema: 0.17.0 → 0.17.1](https://github.com/sirosen/check-jsonschema/compare/0.17.0...0.17.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a3c451352..c1ac30725 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -64,13 +64,13 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.19.0 + rev: v8.20.0 hooks: - id: eslint stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.17.0 + rev: 0.17.1 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From cac560af411cd6725c61442de5556cb3d2fe9516 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 25 Jul 2022 17:39:51 -0500 Subject: [PATCH 0877/1195] [pre-commit.ci] pre-commit autoupdate (#971) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c1ac30725..e10de6f1c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.961 + rev: v0.971 hooks: - id: mypy exclude: "ipykernel.*tests" From e177fe0ea3e86c31de5d2d2435217640b520e3b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Aug 2022 20:49:05 -0500 Subject: [PATCH 0878/1195] [pre-commit.ci] pre-commit autoupdate (#974) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e10de6f1c..e029f6286 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,7 +35,7 @@ repos: - id: prettier - repo: https://github.com/PyCQA/doc8 - rev: 0.11.2 + rev: v1.0.0 hooks: - id: doc8 args: [--max-line-length=200] @@ -52,19 +52,15 @@ repos: stages: [manual] - repo: https://github.com/pycqa/flake8 - rev: 4.0.1 + rev: 5.0.2 hooks: - id: flake8 additional_dependencies: - [ - "flake8-bugbear==20.1.4", - "flake8-logging-format==0.6.0", - "flake8-implicit-str-concat==0.2.0", - ] + ["flake8-bugbear==22.6.22", "flake8-implicit-str-concat==0.2.0"] stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.20.0 + rev: v8.21.0 hooks: - id: eslint stages: [manual] From 13f07ca560b6ab9aaed501de87969f095505d722 Mon Sep 17 00:00:00 2001 From: Audrey Dutcher Date: Mon, 8 Aug 2022 15:02:03 -0700 Subject: [PATCH 0879/1195] `_abort_queues` is no longer async (#942) --- ipykernel/inprocess/ipkernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index aaaa5099f..f185ddc35 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -81,7 +81,7 @@ def start(self): """Override registration of dispatchers for streams.""" self.shell.exit_now = False - async def _abort_queues(self): + def _abort_queues(self): """The in-process kernel doesn't abort requests.""" pass From 689c5d6596549e7be13350349fcb5fe2b9941b92 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 9 Aug 2022 08:16:44 +0200 Subject: [PATCH 0880/1195] [pre-commit.ci] pre-commit autoupdate (#976) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/pycqa/flake8: 5.0.2 → 5.0.4](https://github.com/pycqa/flake8/compare/5.0.2...5.0.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e029f6286..8e0d631ae 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,7 +52,7 @@ repos: stages: [manual] - repo: https://github.com/pycqa/flake8 - rev: 5.0.2 + rev: 5.0.4 hooks: - id: flake8 additional_dependencies: From e21e952293dbeb2a1cc10ce8b9a9ad3dd4b51494 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Aug 2022 18:31:45 -0500 Subject: [PATCH 0881/1195] [pre-commit.ci] pre-commit autoupdate (#977) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8e0d631ae..ec7506e58 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,7 +60,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.21.0 + rev: v8.22.0 hooks: - id: eslint stages: [manual] From 724753a185b0954f0e662c226b86dc8146c62bcb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Aug 2022 17:59:52 -0500 Subject: [PATCH 0882/1195] [pre-commit.ci] pre-commit autoupdate (#978) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ec7506e58..0c127ab74 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,7 +30,7 @@ repos: args: [--profile=black] - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 + rev: v3.0.0-alpha.0 hooks: - id: prettier From 72cd22b85316f5f242588ea28b4983bb2d5a9e85 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 29 Aug 2022 11:44:06 -0500 Subject: [PATCH 0883/1195] Automated Changelog Entry for 6.15.2 on main (#980) Co-authored-by: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09db750a1..7f784938a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ +## 6.15.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.1...724753a185b0954f0e662c226b86dc8146c62bcb)) + +### Bugs fixed + +- `_abort_queues` is no longer async [#942](https://github.com/ipython/ipykernel/pull/942) ([@rhelmot](https://github.com/rhelmot)) + +### Maintenance and upkeep improvements + +- [pre-commit.ci] pre-commit autoupdate [#978](https://github.com/ipython/ipykernel/pull/978) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#977](https://github.com/ipython/ipykernel/pull/977) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#976](https://github.com/ipython/ipykernel/pull/976) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#974](https://github.com/ipython/ipykernel/pull/974) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#971](https://github.com/ipython/ipykernel/pull/971) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#968](https://github.com/ipython/ipykernel/pull/968) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#966](https://github.com/ipython/ipykernel/pull/966) ([@pre-commit-ci](https://github.com/pre-commit-ci)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-07-08&to=2022-08-29&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-07-08..2022-08-29&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-07-08..2022-08-29&type=Issues) | [@rayosborn](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Arayosborn+updated%3A2022-07-08..2022-08-29&type=Issues) | [@rhelmot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Arhelmot+updated%3A2022-07-08..2022-08-29&type=Issues) + + + ## 6.15.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.0...d9a8578ab2864b4ee636b12252e04a9b70047d0b)) @@ -22,8 +48,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-06-15..2022-07-08&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-06-15..2022-07-08&type=Issues) - - ## 6.15.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.14.0...5c1adcae929d8b4d28bf2b7849fe0e220c729b26)) From 9aee3f1f172d1b0e36ba2727b97001a42d098bfe Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 16:46:00 +0000 Subject: [PATCH 0884/1195] Publish 6.15.2 SHA256 hashes: ipykernel-6.15.2-py3-none-any.whl: 59183ef833b82c72211aace3fb48fd20eae8e2d0cae475f3d5c39d4a688e81ec ipykernel-6.15.2.tar.gz: e7481083b438609c9c8a22d6362e8e1bc6ec94ba0741b666941e634f2d61bdf3 --- ipykernel/_version.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 26625cb8b..0421edeae 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for tbump versioning -__version__ = "6.15.1" +__version__ = "6.15.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/pyproject.toml b/pyproject.toml index 13390298f..1c6998dbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ipykernel" -version = "6.15.1" +version = "6.15.2" authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] license = {file = "COPYING.md"} readme = "README.md" @@ -61,7 +61,7 @@ test = [ skip = ["check-links"] [tool.tbump.version] -current = "6.15.1" +current = "6.15.2" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From 9ac0b49b7f9b8cf78855086dd29ec3957dc7439f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Aug 2022 17:22:13 -0500 Subject: [PATCH 0885/1195] [pre-commit.ci] pre-commit autoupdate (#982) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0c127ab74..c033ce9a7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,13 +60,13 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.22.0 + rev: v8.23.0 hooks: - id: eslint stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.17.1 + rev: 0.18.1 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From e135d6f3d3c0937c901d8a3b274e389467136ce6 Mon Sep 17 00:00:00 2001 From: Stephannie Jimenez Gacha Date: Thu, 1 Sep 2022 10:20:18 -0500 Subject: [PATCH 0886/1195] Add python logo in svg format (#984) --- ipykernel/resources/logo-svg.svg | 265 +++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100644 ipykernel/resources/logo-svg.svg diff --git a/ipykernel/resources/logo-svg.svg b/ipykernel/resources/logo-svg.svg new file mode 100644 index 000000000..467b07b26 --- /dev/null +++ b/ipykernel/resources/logo-svg.svg @@ -0,0 +1,265 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From cce9b7d736d2cdd32e8695436dbed77bc7912080 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Sep 2022 20:27:54 -0500 Subject: [PATCH 0887/1195] [pre-commit.ci] pre-commit autoupdate (#985) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c033ce9a7..b8fa61467 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 22.6.0 + rev: 22.8.0 hooks: - id: black args: ["--line-length", "100"] @@ -66,7 +66,7 @@ repos: stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.18.1 + rev: 0.18.2 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From b115701d6eb227006c8fe428a97b531b4fca3649 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 12 Sep 2022 17:52:23 -0500 Subject: [PATCH 0888/1195] [pre-commit.ci] pre-commit autoupdate (#989) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b8fa61467..bf0602f90 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,7 +60,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.23.0 + rev: v8.23.1 hooks: - id: eslint stages: [manual] From 8d3c88b0abed2e805e135c5d517b68188e916913 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Tue, 13 Sep 2022 15:45:37 +0200 Subject: [PATCH 0889/1195] PR: Close memory leak (#990) --- ipykernel/eventloops.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index fdc5ddd60..cb693e8f8 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -21,7 +21,7 @@ def _use_appnope(): return sys.platform == "darwin" and V(platform.mac_ver()[0]) >= V("10.9") -def _notify_stream_qt(kernel, stream): +def _notify_stream_qt(kernel): from IPython.external.qt_for_kernel import QtCore @@ -31,13 +31,17 @@ def process_stream_events(): # due to our consuming of the edge-triggered FD # flush returns the number of events consumed. # if there were any, wake it up - if stream.flush(limit=1): - notifier.setEnabled(False) + if kernel.shell_stream.flush(limit=1): + kernel._qt_notifier.setEnabled(False) kernel.app.quit() - fd = stream.getsockopt(zmq.FD) - notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) - notifier.activated.connect(process_stream_events) + if not hasattr(kernel, "_qt_notifier"): + fd = kernel.shell_stream.getsockopt(zmq.FD) + kernel._qt_notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) + kernel._qt_notifier.activated.connect(process_stream_events) + else: + kernel._qt_notifier.setEnabled(True) + # there may already be unprocessed events waiting. # these events will not wake zmq's edge-triggered FD # since edge-triggered notification only occurs on new i/o activity. @@ -45,10 +49,11 @@ def process_stream_events(): # so we start in a clean state ensuring that any new i/o events will notify. # schedule first call on the eventloop as soon as it's running, # so we don't block here processing events - timer = QtCore.QTimer(kernel.app) - timer.setSingleShot(True) - timer.timeout.connect(process_stream_events) - timer.start(0) + if not hasattr(kernel, "_qt_timer"): + kernel._qt_timer = QtCore.QTimer(kernel.app) + kernel._qt_timer.setSingleShot(True) + kernel._qt_timer.timeout.connect(process_stream_events) + kernel._qt_timer.start(0) # mapping of keys to loop functions @@ -118,7 +123,7 @@ def loop_qt4(kernel): kernel.app = get_app_qt4([" "]) if isinstance(kernel.app, QtGui.QApplication): kernel.app.setQuitOnLastWindowClosed(False) - _notify_stream_qt(kernel, kernel.shell_stream) + _notify_stream_qt(kernel) _loop_qt(kernel.app) From 861b1242a7601f1608707ed8bbfb6e801914cb4a Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 13 Sep 2022 16:57:52 +0200 Subject: [PATCH 0890/1195] Handle all possible exceptions when trying to import the debugger (#987) --- ipykernel/debugger.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 7d0f3054e..224678232 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -30,6 +30,14 @@ _is_debugpy_available = True except ImportError: _is_debugpy_available = False +except Exception as e: + # We cannot import the module where the DebuggerInitializationError + # is defined + if e.__class__.__name__ == "DebuggerInitializationError": + _is_debugpy_available = False + else: + raise e + # Required for backwards compatiblity ROUTING_ID = getattr(zmq, "ROUTING_ID", None) or zmq.IDENTITY From 9a09c4fade7759c01e0db2c1d695fa2c44ab54f1 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 13 Sep 2022 10:28:43 -0500 Subject: [PATCH 0891/1195] Automated Changelog Entry for 6.15.3 on main (#991) Co-authored-by: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f784938a..597cdf21b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ +## 6.15.3 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.2...861b1242a7601f1608707ed8bbfb6e801914cb4a)) + +### Bugs fixed + +- PR: Close memory leak [#990](https://github.com/ipython/ipykernel/pull/990) ([@impact27](https://github.com/impact27)) +- Handle all possible exceptions when trying to import the debugger [#987](https://github.com/ipython/ipykernel/pull/987) ([@JohanMabille](https://github.com/JohanMabille)) + +### Maintenance and upkeep improvements + +- [pre-commit.ci] pre-commit autoupdate [#989](https://github.com/ipython/ipykernel/pull/989) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#985](https://github.com/ipython/ipykernel/pull/985) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Add python logo in svg format [#984](https://github.com/ipython/ipykernel/pull/984) ([@steff456](https://github.com/steff456)) +- [pre-commit.ci] pre-commit autoupdate [#982](https://github.com/ipython/ipykernel/pull/982) ([@pre-commit-ci](https://github.com/pre-commit-ci)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-08-29&to=2022-09-13&type=c)) + +[@impact27](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aimpact27+updated%3A2022-08-29..2022-09-13&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2022-08-29..2022-09-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-08-29..2022-09-13&type=Issues) | [@steff456](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Asteff456+updated%3A2022-08-29..2022-09-13&type=Issues) + + + ## 6.15.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.1...724753a185b0954f0e662c226b86dc8146c62bcb)) @@ -26,8 +50,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-07-08..2022-08-29&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-07-08..2022-08-29&type=Issues) | [@rayosborn](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Arayosborn+updated%3A2022-07-08..2022-08-29&type=Issues) | [@rhelmot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Arhelmot+updated%3A2022-07-08..2022-08-29&type=Issues) - - ## 6.15.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.0...d9a8578ab2864b4ee636b12252e04a9b70047d0b)) From 0f6efaadc755708e1e30b1a90759dd2e4e3e5b14 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 15:31:02 +0000 Subject: [PATCH 0892/1195] Publish 6.15.3 SHA256 hashes: ipykernel-6.15.3-py3-none-any.whl: befe3736944b21afec8e832725e9a45f254c8bd9afc40b61d6661c97e45aff5a ipykernel-6.15.3.tar.gz: b81d57b0e171670844bf29cdc11562b1010d3da87115c4513e0ee660a8368765 --- ipykernel/_version.py | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 0421edeae..11d21c6be 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for tbump versioning -__version__ = "6.15.2" +__version__ = "6.15.3" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/pyproject.toml b/pyproject.toml index 1c6998dbe..40ae2266a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ipykernel" -version = "6.15.2" +version = "6.15.3" authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] license = {file = "COPYING.md"} readme = "README.md" @@ -61,7 +61,7 @@ test = [ skip = ["check-links"] [tool.tbump.version] -current = "6.15.2" +current = "6.15.3" regex = ''' (?P\d+)\.(?P\d+)\.(?P\d+) ((?Pa|b|rc|.dev)(?P\d+))? From e6441449d9e56211061417d4893e92565c17fa36 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 19 Sep 2022 18:42:13 -0500 Subject: [PATCH 0893/1195] [pre-commit.ci] pre-commit autoupdate (#993) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index bf0602f90..004c934cf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -66,7 +66,7 @@ repos: stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.18.2 + rev: 0.18.3 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From 51a229ff8091271dc11e87c2da2712ff5233d4b5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 19 Sep 2022 18:56:55 -0500 Subject: [PATCH 0894/1195] Remove unused manifest file (#994) --- MANIFEST.in | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 1fb3d59ee..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,29 +0,0 @@ -include *.md -include pyproject.toml -include ipykernel/py.typed - -# Documentation -graft docs -exclude docs/\#* - -# Examples -graft examples - -# docs subdirs we want to skip -prune docs/_build -prune docs/gh-pages -prune docs/dist - -# Patterns to exclude from any directory -global-exclude *~ -global-exclude *.pyc -global-exclude *.pyo -global-exclude .git -global-exclude .ipynb_checkpoints - -prune data_kernelspec -exclude .mailmap -exclude readthedocs.yml -exclude .coveragerc -exclude .pre-commit-config.yaml -exclude .git-blame-ignore-revs From b80e59e734b01987a419976267ea86b52920cd1f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 21 Sep 2022 19:55:58 -0500 Subject: [PATCH 0895/1195] Use hatch for version (#998) --- RELEASE.md | 10 +++++----- ipykernel/_version.py | 2 +- pyproject.toml | 22 ++++------------------ 3 files changed, 10 insertions(+), 24 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index 2f3954582..1253d50d9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -12,14 +12,14 @@ The recommended way to make a release is to use [`jupyter_releaser`](https://git ```bash export VERSION= -pip install jupyter_releaser -tbump --only-patch $VERSION +pip install pipx +pipx run hatch version $VERSION git commit -a -m "Release $VERSION" git tag $VERSION; true; git push --all git push --tags rm -rf dist build -python -m build . -twine check dist/* -twine upload dist/* +pipx run build . +pipx run twine check dist/* +pipx run twine upload dist/* ``` diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 11d21c6be..defb7bf26 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re from typing import List -# Version string must appear intact for tbump versioning +# Version string must appear intact for hatch versioning __version__ = "6.15.3" # Build up version_info tuple for backwards compatibility diff --git a/pyproject.toml b/pyproject.toml index 40ae2266a..b5ae8015a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "ipykernel" -version = "6.15.3" +dynamic = ["version"] authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] license = {file = "COPYING.md"} readme = "README.md" @@ -48,6 +48,9 @@ test = [ "pytest-timeout", ] +[tool.hatch.version] +path = "ipykernel/_version.py" + # Used to call hatch_build.py [tool.hatch.build.hooks.custom] @@ -60,23 +63,6 @@ test = [ [tool.jupyter-releaser] skip = ["check-links"] -[tool.tbump.version] -current = "6.15.3" -regex = ''' - (?P\d+)\.(?P\d+)\.(?P\d+) - ((?Pa|b|rc|.dev)(?P\d+))? -''' - -[tool.tbump.git] -message_template = "Bump to {new_version}" -tag_template = "v{new_version}" - -[[tool.tbump.file]] -src = "ipykernel/_version.py" - -[[tool.tbump.file]] -src = "pyproject.toml" - [tool.mypy] check_untyped_defs = true disallow_any_generics = true From 92292ad9d844e594e9c97f7f391149023e58de9e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 24 Sep 2022 20:35:12 -0500 Subject: [PATCH 0896/1195] Add client 8 support (#996) --- ipykernel/inprocess/client.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 807cad760..53db84f8b 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -16,6 +16,11 @@ from jupyter_client.client import KernelClient from jupyter_client.clientabc import KernelClientABC +try: + from jupyter_client.utils import run_sync # requires 7.0+ +except ImportError: + run_sync = None # type:ignore + # IPython imports from traitlets import Instance, Type, default @@ -179,8 +184,12 @@ def _dispatch_to_kernel(self, msg): stream = kernel.shell_stream self.session.send(stream, msg) msg_parts = stream.recv_multipart() - loop = asyncio.get_event_loop() - loop.run_until_complete(kernel.dispatch_shell(msg_parts)) + if run_sync: + dispatch_shell = run_sync(kernel.dispatch_shell) + dispatch_shell(msg_parts) + else: + loop = asyncio.get_event_loop() + loop.run_until_complete(kernel.dispatch_shell(msg_parts)) idents, reply_msg = self.session.recv(stream, copy=False) self.shell_channel.call_handlers_later(reply_msg) From ca533962c379401bddda93b664ba67a3c1004f90 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 26 Sep 2022 10:04:36 -0500 Subject: [PATCH 0897/1195] Automated Changelog Entry for 6.16.0 on main (#999) Co-authored-by: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> --- CHANGELOG.md | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 597cdf21b..5eefbfe0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ +## 6.16.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.3...92292ad9d844e594e9c97f7f391149023e58de9e)) + +### Maintenance and upkeep improvements + +- Use hatch for version [#998](https://github.com/ipython/ipykernel/pull/998) ([@blink1073](https://github.com/blink1073)) +- Add client 8 support [#996](https://github.com/ipython/ipykernel/pull/996) ([@blink1073](https://github.com/blink1073)) +- Remove unused manifest file [#994](https://github.com/ipython/ipykernel/pull/994) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-09-13&to=2022-09-26&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-09-13..2022-09-26&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-09-13..2022-09-26&type=Issues) + + + ## 6.15.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.2...861b1242a7601f1608707ed8bbfb6e801914cb4a)) @@ -24,8 +42,6 @@ [@impact27](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aimpact27+updated%3A2022-08-29..2022-09-13&type=Issues) | [@JohanMabille](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2022-08-29..2022-09-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-08-29..2022-09-13&type=Issues) | [@steff456](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Asteff456+updated%3A2022-08-29..2022-09-13&type=Issues) - - ## 6.15.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.1...724753a185b0954f0e662c226b86dc8146c62bcb)) From 9a1710add7132716660158ae9dd34fd05369f961 Mon Sep 17 00:00:00 2001 From: GitHub Action <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 15:06:58 +0000 Subject: [PATCH 0898/1195] Publish 6.16.0 SHA256 hashes: ipykernel-6.16.0-py3-none-any.whl: d3d95241cd4dd302fea9d5747b00509b58997356d1f6333c9a074c3eccb78cb3 ipykernel-6.16.0.tar.gz: 7fe42c0d58435e971dc15fd42189f20d66bf35f3056bda4f6554271bc1fa3d0d --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index defb7bf26..1614b71c8 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.15.3" +__version__ = "6.16.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 8c7c86369a73079b48731754b8de0c891253edf2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 26 Sep 2022 20:26:21 -0500 Subject: [PATCH 0899/1195] [pre-commit.ci] pre-commit autoupdate (#1000) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 004c934cf..9d893d1b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -60,7 +60,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.23.1 + rev: v8.24.0 hooks: - id: eslint stages: [manual] From 655df2e3e6e953e6c3aaa17319e79841ed28c2d6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Oct 2022 08:37:39 -0500 Subject: [PATCH 0900/1195] [pre-commit.ci] pre-commit autoupdate (#1001) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9d893d1b9..62eaaa27f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -42,7 +42,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.971 + rev: v0.981 hooks: - id: mypy exclude: "ipykernel.*tests" From 63bb11ce04d229f22afebd3d714e092b41753a36 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 4 Oct 2022 08:45:12 -0500 Subject: [PATCH 0901/1195] Ignore warnings in prereleases test (#1002) --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8464830c8..122ac7d8f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -198,7 +198,7 @@ jobs: pip check - name: Run the tests run: | - cmd="python -m pytest -vv" + cmd="python -m pytest -vv -W default" $cmd || $cmd --lf make_sdist: From 6dba150d2fbf2fdbce01db79ba9d884e8c330551 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 10 Oct 2022 19:55:51 -0500 Subject: [PATCH 0902/1195] [pre-commit.ci] pre-commit autoupdate (#1003) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/psf/black: 22.8.0 → 22.10.0](https://github.com/psf/black/compare/22.8.0...22.10.0) - [github.com/pre-commit/mirrors-prettier: v3.0.0-alpha.0 → v3.0.0-alpha.1](https://github.com/pre-commit/mirrors-prettier/compare/v3.0.0-alpha.0...v3.0.0-alpha.1) - [github.com/pre-commit/mirrors-mypy: v0.981 → v0.982](https://github.com/pre-commit/mirrors-mypy/compare/v0.981...v0.982) - [github.com/pre-commit/mirrors-eslint: v8.24.0 → v8.25.0](https://github.com/pre-commit/mirrors-eslint/compare/v8.24.0...v8.25.0) * Update .pre-commit-config.yaml * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * bump min pytest Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 18 ++++++--- CHANGELOG.md | 89 +++++++++++++++++++++-------------------- CONTRIBUTING.md | 10 +++-- COPYING.md | 6 ++- README.md | 4 +- pyproject.toml | 2 +- 6 files changed, 72 insertions(+), 57 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 62eaaa27f..ec3f569fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -17,7 +17,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 22.10.0 hooks: - id: black args: ["--line-length", "100"] @@ -29,10 +29,16 @@ repos: files: \.py$ args: [--profile=black] - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v3.0.0-alpha.0 + - repo: https://github.com/abravalheri/validate-pyproject + rev: v0.10.1 hooks: - - id: prettier + - id: validate-pyproject + stages: [manual] + + - repo: https://github.com/executablebooks/mdformat + rev: 0.7.16 + hooks: + - id: mdformat - repo: https://github.com/PyCQA/doc8 rev: v1.0.0 @@ -42,7 +48,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.981 + rev: v0.982 hooks: - id: mypy exclude: "ipykernel.*tests" @@ -60,7 +66,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.24.0 + rev: v8.25.0 hooks: - id: eslint stages: [manual] diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eefbfe0f..e6c0fa1bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,10 +31,10 @@ ### Maintenance and upkeep improvements -- [pre-commit.ci] pre-commit autoupdate [#989](https://github.com/ipython/ipykernel/pull/989) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#985](https://github.com/ipython/ipykernel/pull/985) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#989](https://github.com/ipython/ipykernel/pull/989) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#985](https://github.com/ipython/ipykernel/pull/985) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Add python logo in svg format [#984](https://github.com/ipython/ipykernel/pull/984) ([@steff456](https://github.com/steff456)) -- [pre-commit.ci] pre-commit autoupdate [#982](https://github.com/ipython/ipykernel/pull/982) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#982](https://github.com/ipython/ipykernel/pull/982) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -52,13 +52,13 @@ ### Maintenance and upkeep improvements -- [pre-commit.ci] pre-commit autoupdate [#978](https://github.com/ipython/ipykernel/pull/978) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#977](https://github.com/ipython/ipykernel/pull/977) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#976](https://github.com/ipython/ipykernel/pull/976) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#974](https://github.com/ipython/ipykernel/pull/974) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#971](https://github.com/ipython/ipykernel/pull/971) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#968](https://github.com/ipython/ipykernel/pull/968) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#966](https://github.com/ipython/ipykernel/pull/966) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#978](https://github.com/ipython/ipykernel/pull/978) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#977](https://github.com/ipython/ipykernel/pull/977) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#976](https://github.com/ipython/ipykernel/pull/976) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#974](https://github.com/ipython/ipykernel/pull/974) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#971](https://github.com/ipython/ipykernel/pull/971) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#968](https://github.com/ipython/ipykernel/pull/968) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#966](https://github.com/ipython/ipykernel/pull/966) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -76,9 +76,9 @@ ### Maintenance and upkeep improvements -- [pre-commit.ci] pre-commit autoupdate [#962](https://github.com/ipython/ipykernel/pull/962) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#961](https://github.com/ipython/ipykernel/pull/961) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#960](https://github.com/ipython/ipykernel/pull/960) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#962](https://github.com/ipython/ipykernel/pull/962) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#961](https://github.com/ipython/ipykernel/pull/961) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#960](https://github.com/ipython/ipykernel/pull/960) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -98,7 +98,7 @@ - Back to top-level tornado IOLoop [#958](https://github.com/ipython/ipykernel/pull/958) ([@minrk](https://github.com/minrk)) - Explicitly require pyzmq >= 17 [#957](https://github.com/ipython/ipykernel/pull/957) ([@minrk](https://github.com/minrk)) -- [pre-commit.ci] pre-commit autoupdate [#954](https://github.com/ipython/ipykernel/pull/954) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#954](https://github.com/ipython/ipykernel/pull/954) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -122,7 +122,7 @@ ### Maintenance and upkeep improvements - Fix sphinx 5.0 support [#951](https://github.com/ipython/ipykernel/pull/951) ([@blink1073](https://github.com/blink1073)) -- [pre-commit.ci] pre-commit autoupdate [#950](https://github.com/ipython/ipykernel/pull/950) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#950](https://github.com/ipython/ipykernel/pull/950) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -141,18 +141,18 @@ ### Maintenance and upkeep improvements -- [pre-commit.ci] pre-commit autoupdate [#945](https://github.com/ipython/ipykernel/pull/945) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#945](https://github.com/ipython/ipykernel/pull/945) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Clean up typings [#939](https://github.com/ipython/ipykernel/pull/939) ([@blink1073](https://github.com/blink1073)) -- [pre-commit.ci] pre-commit autoupdate [#938](https://github.com/ipython/ipykernel/pull/938) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#938](https://github.com/ipython/ipykernel/pull/938) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Clean up types [#933](https://github.com/ipython/ipykernel/pull/933) ([@blink1073](https://github.com/blink1073)) -- [pre-commit.ci] pre-commit autoupdate [#932](https://github.com/ipython/ipykernel/pull/932) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#932](https://github.com/ipython/ipykernel/pull/932) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Switch to hatch backend [#931](https://github.com/ipython/ipykernel/pull/931) ([@blink1073](https://github.com/blink1073)) -- [pre-commit.ci] pre-commit autoupdate [#928](https://github.com/ipython/ipykernel/pull/928) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#926](https://github.com/ipython/ipykernel/pull/926) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#928](https://github.com/ipython/ipykernel/pull/928) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#926](https://github.com/ipython/ipykernel/pull/926) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Allow enforce PR label workflow to add labels [#921](https://github.com/ipython/ipykernel/pull/921) ([@blink1073](https://github.com/blink1073)) -- [pre-commit.ci] pre-commit autoupdate [#920](https://github.com/ipython/ipykernel/pull/920) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#919](https://github.com/ipython/ipykernel/pull/919) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- [pre-commit.ci] pre-commit autoupdate [#917](https://github.com/ipython/ipykernel/pull/917) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#920](https://github.com/ipython/ipykernel/pull/920) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#919](https://github.com/ipython/ipykernel/pull/919) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#917](https://github.com/ipython/ipykernel/pull/917) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -177,7 +177,7 @@ - Add basic mypy support [#913](https://github.com/ipython/ipykernel/pull/913) ([@blink1073](https://github.com/blink1073)) - Clean up pre-commit [#911](https://github.com/ipython/ipykernel/pull/911) ([@blink1073](https://github.com/blink1073)) - Update setup.py [#909](https://github.com/ipython/ipykernel/pull/909) ([@tlinhart](https://github.com/tlinhart)) -- [pre-commit.ci] pre-commit autoupdate [#906](https://github.com/ipython/ipykernel/pull/906) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- \[pre-commit.ci\] pre-commit autoupdate [#906](https://github.com/ipython/ipykernel/pull/906) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -595,7 +595,7 @@ - Add watchfd keyword to InProcessKernel OutStream initialization [#727](https://github.com/ipython/ipykernel/pull/727) ([@rayosborn](https://github.com/rayosborn)) - Fix typo in eventloops.py [#711](https://github.com/ipython/ipykernel/pull/711) ([@selasley](https://github.com/selasley)) -- [bugfix] fix in setup.py (comma before appnope) [#709](https://github.com/ipython/ipykernel/pull/709) ([@jstriebel](https://github.com/jstriebel)) +- \[bugfix\] fix in setup.py (comma before appnope) [#709](https://github.com/ipython/ipykernel/pull/709) ([@jstriebel](https://github.com/jstriebel)) ### Maintenance and upkeep improvements @@ -790,8 +790,8 @@ following non-exhaustive changes. ### 5.4.2 -- Revert \"Fix stop_on_error_timeout blocking other messages in - queue\". [#570](https://github.com/ipython/ipykernel/pull/570) +- Revert "Fix stop_on_error_timeout blocking other messages in + queue". [#570](https://github.com/ipython/ipykernel/pull/570) ### 5.4.1 @@ -805,7 +805,7 @@ asyncio compatibility. - Add github actions, bail on asyncio patch for tornado 6.1. [#564](https://github.com/ipython/ipykernel/pull/564) - Start testing on Python 3.9. [#551](https://github.com/ipython/ipykernel/pull/551) -- Fix stack levels for ipykernel\'s deprecation warnings and stop +- Fix stack levels for ipykernel's deprecation warnings and stop using some deprecated APIs. [#547](https://github.com/ipython/ipykernel/pull/547) - Add env parameter to kernel installation [#541](https://github.com/ipython/ipykernel/pull/541) - Fix stop_on_error_timeout blocking other messages in queue. @@ -831,7 +831,7 @@ asyncio compatibility. ### 5.3.1 -- Fix \#520: run post_execute and post_run_cell on async cells +- Fix #520: run post_execute and post_run_cell on async cells [#521](https://github.com/ipython/ipykernel/pull/521) - Fix exception causes in zmqshell.py [#516](https://github.com/ipython/ipykernel/pull/516) - Make pdb on Windows interruptible [#490](https://github.com/ipython/ipykernel/pull/490) @@ -893,7 +893,7 @@ asyncio compatibility. shutdown ([#433](https://github.com/ipython/ipykernel/pull/433), [#435](https://github.com/ipython/ipykernel/pull/435)) - Fix `Heartbeat._bind_socket` to return on the first bind ([#431](https://github.com/ipython/ipykernel/pull/431)) - Moved `InProcessKernelClient.flush` to `DummySocket` ([#437](https://github.com/ipython/ipykernel/pull/437)) -- Don\'t redirect stdout if nose machinery is not present ([#427](https://github.com/ipython/ipykernel/pull/427)) +- Don't redirect stdout if nose machinery is not present ([#427](https://github.com/ipython/ipykernel/pull/427)) - Rename `_asyncio.py` to `_asyncio_utils.py` to avoid name conflicts on Python 3.6+ ([#426](https://github.com/ipython/ipykernel/pull/426)) @@ -940,8 +940,8 @@ failures in nbconvert. [5.0.0 on GitHub](https://github.com/ipython/ipykernel/milestones/5.0) -- Drop support for Python 2. `ipykernel` 5.0 requires Python \>= 3.4 -- Add support for IPython\'s asynchronous code execution +- Drop support for Python 2. `ipykernel` 5.0 requires Python >= 3.4 +- Add support for IPython's asynchronous code execution [#323](https://github.com/ipython/ipykernel/pull/323) - Update release process in `CONTRIBUTING.md` [#339](https://github.com/ipython/ipykernel/pull/339) @@ -1037,31 +1037,31 @@ failures in nbconvert. - Support new `transient` key in `display_data` messages spec for `publish`. For a display data message, `transient` contains data - that shouldn\'t be persisted to files or documents. Add a + that shouldn't be persisted to files or documents. Add a `display_id` to this `transient` dict by `display(obj, display_id=\...)` - Add `ipykernel_launcher` module which removes the current working directory from `sys.path` before launching the kernel. This helps to reduce the cases where the - kernel won\'t start because there\'s a `random.py` (or + kernel won't start because there's a `random.py` (or similar) module in the current working directory. - Add busy/idle messages on IOPub during processing of aborted requests - Add active event loop setting to GUI, which enables the correct - response to IPython\'s `is_event_loop_running_xxx` + response to IPython's `is_event_loop_running_xxx` -- Include IPython kernelspec in wheels to reduce reliance on \"native - kernel spec\" in jupyter_client +- Include IPython kernelspec in wheels to reduce reliance on "native + kernel spec" in jupyter_client - Modify `OutStream` to inherit from `TextIOBase` instead of object to improve API support and error reporting -- Fix IPython kernel death messages at start, such as \"Kernel - Restarting\...\" and \"Kernel appears to have died\", when +- Fix IPython kernel death messages at start, such as "Kernel + Restarting..." and "Kernel appears to have died", when parent-poller handles PID 1 - Various bugfixes @@ -1113,7 +1113,7 @@ failures in nbconvert. - Use [MPLBACKEND](http://matplotlib.org/devel/coding_guide.html?highlight=mplbackend#developing-a-new-backend) - environment variable to tell matplotlib \>= 1.5 use use the inline + environment variable to tell matplotlib >= 1.5 use use the inline backend by default. This is only done if MPLBACKEND is not already set and no backend has been explicitly loaded, so setting `MPLBACKEND=Qt4Agg` or calling `%matplotlib notebook` or @@ -1124,7 +1124,7 @@ failures in nbconvert. `ipython kernel install`. - Allow Comm (Widget) messages to be sent from background threads. - Select inline matplotlib backend by default if `%matplotlib` magic - or `matplotlib.use()` are not called explicitly (for matplotlib \>= + or `matplotlib.use()` are not called explicitly (for matplotlib >= 1.5). - Fix some longstanding minor deviations from the message protocol (missing status: ok in a few replies, connect_reply format). @@ -1149,9 +1149,12 @@ failures in nbconvert. - Publish all IO in a thread, via `IOPubThread`. This solves the problem of requiring `sys.stdout.flush` to be called in the notebook to produce output promptly during long-running cells. + - Remove references to outdated IPython guiref in kernel banner. + - Patch faulthandler to use `sys.__stderr__` instead of forwarded `sys.stderr`, which has no fileno when forwarded. + - Deprecate some vestiges of the Big Split: - `ipykernel.find_connection_file` @@ -1167,7 +1170,7 @@ failures in nbconvert. [4.2.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2.2) -- Don\'t show interactive debugging info when kernel crashes +- Don't show interactive debugging info when kernel crashes - Fix handling of numerical types in json_clean - Testing fixes for output capturing @@ -1175,7 +1178,7 @@ failures in nbconvert. [4.2.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2.1) -- Fix default display name back to \"Python X\" instead of \"pythonX\" +- Fix default display name back to "Python X" instead of "pythonX" ### 4.2.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5091d692d..7b5fd5195 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -25,12 +25,16 @@ As long as your code is valid, the pre-commit hook should take care of how it should look. To install `pre-commit`, run the following:: - pip install pre-commit - pre-commit install +``` +pip install pre-commit +pre-commit install +``` You can invoke the pre-commit hook by hand at any time with:: - pre-commit run +``` +pre-commit run +``` which should run any autoformatting on your code and tell you about any errors it couldn't fix automatically. diff --git a/COPYING.md b/COPYING.md index 0ccda3eb1..d7447c93b 100644 --- a/COPYING.md +++ b/COPYING.md @@ -55,5 +55,7 @@ change to one of the IPython repositories. With this in mind, the following banner should be used in any source code file to indicate the copyright and license terms: - # Copyright (c) IPython Development Team. - # Distributed under the terms of the Modified BSD License. +``` +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +``` diff --git a/README.md b/README.md index 06574280f..a6a02a812 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,8 @@ This package provides the IPython kernel for Jupyter. ## Installation from source 1. `git clone` -2. `cd ipykernel` -3. `pip install -e ".[test]"` +1. `cd ipykernel` +1. `pip install -e ".[test]"` After that, all normal `ipython` commands will use this newly-installed version of the kernel. diff --git a/pyproject.toml b/pyproject.toml index b5ae8015a..3a07b1ba3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ [project.optional-dependencies] test = [ - "pytest>=6.0", + "pytest>=7.0", "pytest-cov", "flaky", "ipyparallel", From dfdaf7cda32eb3646279e48f2d4d7c7ad88b7888 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 16 Oct 2022 21:04:36 -0500 Subject: [PATCH 0903/1195] Maintenance cleanup (#1006) --- .flake8 | 17 ---- .github/workflows/check-release.yml | 27 +----- .github/workflows/ci.yml | 130 ++++++++++------------------ .pre-commit-config.yaml | 13 ++- .readthedocs.yaml | 11 +++ docs/conf.py | 2 +- docs/requirements.txt | 2 - ipykernel/connect.py | 2 + ipykernel/iostream.py | 8 ++ ipykernel/tests/test_kernel.py | 2 - pyproject.toml | 47 +++++++++- readthedocs.yml | 4 - 12 files changed, 126 insertions(+), 139 deletions(-) delete mode 100644 .flake8 create mode 100644 .readthedocs.yaml delete mode 100644 docs/requirements.txt delete mode 100644 readthedocs.yml diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 6792947f9..000000000 --- a/.flake8 +++ /dev/null @@ -1,17 +0,0 @@ -[flake8] -ignore = E501, W503, E402 -builtins = c, get_config -exclude = - .cache, - .github, - docs, - setup.py -enable-extensions = G -extend-ignore = - G001, G002, G004, G200, G201, G202, - # black adds spaces around ':' - E203, -per-file-ignores = - # B011: Do not call assert False since python -O removes these calls - # F841 local variable 'foo' is assigned to but never used - ipykernel/tests/*: B011, F841 diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index cc11b5cee..ab757a3a4 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -11,30 +11,9 @@ concurrency: jobs: check_release: runs-on: ubuntu-latest - strategy: - matrix: - group: [check_release, link_check] - fail-fast: false steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Checkout - uses: actions/checkout@v2 - - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - - name: Install Dependencies - run: | - pip install -e . - - - name: Check Release - if: ${{ matrix.group == 'check_release' }} - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v1 + - uses: actions/checkout@v2 + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 with: token: ${{ secrets.GITHUB_TOKEN }} - - - name: Run Link Check - if: ${{ matrix.group == 'link_check' }} - uses: jupyter-server/jupyter_releaser/.github/actions/check-links@v1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 122ac7d8f..f67948158 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ concurrency: defaults: run: - shell: bash + shell: bash -eux {0} jobs: build: @@ -26,6 +26,8 @@ jobs: python-version: "3.9" - os: ubuntu-latest python-version: "pypy-3.7" + - os: ubuntu-latest + python-version: "3.11-dev" - os: macos-latest python-version: "3.8" steps: @@ -63,18 +65,18 @@ jobs: timeout-minutes: 15 if: ${{ !startsWith( matrix.python-version, 'pypy' ) && !startsWith(matrix.os, 'windows') }} run: | - cmd="python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered" - $cmd || $cmd --lf + hatch run cov:test || hatch run test:test --lf - name: Run the tests on pypy and windows timeout-minutes: 15 if: ${{ startsWith( matrix.python-version, 'pypy' ) || startsWith(matrix.os, 'windows') }} run: | - cmd="python -m pytest -vv" - $cmd || $cmd --lf + pip install -e ".[test]" + pytest -vv || pytest -vv --lf - name: Coverage run: | + pip install codecov codecov - name: Check Launcher @@ -82,57 +84,19 @@ jobs: cd $HOME python -m ipykernel_launcher --help - pre-commit: - name: pre-commit + pre_commit: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - uses: pre-commit/action@v2.0.0 - with: - extra_args: --all-files --hook-stage=manual - - name: Help message if pre-commit fail - if: ${{ failure() }} - run: | - echo "You can install pre-commit hooks to automatically run formatting" - echo "on each commit with:" - echo " pre-commit install" - echo "or you can run by hand on staged files with" - echo " pre-commit run" - echo "or after-the-fact on already committed files with" - echo " pre-commit run --all-files --hook-stage=manual" + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: jupyterlab/maintainer-tools/.github/actions/pre-commit@v1 test_docs: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest] - python-version: ["3.9"] - exclude: - - os: windows-latest - python-version: pypy-3.7 + runs-on: ubuntu-latest steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - - name: Build the docs - run: | - cd docs - pip install -r requirements.txt - make html SPHINXOPTS="-W" - - - name: Install the Python dependencies - run: | - pip install . - pip install velin - - - name: Check Docstrings - run: | - velin . --check --compact + - uses: actions/checkout@v2 + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - run: hatch run docs:build test_without_debugpy: runs-on: ${{ matrix.os }} @@ -159,9 +123,7 @@ jobs: - name: Run the tests timeout-minutes: 10 - run: | - cmd="python -m pytest -vv" - $cmd || $cmd --lf + run: hatch run test:test test_miniumum_versions: name: Test Minimum Versions @@ -177,8 +139,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1 - name: Run the unit tests run: | - cmd="python -m pytest -vv -W default" - $cmd || $cmd --lf + pytest -vv -W default || pytest -vv -W default --lf test_prereleases: name: Test Prereleases @@ -198,8 +159,7 @@ jobs: pip check - name: Run the tests run: | - cmd="python -m pytest -vv -W default" - $cmd || $cmd --lf + pytest -vv -W default || pytest -vv -W default --lf make_sdist: name: Make SDist @@ -207,16 +167,8 @@ jobs: timeout-minutes: 20 steps: - uses: actions/checkout@v2 - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Build SDist - run: | - pip install build - python -m build --sdist - - uses: actions/upload-artifact@v2 - with: - name: "sdist" - path: dist/*.tar.gz + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1 test_sdist: runs-on: ubuntu-latest @@ -224,20 +176,30 @@ jobs: name: Install from SDist and Test timeout-minutes: 20 steps: - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Download sdist - uses: actions/download-artifact@v2 - - name: Install From SDist - run: | - set -ex - cd sdist - mkdir test - tar --strip-components=1 -zxvf *.tar.gz -C ./test - cd test - pip install .[test] - - name: Run Test - run: | - cd sdist/test - cmd="python -m pytest -vv" - $cmd || $cmd --lf + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: jupyterlab/maintainer-tools/.github/actions/test-sdist@v1 + + link_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 + + tests_check: # This job does nothing and is only used for the branch protection + if: always() + needs: + - build + - test_docs + - test_without_debugpy + - test_miniumum_versions + - pre_commit + - test_prereleases + - link_check + - test_sdist + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ec3f569fa..dad6d3469 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,6 +22,12 @@ repos: - id: black args: ["--line-length", "100"] + - repo: https://github.com/Carreau/velin + rev: 0.0.12 + hooks: + - id: velin + args: ["ipykernel"] + - repo: https://github.com/PyCQA/isort rev: 5.10.1 hooks: @@ -57,10 +63,11 @@ repos: [tornado, jupyter_client, pytest, traitlets, jupyter_core] stages: [manual] - - repo: https://github.com/pycqa/flake8 - rev: 5.0.4 + - repo: https://github.com/john-hen/Flake8-pyproject + rev: 1.0.1 hooks: - - id: flake8 + - id: Flake8-pyproject + alias: flake8 additional_dependencies: ["flake8-bugbear==22.6.22", "flake8-implicit-str-concat==0.2.0"] stages: [manual] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 000000000..fed614d54 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,11 @@ +version: 2 +sphinx: + configuration: docs/conf.py +python: + version: 3.8 + install: + # install itself with pip install . + - method: pip + path: . + extra_requirements: + - docs diff --git a/docs/conf.py b/docs/conf.py index 75d9fa784..4a1a6fa5f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -122,7 +122,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -# html_theme = 'alabaster' +html_theme = "pydata_sphinx_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the diff --git a/docs/requirements.txt b/docs/requirements.txt deleted file mode 100644 index b3608d506..000000000 --- a/docs/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -myst_parser -sphinxcontrib_github_alt diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 6f1287708..6baae872a 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -57,6 +57,7 @@ def get_connection_info(connection_file=None, unpack=False): If unspecified, the connection file for the currently running IPython Kernel will be used, which is only allowed from inside a kernel. + unpack : bool [default: False] if True, return the unpacked dict, otherwise just the string contents of the file. @@ -95,6 +96,7 @@ def connect_qtconsole(connection_file=None, argv=None): If unspecified, the connection file for the currently running IPython Kernel will be used, which is only allowed from inside a kernel. + argv : list [optional] Any extra args to be passed to the console. diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index aeaf38e02..e6a03d1ef 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -346,8 +346,16 @@ def __init__( """ Parameters ---------- + session : object + the session object + pub_thread : threading.Thread + the publication thread name : str {'stderr', 'stdout'} the name of the standard stream to replace + pipe : object + the pip object + echo : bool + whether to echo output watchfd : bool (default, True) Watch the file descripttor corresponding to the replaced stream. This is useful if you know some underlying code will write directly diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 40b9471eb..9a53ad640 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -72,7 +72,6 @@ def test_capture_fd(): @pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing") def test_subprocess_peek_at_stream_fileno(): - """""" with kernel() as kc: iopub = kc.iopub_channel msg_id, content = execute( @@ -417,7 +416,6 @@ def test_interrupt_during_input(): @pytest.mark.skipif(os.name == "nt", reason="Message based interrupt not supported on Windows") def test_interrupt_with_message(): - """ """ with new_kernel() as kc: km = kc.parent km.kernel_spec.interrupt_mode = "message" diff --git a/pyproject.toml b/pyproject.toml index 3a07b1ba3..1ebcce91f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,12 @@ dependencies = [ ] [project.optional-dependencies] +docs = [ + "sphinx", + "myst_parser", + "pydata_sphinx_theme", + "sphinxcontrib_github_alt" +] test = [ "pytest>=7.0", "pytest-cov", @@ -60,8 +66,24 @@ path = "ipykernel/_version.py" [tool.hatch.build.force-include] "./ipykernel_launcher.py" = "ipykernel_launcher.py" -[tool.jupyter-releaser] -skip = ["check-links"] +[tool.hatch.envs.docs] +features = ["docs"] +[tool.hatch.envs.docs.scripts] +build = "make -C docs html SPHINXOPTS='-W'" + +[tool.hatch.envs.test] +features = ["test"] +[tool.hatch.envs.test.scripts] +test = "python -m pytest -vv {args}" +nowarn = "python -m pytest -vv -W default {args}" + +[tool.hatch.envs.cov] +features = ["test"] +dependencies = ["coverage", "pytest-cov"] +[tool.hatch.envs.cov.env-vars] +ARGS = "-vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered" +[tool.hatch.envs.cov.scripts] +test = "python -m pytest $ARGS --cov-fail-under 50 {args}" [tool.mypy] check_untyped_defs = true @@ -103,3 +125,24 @@ filterwarnings= [ "ignore:unclosed event loop:ResourceWarning", "ignore:There is no current event loop:DeprecationWarning" ] + +[tool.flake8] +ignore = "E501, W503, E402" +builtins = "c, get_config" +exclude = [ + ".cache", + ".github", + "docs", + "setup.py", +] +enable-extensions = "G" +extend-ignore = [ + "G001", "G002", "G004", "G200", "G201", "G202", + # black adds spaces around ':' + "E203", +] +per-file-ignores = [ + # B011: Do not call assert False since python -O removes these calls + # F841 local variable 'foo' is assigned to but never used + "ipykernel/tests/*: B011", "F841", +] diff --git a/readthedocs.yml b/readthedocs.yml deleted file mode 100644 index 2b684d138..000000000 --- a/readthedocs.yml +++ /dev/null @@ -1,4 +0,0 @@ -python: - version: 3.8 - pip_install: true -requirements_file: docs/requirements.txt From a327bcdd24b4d87c4f16c3fce4b119ea6e404b7a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 19:38:32 -0500 Subject: [PATCH 0904/1195] [pre-commit.ci] pre-commit autoupdate (#1007) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dad6d3469..f8ffdd891 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -64,7 +64,7 @@ repos: stages: [manual] - repo: https://github.com/john-hen/Flake8-pyproject - rev: 1.0.1 + rev: 1.1.0.post0 hooks: - id: Flake8-pyproject alias: flake8 From 632a1ba3892bed707e1ee19fe1344e92475e19c9 Mon Sep 17 00:00:00 2001 From: Quentin Peter Date: Thu, 20 Oct 2022 12:13:20 +0200 Subject: [PATCH 0905/1195] PR: Destroy tk app to avoid memory leak (#1008) Co-authored-by: Quentin Peter Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/eventloops.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index cb693e8f8..106381c19 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -247,6 +247,8 @@ def process_stream_events(stream, *a, **kw): if stream.flush(limit=1): app.tk.deletefilehandler(stream.getsockopt(zmq.FD)) app.quit() + app.destroy() + del kernel.app_wrapper # For Tkinter, we create a Tk object and call its withdraw method. kernel.app_wrapper = BasicAppWrapper(app) @@ -297,7 +299,8 @@ def start(self): def loop_tk_exit(kernel): try: kernel.app_wrapper.app.destroy() - except RuntimeError: + del kernel.app_wrapper + except (RuntimeError, AttributeError): pass From f78ebe82901dd9b1744cb52694dce135b6314baa Mon Sep 17 00:00:00 2001 From: blink1073 Date: Thu, 20 Oct 2022 10:17:49 +0000 Subject: [PATCH 0906/1195] Publish 6.16.1 SHA256 hashes: ipykernel-6.16.1-py3-none-any.whl: 32eb7bdc5af57185e9a42b0dcef66413ef91a0490b378eae46cbdf0d4e0b5912 ipykernel-6.16.1.tar.gz: 3a27a550c1d682e7825f0f7732b0142b79ef1b21cd2e713cacac0c9847535f13 --- CHANGELOG.md | 23 +++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6c0fa1bd..b7f1675e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ +## 6.16.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.16.0...632a1ba3892bed707e1ee19fe1344e92475e19c9)) + +### Bugs fixed + +- PR: Destroy tk app to avoid memory leak [#1008](https://github.com/ipython/ipykernel/pull/1008) ([@impact27](https://github.com/impact27)) + +### Maintenance and upkeep improvements + +- Maintenance cleanup [#1006](https://github.com/ipython/ipykernel/pull/1006) ([@blink1073](https://github.com/blink1073)) +- Ignore warnings in prereleases test [#1002](https://github.com/ipython/ipykernel/pull/1002) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-09-26&to=2022-10-20&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-09-26..2022-10-20&type=Issues) | [@impact27](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aimpact27+updated%3A2022-09-26..2022-10-20&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-09-26..2022-10-20&type=Issues) + + + ## 6.16.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.3...92292ad9d844e594e9c97f7f391149023e58de9e)) @@ -18,8 +39,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-09-13..2022-09-26&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-09-13..2022-09-26&type=Issues) - - ## 6.15.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.2...861b1242a7601f1608707ed8bbfb6e801914cb4a)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 1614b71c8..c5e9301da 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.16.0" +__version__ = "6.16.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 03973be58e0f0a88c1699957e4ae813cd31ba94d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Oct 2022 19:49:39 -0500 Subject: [PATCH 0907/1195] [pre-commit.ci] pre-commit autoupdate (#1009) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f8ffdd891..17ebae23c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -73,13 +73,13 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.25.0 + rev: v8.26.0 hooks: - id: eslint stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.18.3 + rev: 0.18.4 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From 99706182995e0fd5431965d4c9d96a8ce7afae12 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 25 Oct 2022 09:07:24 -0500 Subject: [PATCH 0908/1195] Fix failing test and update matrix (#1010) --- .github/workflows/ci.yml | 9 ++++++--- ipykernel/tests/test_message_spec.py | 4 +--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f67948158..1c27c1f51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,15 +20,18 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.7", "3.10"] + python-version: ["3.7", "3.11"] + exclude: + - os: macos-latest + python-version: "3.11" # not yet available include: - os: windows-latest python-version: "3.9" - os: ubuntu-latest python-version: "pypy-3.7" - - os: ubuntu-latest - python-version: "3.11-dev" - os: macos-latest + python-version: "3.10" + - os: ubuntu-latest python-version: "3.8" steps: - name: Checkout diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index c2195c099..1833f7ddc 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -509,9 +509,7 @@ def test_connect_request(): flush_channels() msg = KC.session.msg("connect_request") KC.shell_channel.send(msg) - return msg["header"]["msg_id"] - - msg_id = KC.kernel_info() + msg_id = msg["header"]["msg_id"] reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, "connect_reply", msg_id) From 7f73ff705510b35d1e2faad7f5a676c620ce08d4 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 25 Oct 2022 14:12:37 +0000 Subject: [PATCH 0909/1195] Publish 6.16.2 SHA256 hashes: ipykernel-6.16.2-py3-none-any.whl: 67daf93e5b52456cd8eea87a8b59405d2bb80ae411864a1ea206c3631d8179af ipykernel-6.16.2.tar.gz: 463f3d87a92e99969b1605cb7a5b4d7b36b7145a0e72d06e65918a6ddefbe630 --- CHANGELOG.md | 18 ++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f1675e7..027de670a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.16.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.16.1...99706182995e0fd5431965d4c9d96a8ce7afae12)) + +### Maintenance and upkeep improvements + +- Fix failing test and update matrix [#1010](https://github.com/ipython/ipykernel/pull/1010) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-10-20&to=2022-10-25&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-10-20..2022-10-25&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-10-20..2022-10-25&type=Issues) + + + ## 6.16.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.16.0...632a1ba3892bed707e1ee19fe1344e92475e19c9)) @@ -21,8 +37,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-09-26..2022-10-20&type=Issues) | [@impact27](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aimpact27+updated%3A2022-09-26..2022-10-20&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-09-26..2022-10-20&type=Issues) - - ## 6.16.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.15.3...92292ad9d844e594e9c97f7f391149023e58de9e)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index c5e9301da..7242c7891 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.16.1" +__version__ = "6.16.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 2bd532148e6f699147b1559c4b1c23cc64275415 Mon Sep 17 00:00:00 2001 From: zhizheng1 <114848324+zhizheng1@users.noreply.github.com> Date: Mon, 31 Oct 2022 18:42:38 +0800 Subject: [PATCH 0910/1195] Enable webagg in %matplotlib (#1012) --- ipykernel/eventloops.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 106381c19..b9174093a 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -60,6 +60,7 @@ def process_stream_events(): loop_map = { "inline": None, "nbagg": None, + "webagg": None, "notebook": None, "ipympl": None, "widget": None, From db00586a25a4f047a90386f4947e60ff1dbee2b6 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 31 Oct 2022 09:28:11 -0500 Subject: [PATCH 0911/1195] Update supported pythons to 3.8-3.11 (#1013) --- .github/workflows/ci.yml | 9 +++------ pyproject.toml | 4 ++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c27c1f51..e34204ab8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,15 +20,12 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.7", "3.11"] - exclude: - - os: macos-latest - python-version: "3.11" # not yet available + python-version: ["3.8", "3.11"] include: - os: windows-latest python-version: "3.9" - os: ubuntu-latest - python-version: "pypy-3.7" + python-version: "pypy-3.8" - os: macos-latest python-version: "3.10" - os: ubuntu-latest @@ -137,7 +134,7 @@ jobs: - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: - python_version: "3.7" + python_version: "3.8" - name: Install miniumum versions uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1 - name: Run the unit tests diff --git a/pyproject.toml b/pyproject.toml index 1ebcce91f..bb6ab4231 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,13 +17,13 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", ] urls = {Homepage = "https://ipython.org"} -requires-python = ">=3.7" +requires-python = ">=3.8" dependencies = [ "debugpy>=1.0", "ipython>=7.23.1", From 99a1becaa958b33d80fe337fdbc41305030fdb6d Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 31 Oct 2022 14:33:40 +0000 Subject: [PATCH 0912/1195] Publish 6.17.0 SHA256 hashes: ipykernel-6.17.0-py3-none-any.whl: 301fdb487587c9bf277025001da97b53697aab73ae1268d9d1ba972a2c5fc801 ipykernel-6.17.0.tar.gz: e195cf6d8c3dd5d41f3cf8ad831d9891f95d7d18fa6d5fb4d30a713df99b26a4 --- CHANGELOG.md | 22 ++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 027de670a..19f8f089c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ +## 6.17.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.16.2...db00586a25a4f047a90386f4947e60ff1dbee2b6)) + +### Enhancements made + +- Enable webagg in %matplotlib [#1012](https://github.com/ipython/ipykernel/pull/1012) ([@zhizheng1](https://github.com/zhizheng1)) + +### Maintenance and upkeep improvements + +- Update supported pythons to 3.8-3.11 [#1013](https://github.com/ipython/ipykernel/pull/1013) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-10-25&to=2022-10-31&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-10-25..2022-10-31&type=Issues) | [@zhizheng1](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Azhizheng1+updated%3A2022-10-25..2022-10-31&type=Issues) + + + ## 6.16.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.16.1...99706182995e0fd5431965d4c9d96a8ce7afae12)) @@ -16,8 +36,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-10-20..2022-10-25&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-10-20..2022-10-25&type=Issues) - - ## 6.16.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.16.0...632a1ba3892bed707e1ee19fe1344e92475e19c9)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7242c7891..2ebfdfbb6 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.16.2" +__version__ = "6.17.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 1860282cf022075d3649ff8ae9a0b3f33c518ef1 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 31 Oct 2022 17:26:41 -0500 Subject: [PATCH 0913/1195] Add pyupgrade to pre-commit (#1014) --- .pre-commit-config.yaml | 8 ++++++++ examples/embedding/internal_ipkernel.py | 2 +- ipykernel/eventloops.py | 2 +- ipykernel/heartbeat.py | 4 ++-- ipykernel/ipkernel.py | 2 +- ipykernel/kernelapp.py | 2 +- ipykernel/kernelbase.py | 2 +- ipykernel/kernelspec.py | 2 +- ipykernel/tests/test_kernel.py | 2 +- ipykernel/tests/test_message_spec.py | 4 ++-- ipykernel/zmqshell.py | 2 +- 11 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 17ebae23c..1253ed649 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,6 +45,14 @@ repos: rev: 0.7.16 hooks: - id: mdformat + additional_dependencies: + [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] + + - repo: https://github.com/asottile/pyupgrade + rev: v3.1.0 + hooks: + - id: pyupgrade + args: [--py38-plus] - repo: https://github.com/PyCQA/doc8 rev: v1.0.0 diff --git a/examples/embedding/internal_ipkernel.py b/examples/embedding/internal_ipkernel.py index 46e97a5e6..7d126c17f 100644 --- a/examples/embedding/internal_ipkernel.py +++ b/examples/embedding/internal_ipkernel.py @@ -44,7 +44,7 @@ def print_namespace(self, evt=None): print("\n***Variables in User namespace***") for k, v in self.namespace.items(): if not k.startswith("_"): - print("%s -> %r" % (k, v)) + print(f"{k} -> {v!r}") sys.stdout.flush() def new_qt_console(self, evt=None): diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index b9174093a..0329926a3 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -447,7 +447,7 @@ def close_loop(): def enable_gui(gui, kernel=None): """Enable integration with a given GUI""" if gui not in loop_map: - e = "Invalid GUI request %r, valid ones are:%s" % (gui, loop_map.keys()) + e = f"Invalid GUI request {gui!r}, valid ones are:{loop_map.keys()}" raise ValueError(e) if kernel is None: if Application.initialized(): diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 6f3bd9092..091cdd372 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -52,7 +52,7 @@ def pick_port(self): s.close() elif self.transport == "ipc": self.port = 1 - while os.path.exists("%s-%s" % (self.ip, self.port)): + while os.path.exists(f"{self.ip}-{self.port}"): self.port = self.port + 1 else: raise ValueError("Unrecognized zmq transport: %s" % self.transport) @@ -60,7 +60,7 @@ def pick_port(self): def _try_bind_socket(self): c = ":" if self.transport == "tcp" else "-" - return self.socket.bind("%s://%s" % (self.transport, self.ip) + c + str(self.port)) + return self.socket.bind(f"{self.transport}://{self.ip}" + c + str(self.port)) def _bind_socket(self): try: diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index c4ef68d3d..3dffaaff4 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -600,7 +600,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): ns = {fname: f, argname: args, kwargname: kwargs, resultname: None} # print ns working.update(ns) - code = "%s = %s(*%s,**%s)" % (resultname, fname, argname, kwargname) + code = f"{resultname} = {fname}(*{argname},**{kwargname})" try: exec(code, shell.user_global_ns, shell.user_ns) result = working.get(resultname) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 8e6084671..50a56febd 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -212,7 +212,7 @@ def init_poller(self): self.poller = ParentPollerUnix() def _try_bind_socket(self, s, port): - iface = "%s://%s" % (self.transport, self.ip) + iface = f"{self.transport}://{self.ip}" if self.transport == "tcp": if port <= 0: port = s.bind_to_random_port(iface) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e1cc99d71..ca857eab3 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1083,7 +1083,7 @@ def _topic(self, topic): """prefixed topic for IOPub messages""" base = "kernel.%s" % self.ident - return ("%s.%s" % (base, topic)).encode() + return (f"{base}.{topic}").encode() _aborting = Bool(False) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 01d4b43bf..64bc0acae 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -242,7 +242,7 @@ def start(self): print("Perhaps you want `sudo` or `--user`?", file=sys.stderr) self.exit(1) raise - print("Installed kernelspec %s in %s" % (opts.name, dest)) + print(f"Installed kernelspec {opts.name} in {dest}") if __name__ == "__main__": diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 9a53ad640..ad38f8797 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -203,7 +203,7 @@ def test_raw_input(): input_f = "input" theprompt = "prompt> " - code = 'print({input_f}("{theprompt}"))'.format(**locals()) + code = f'print({input_f}("{theprompt}"))' msg_id = kc.execute(code, allow_stdin=True) msg = kc.get_stdin_msg(timeout=TIMEOUT) assert msg["header"]["msg_type"] == "input_request" diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 1833f7ddc..9ab41ae0f 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -74,9 +74,9 @@ def __init__(self, *args, **kwargs): def validate(self, obj, value): if self.min and V(value) < V(self.min): - raise TraitError("bad version: %s < %s" % (value, self.min)) + raise TraitError(f"bad version: {value} < {self.min}") if self.max and (V(value) > V(self.max)): - raise TraitError("bad version: %s > %s" % (value, self.max)) + raise TraitError(f"bad version: {value} > {self.max}") class RMessage(Reference): diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 45ac57507..1aaa591a4 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -627,7 +627,7 @@ def system_piped(self, cmd): with AvoidUNCPath() as path: if path is not None: - cmd = "pushd %s &&%s" % (path, cmd) + cmd = f"pushd {path} &&{cmd}" self.user_ns["_exit_code"] = system(cmd) else: self.user_ns["_exit_code"] = system(self.var_expand(cmd, depth=1)) From 19004f7be9f8f6b64f3b266d2734fb901b84b509 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Nov 2022 08:36:34 +0100 Subject: [PATCH 0914/1195] [pre-commit.ci] pre-commit autoupdate (#1015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/asottile/pyupgrade: v3.1.0 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.2.0) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1253ed649..b99200909 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,7 +49,7 @@ repos: [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - repo: https://github.com/asottile/pyupgrade - rev: v3.1.0 + rev: v3.2.0 hooks: - id: pyupgrade args: [--py38-plus] From 93ec4679e174bf9921ffebf739651e3990609d8c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 7 Nov 2022 18:49:27 -0600 Subject: [PATCH 0915/1195] [pre-commit.ci] pre-commit autoupdate (#1016) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b99200909..5d13656c8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -81,7 +81,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.26.0 + rev: v8.27.0 hooks: - id: eslint stages: [manual] From ba474bf2f8815bfd0dcf6bb6144f0d349f198f2c Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 8 Nov 2022 08:24:40 -0600 Subject: [PATCH 0916/1195] Add dependabot (#1017) --- .github/dependabot.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..f10a5bc1f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,8 @@ +version: 2 +updates: + # Set update schedule for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + # Check for updates to GitHub Actions every weekday + interval: "weekly" From 38ced9dd90b06fdbd3e6ed831e8ba12421d5f1ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Nov 2022 08:57:59 -0600 Subject: [PATCH 0917/1195] Bump actions/checkout from 2 to 3 (#1018) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/check-release.yml | 2 +- .github/workflows/ci.yml | 16 ++++++++-------- .github/workflows/downstream.yml | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml index ab757a3a4..1dac3403b 100644 --- a/.github/workflows/check-release.yml +++ b/.github/workflows/check-release.yml @@ -12,7 +12,7 @@ jobs: check_release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e34204ab8..14da9db63 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: python-version: "3.8" steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -87,14 +87,14 @@ jobs: pre_commit: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/pre-commit@v1 test_docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - run: hatch run docs:build @@ -107,7 +107,7 @@ jobs: python-version: ["3.9"] steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -130,7 +130,7 @@ jobs: timeout-minutes: 20 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: @@ -147,7 +147,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Install the Python dependencies @@ -166,7 +166,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1 @@ -182,7 +182,7 @@ jobs: link_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 061299943..0d314fa44 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 From a06867786eaf0c5d9454d2df61f354c7012a625e Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Wed, 9 Nov 2022 08:18:13 -0700 Subject: [PATCH 0918/1195] Ignore the new Jupyter_core deprecation warning in CI (#1019) --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bb6ab4231..5aed204bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -123,7 +123,8 @@ filterwarnings= [ # Ignore jupyter_client warnings "ignore:unclosed Date: Wed, 9 Nov 2022 15:38:52 +0000 Subject: [PATCH 0919/1195] Publish 6.17.1 SHA256 hashes: ipykernel-6.17.1-py3-none-any.whl: 3a9a1b2ad6dbbd5879855aabb4557f08e63fa2208bffed897f03070e2bb436f6 ipykernel-6.17.1.tar.gz: e178c1788399f93a459c241fe07c3b810771c607b1fb064a99d2c5d40c90c5d4 --- CHANGELOG.md | 21 +++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19f8f089c..5a05aff15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ +## 6.17.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.17.0...a06867786eaf0c5d9454d2df61f354c7012a625e)) + +### Maintenance and upkeep improvements + +- Ignore the new Jupyter_core deprecation warning in CI [#1019](https://github.com/ipython/ipykernel/pull/1019) ([@jasongrout](https://github.com/jasongrout)) +- Bump actions/checkout from 2 to 3 [#1018](https://github.com/ipython/ipykernel/pull/1018) ([@dependabot](https://github.com/dependabot)) +- Add dependabot [#1017](https://github.com/ipython/ipykernel/pull/1017) ([@blink1073](https://github.com/blink1073)) +- Add pyupgrade to pre-commit [#1014](https://github.com/ipython/ipykernel/pull/1014) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-10-31&to=2022-11-09&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-10-31..2022-11-09&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2022-10-31..2022-11-09&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2022-10-31..2022-11-09&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-10-31..2022-11-09&type=Issues) + + + ## 6.17.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.16.2...db00586a25a4f047a90386f4947e60ff1dbee2b6)) @@ -20,8 +39,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-10-25..2022-10-31&type=Issues) | [@zhizheng1](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Azhizheng1+updated%3A2022-10-25..2022-10-31&type=Issues) - - ## 6.16.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.16.1...99706182995e0fd5431965d4c9d96a8ce7afae12)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 2ebfdfbb6..314c9c554 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.17.0" +__version__ = "6.17.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 73b1e6a87d2c705a5b81ffe5a9050ebad66677f6 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 11 Nov 2022 09:24:46 -0600 Subject: [PATCH 0920/1195] Allow releasing from repo (#1020) --- .github/workflows/prep-release.yml | 42 +++++++++++++++++++++ .github/workflows/publish-release.yml | 54 +++++++++++++++++++++++++++ RELEASE.md | 2 +- 3 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/prep-release.yml create mode 100644 .github/workflows/publish-release.yml diff --git a/.github/workflows/prep-release.yml b/.github/workflows/prep-release.yml new file mode 100644 index 000000000..7a2a18de7 --- /dev/null +++ b/.github/workflows/prep-release.yml @@ -0,0 +1,42 @@ +name: "Step 1: Prep Release" +on: + workflow_dispatch: + inputs: + version_spec: + description: "New Version Specifier" + default: "next" + required: false + branch: + description: "The branch to target" + required: false + post_version_spec: + description: "Post Version Specifier" + required: false + since: + description: "Use PRs with activity since this date or git reference" + required: false + since_last_stable: + description: "Use PRs with activity since the last stable git tag" + required: false + type: boolean +jobs: + prep_release: + runs-on: ubuntu-latest + steps: + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Prep Release + id: prep-release + uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 + with: + token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + version_spec: ${{ github.event.inputs.version_spec }} + post_version_spec: ${{ github.event.inputs.post_version_spec }} + target: ${{ github.event.inputs.target }} + branch: ${{ github.event.inputs.branch }} + since: ${{ github.event.inputs.since }} + since_last_stable: ${{ github.event.inputs.since_last_stable }} + + - name: "** Next Step **" + run: | + echo "Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}" diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 000000000..dbaaeaad2 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,54 @@ +name: "Step 2: Publish Release" +on: + workflow_dispatch: + inputs: + branch: + description: "The target branch" + required: false + release_url: + description: "The URL of the draft GitHub release" + required: false + steps_to_skip: + description: "Comma separated list of steps to skip" + required: false + +jobs: + publish_release: + runs-on: ubuntu-latest + steps: + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Populate Release + id: populate-release + uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 + with: + token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + target: ${{ github.event.inputs.target }} + branch: ${{ github.event.inputs.branch }} + release_url: ${{ github.event.inputs.release_url }} + steps_to_skip: ${{ github.event.inputs.steps_to_skip }} + + - name: Finalize Release + id: finalize-release + env: + PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} + PYPI_TOKEN_MAP: ${{ secrets.PYPI_TOKEN_MAP }} + TWINE_USERNAME: __token__ + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + uses: jupyter-server/jupyter-releaser/.github/actions/finalize-release@v2 + with: + token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + target: ${{ github.event.inputs.target }} + release_url: ${{ steps.populate-release.outputs.release_url }} + + - name: "** Next Step **" + if: ${{ success() }} + run: | + echo "Verify the final release" + echo ${{ steps.finalize-release.outputs.release_url }} + + - name: "** Failure Message **" + if: ${{ failure() }} + run: | + echo "Failed to Publish the Draft Release Url:" + echo ${{ steps.populate-release.outputs.release_url }} diff --git a/RELEASE.md b/RELEASE.md index 1253d50d9..3370c6c36 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -2,7 +2,7 @@ ## Using `jupyter_releaser` -The recommended way to make a release is to use [`jupyter_releaser`](https://github.com/jupyter-server/jupyter_releaser#checklist-for-adoption). +The recommended way to make a release is to use [`jupyter_releaser`](https://jupyter-releaser.readthedocs.io/en/latest/get_started/making_release_from_repo.html). ## Manual Release From 85d0a3d3ac6b0d16d77388a5c33c3c51ddae6f82 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 14 Nov 2022 19:09:18 -0600 Subject: [PATCH 0921/1195] [pre-commit.ci] pre-commit autoupdate (#1022) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 6 +++--- ipykernel/inprocess/client.py | 2 +- ipykernel/ipkernel.py | 2 +- ipykernel/kernelapp.py | 2 +- ipykernel/kernelbase.py | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5d13656c8..2781ddda4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,7 +49,7 @@ repos: [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.0 + rev: v3.2.2 hooks: - id: pyupgrade args: [--py38-plus] @@ -62,7 +62,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.982 + rev: v0.990 hooks: - id: mypy exclude: "ipykernel.*tests" @@ -87,7 +87,7 @@ repos: stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.18.4 + rev: 0.19.1 hooks: - id: check-jsonschema name: "Check GitHub Workflows" diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 53db84f8b..d6bfc9a5f 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -184,7 +184,7 @@ def _dispatch_to_kernel(self, msg): stream = kernel.shell_stream self.session.send(stream, msg) msg_parts = stream.recv_multipart() - if run_sync: + if run_sync is not None: dispatch_shell = run_sync(kernel.dispatch_shell) dispatch_shell(msg_parts) else: diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 3dffaaff4..c9d86b4c7 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -243,7 +243,7 @@ def _restore_input(self): getpass.getpass = self._save_getpass - @property # type:ignore[override] + @property def execution_count(self): return self.shell.execution_count diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 50a56febd..bb96c1b35 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -465,7 +465,7 @@ def init_io(self): handler.stream = TextIOWrapper( FileIO( - sys.stderr._original_stdstream_copy, # type:ignore[attr-defined] + sys.stderr._original_stdstream_copy, "w", ) ) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index ca857eab3..b21991b02 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -872,7 +872,7 @@ async def comm_info_request(self, stream, ident, parent): if hasattr(self, "comm_manager"): comms = { k: dict(target_name=v.target_name) - for (k, v) in self.comm_manager.comms.items() # type:ignore[attr-defined] + for (k, v) in self.comm_manager.comms.items() if v.target_name == target_name or target_name is None } else: From dbea8f230adfde72b818da86761e6290bd1232d6 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 15 Nov 2022 05:07:33 -0600 Subject: [PATCH 0922/1195] Increase coverage (#1021) * increase coverage * add codecov file * add tk guard * add another guard * skip test on windows * skip more tests on windows * more skips --- codecov.yml | 9 +++ ipykernel/kernelbase.py | 12 ++- ipykernel/tests/conftest.py | 103 ++++++++++++++++++++++++++ ipykernel/tests/test_eventloop.py | 45 +++++++++++ ipykernel/tests/test_kernel_direct.py | 78 +++++++++++++++++++ pyproject.toml | 18 ++++- 6 files changed, 257 insertions(+), 8 deletions(-) create mode 100644 codecov.yml create mode 100644 ipykernel/tests/test_kernel_direct.py diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 000000000..b75c3e2db --- /dev/null +++ b/codecov.yml @@ -0,0 +1,9 @@ +coverage: + status: + project: + default: + target: auto + threshold: 1 + patch: + default: + target: 0% diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index b21991b02..e5032c738 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -102,7 +102,7 @@ def _update_eventloop(self, change): banner: str @default("shell_streams") - def _shell_streams_default(self): + def _shell_streams_default(self): # pragma: no cover warnings.warn( "Kernel.shell_streams is deprecated in ipykernel 6.0. Use Kernel.shell_stream", DeprecationWarning, @@ -114,7 +114,7 @@ def _shell_streams_default(self): return [] @observe("shell_streams") - def _shell_streams_changed(self, change): + def _shell_streams_changed(self, change): # pragma: no cover warnings.warn( "Kernel.shell_streams is deprecated in ipykernel 6.0. Use Kernel.shell_stream", DeprecationWarning, @@ -683,7 +683,6 @@ def finish_metadata(self, parent, metadata, reply_content): async def execute_request(self, stream, ident, parent): """handle an execute_request""" - try: content = parent["content"] code = content["code"] @@ -947,7 +946,6 @@ def do_is_complete(self, code): async def debug_request(self, stream, ident, parent): content = parent["content"] - reply_content = self.do_debug_request(content) if inspect.isawaitable(reply_content): reply_content = await reply_content @@ -1006,7 +1004,7 @@ async def do_debug_request(self, msg): # Engine methods (DEPRECATED) # --------------------------------------------------------------------------- - async def apply_request(self, stream, ident, parent): + async def apply_request(self, stream, ident, parent): # pragma: no cover self.log.warning("apply_request is deprecated in kernel_base, moving to ipyparallel.") try: content = parent["content"] @@ -1044,7 +1042,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): # Control messages (DEPRECATED) # --------------------------------------------------------------------------- - async def abort_request(self, stream, ident, parent): + async def abort_request(self, stream, ident, parent): # pragma: no cover """abort a specific msg by id""" self.log.warning( "abort_request is deprecated in kernel_base. It is only part of IPython parallel" @@ -1063,7 +1061,7 @@ async def abort_request(self, stream, ident, parent): ) self.log.debug("%s", reply_msg) - async def clear_request(self, stream, idents, parent): + async def clear_request(self, stream, idents, parent): # pragma: no cover """Clear our namespace.""" self.log.warning( "clear_request is deprecated in kernel_base. It is only part of IPython parallel" diff --git a/ipykernel/tests/conftest.py b/ipykernel/tests/conftest.py index a14511ef8..2ec97a592 100644 --- a/ipykernel/tests/conftest.py +++ b/ipykernel/tests/conftest.py @@ -1,6 +1,15 @@ import asyncio +import logging import os +import pytest +import zmq +from jupyter_client.session import Session +from tornado.ioloop import IOLoop +from zmq.eventloop.zmqstream import ZMQStream + +from ipykernel.kernelbase import Kernel + try: import resource except ImportError: @@ -26,3 +35,97 @@ # Enforce selector event loop on Windows. if os.name == "nt": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + + +class TestKernel(Kernel): + implementation = "test" + implementation_version = "1.0" + language = "no-op" + language_version = "0.1" + language_info = { + "name": "test", + "mimetype": "text/plain", + "file_extension": ".txt", + } + banner = "test kernel" + log = logging.getLogger() + + def __init__(self, *args, **kwargs): + self.context = context = zmq.Context() + self.iopub_socket = context.socket(zmq.PUB) + self.session = Session() + self.test_sockets = [self.iopub_socket] + self.test_streams = [] + + for name in ["shell", "control"]: + socket = context.socket(zmq.ROUTER) + stream = ZMQStream(socket) + stream.on_send(self._on_send) + self.test_sockets.append(socket) + self.test_streams.append(stream) + setattr(self, f"{name}_stream", stream) + super().__init__(*args, **kwargs) + + def do_execute( + self, code, silent, store_history=True, user_expressions=None, allow_stdin=False + ): + if not silent: + stream_content = {"name": "stdout", "text": code} + self.send_response(self.iopub_socket, "stream", stream_content) + + return { + "status": "ok", + # The base class increments the execution count + "execution_count": self.execution_count, + "payload": [], + "user_expressions": {}, + } + + async def do_debug_request(self, msg): + return {} + + async def test_shell_message(self, *args, **kwargs): + msg_list = self._prep_msg(*args, **kwargs) + await self.dispatch_shell(msg_list) + self.shell_stream.flush() + return await self._wait_for_msg() + + async def test_control_message(self, *args, **kwargs): + msg_list = self._prep_msg(*args, **kwargs) + await self.process_control(msg_list) + self.control_stream.flush() + return await self._wait_for_msg() + + def destroy(self): + for stream in self.test_streams: + stream.close() + for socket in self.test_sockets: + socket.close() + self.context.destroy() + + def _on_send(self, msg, *args, **kwargs): + self._reply = msg + + def _prep_msg(self, *args, **kwargs): + self._reply = None + msg = self.session.msg(*args, **kwargs) + msg = self.session.serialize(msg) + return [zmq.Message(m) for m in msg] + + async def _wait_for_msg(self): + while not self._reply: + await asyncio.sleep(0.1) + _, msg = self.session.feed_identities(self._reply) + return self.session.deserialize(msg) + + def _send_interupt_children(self): + # override to prevent deadlock + pass + + +@pytest.fixture +async def kernel(): + kernel = TestKernel() + kernel.io_loop = IOLoop.current() + yield kernel + kernel.destroy() diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index 35818515e..fa17eb23c 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -1,8 +1,15 @@ """Test eventloop integration""" +import asyncio +import os +import threading +import time + import pytest import tornado +from ipykernel.eventloops import enable_gui, loop_asyncio, loop_tk + from .utils import execute, flush_channels, start_new_kernel KC = KM = None @@ -41,3 +48,41 @@ def test_asyncio_interrupt(): flush_channels(KC) msg_id, content = execute(async_code, KC) assert content["status"] == "ok" + + +windows_skip = pytest.mark.skipif(os.name == "nt", reason="causing failures on windows") + + +@windows_skip +def test_tk_loop(kernel): + def do_thing(): + time.sleep(1) + try: + kernel.app_wrapper.app.quit() + # guard for tk failing to start (if there is no display) + except AttributeError: + pass + + t = threading.Thread(target=do_thing) + t.start() + # guard for tk failing to start (if there is no display) + try: + loop_tk(kernel) + except Exception: + pass + t.join() + + +@windows_skip +def test_asyncio_loop(kernel): + def do_thing(): + loop.call_soon(loop.stop) + + loop = asyncio.get_event_loop() + loop.call_soon(do_thing) + loop_asyncio(kernel) + + +@windows_skip +def test_enable_gui(kernel): + enable_gui("tk", kernel) diff --git a/ipykernel/tests/test_kernel_direct.py b/ipykernel/tests/test_kernel_direct.py new file mode 100644 index 000000000..e0308707c --- /dev/null +++ b/ipykernel/tests/test_kernel_direct.py @@ -0,0 +1,78 @@ +"""test the IPython Kernel""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import os + +import pytest + +if os.name == "nt": + pytest.skip("skipping tests on windows", allow_module_level=True) + + +async def test_direct_kernel_info_request(kernel): + reply = await kernel.test_shell_message("kernel_info_request", {}) + assert reply["header"]["msg_type"] == "kernel_info_reply" + + +async def test_direct_execute_request(kernel): + reply = await kernel.test_shell_message("execute_request", dict(code="hello", silent=False)) + assert reply["header"]["msg_type"] == "execute_reply" + + +async def test_direct_execute_request_aborting(kernel): + kernel._aborting = True + reply = await kernel.test_shell_message("execute_request", dict(code="hello", silent=False)) + assert reply["header"]["msg_type"] == "execute_reply" + assert reply["content"]["status"] == "aborted" + + +async def test_complete_request(kernel): + reply = await kernel.test_shell_message("complete_request", dict(code="hello", cursor_pos=0)) + assert reply["header"]["msg_type"] == "complete_reply" + + +async def test_inspect_request(kernel): + reply = await kernel.test_shell_message("inspect_request", dict(code="hello", cursor_pos=0)) + assert reply["header"]["msg_type"] == "inspect_reply" + + +async def test_history_request(kernel): + reply = await kernel.test_shell_message( + "history_request", dict(hist_access_type="", output="", raw="") + ) + assert reply["header"]["msg_type"] == "history_reply" + + +async def test_comm_info_request(kernel): + reply = await kernel.test_shell_message("comm_info_request") + assert reply["header"]["msg_type"] == "comm_info_reply" + + +async def test_direct_interrupt_request(kernel): + reply = await kernel.test_shell_message("interrupt_request", {}) + assert reply["header"]["msg_type"] == "interrupt_reply" + + +async def test_direct_shutdown_request(kernel): + reply = await kernel.test_shell_message("shutdown_request", dict(restart=False)) + assert reply["header"]["msg_type"] == "shutdown_reply" + reply = await kernel.test_shell_message("shutdown_request", dict(restart=True)) + assert reply["header"]["msg_type"] == "shutdown_reply" + + +async def test_is_complete_request(kernel): + reply = await kernel.test_shell_message("is_complete_request", dict(code="hello")) + assert reply["header"]["msg_type"] == "is_complete_reply" + + +async def test_direct_debug_request(kernel): + reply = await kernel.test_control_message("debug_request", {}) + assert reply["header"]["msg_type"] == "debug_reply" + + +# TODO: this causes deadlock +# async def test_direct_usage_request(kernel): +# reply = await kernel.test_control_message("usage_request", {}) +# assert reply['header']['msg_type'] == 'usage_reply' diff --git a/pyproject.toml b/pyproject.toml index 5aed204bb..c6cf0b4b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,7 +51,8 @@ test = [ "flaky", "ipyparallel", "pre-commit", - "pytest-timeout", + "pytest-asyncio", + "pytest-timeout" ] [tool.hatch.version] @@ -110,6 +111,7 @@ testpaths = [ "ipykernel/tests", "ipykernel/inprocess/tests" ] +asyncio_mode = "auto" timeout = 300 # Restore this setting to debug failures # timeout_method = "thread" @@ -127,6 +129,20 @@ filterwarnings= [ "module:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", ] +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "if self.debug:", + "if settings.DEBUG", + "raise AssertionError", + "raise NotImplementedError", + "if 0:", + "if __name__ == .__main__.:", + "class .*\bProtocol\\):", + "@(abc\\.)?abstractmethod", +] + [tool.flake8] ignore = "E501, W503, E402" builtins = "c, get_config" From c5ce7bf3906d4d6430fca32d5bab95b2dd0e4628 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 17 Nov 2022 21:53:06 -0600 Subject: [PATCH 0923/1195] Add windows coverage and clean up workflows (#1023) --- .github/workflows/check-release.yml | 19 -------- .github/workflows/ci.yml | 67 ++++++++++++---------------- ipykernel/tests/test_embed_kernel.py | 5 +++ ipykernel/tests/test_eventloop.py | 2 + ipykernel/tests/test_start_kernel.py | 5 +++ pyproject.toml | 9 ++-- 6 files changed, 44 insertions(+), 63 deletions(-) delete mode 100644 .github/workflows/check-release.yml diff --git a/.github/workflows/check-release.yml b/.github/workflows/check-release.yml deleted file mode 100644 index 1dac3403b..000000000 --- a/.github/workflows/check-release.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Check Release -on: - push: - branches: ["main"] - pull_request: - -concurrency: - group: check-release-${{ github.ref }} - cancel-in-progress: true - -jobs: - check_release: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 - with: - token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 14da9db63..6fbbb4f9e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,42 +37,23 @@ jobs: - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Install the Python dependencies - run: | - pip install .[test] codecov - - - name: Install matplotlib - if: ${{ !startsWith(matrix.os, 'macos') && !startsWith(matrix.python-version, 'pypy') }} - run: | - pip install matplotlib || echo 'failed to install matplotlib' - - - name: Install alternate event loops - if: ${{ !startsWith(matrix.os, 'windows') }} - run: | - pip install curio || echo 'ignoring curio install failure' - pip install trio || echo 'ignoring trio install failure' - - - name: List installed packages - run: | - pip uninstall pipx -y - pip install pipdeptree - pipdeptree - pipdeptree --reverse - pip freeze - pip check - - name: Run the tests timeout-minutes: 15 if: ${{ !startsWith( matrix.python-version, 'pypy' ) && !startsWith(matrix.os, 'windows') }} run: | - hatch run cov:test || hatch run test:test --lf + hatch run cov:test --cov-fail-under 50 || hatch run test:test --lf + + - name: Run the tests on pypy + timeout-minutes: 15 + if: ${{ startsWith( matrix.python-version, 'pypy' ) }} + run: | + hatch run test:nowarn || hatch run test:nowarn --lf - - name: Run the tests on pypy and windows + - name: Run the tests on Windows timeout-minutes: 15 - if: ${{ startsWith( matrix.python-version, 'pypy' ) || startsWith(matrix.os, 'windows') }} + if: ${{ startsWith(matrix.os, 'windows') }} run: | - pip install -e ".[test]" - pytest -vv || pytest -vv --lf + hatch run cov:nowarn || hatch run test:nowarn --lf - name: Coverage run: | @@ -81,6 +62,7 @@ jobs: - name: Check Launcher run: | + pip install . cd $HOME python -m ipykernel_launcher --help @@ -91,6 +73,15 @@ jobs: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/pre-commit@v1 + check_release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + test_docs: runs-on: ubuntu-latest steps: @@ -123,7 +114,7 @@ jobs: - name: Run the tests timeout-minutes: 10 - run: hatch run test:test + run: pytest -W default -vv || pytest --vv -W default --lf test_miniumum_versions: name: Test Minimum Versions @@ -137,9 +128,12 @@ jobs: python_version: "3.8" - name: Install miniumum versions uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1 + with: + only_create_file: 1 - name: Run the unit tests run: | - pytest -vv -W default || pytest -vv -W default --lf + export PIP_CONSTRAINT="./contraints_file.txt" + hatch run test:nowarn || hatch run test:nowarn --lf test_prereleases: name: Test Prereleases @@ -150,16 +144,10 @@ jobs: uses: actions/checkout@v3 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Install the Python dependencies - run: | - pip install --pre -e ".[test]" - - name: List installed packages - run: | - pip freeze - pip check - name: Run the tests run: | - pytest -vv -W default || pytest -vv -W default --lf + export PIP_PRE=1 + hatch run test:nowarn || hatch run test:nowarn --lf make_sdist: name: Make SDist @@ -195,6 +183,7 @@ jobs: - test_miniumum_versions - pre_commit - test_prereleases + - check_release - link_check - test_sdist runs-on: ubuntu-latest diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 2a9eaba98..cb8229fed 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -10,6 +10,7 @@ from contextlib import contextmanager from subprocess import PIPE, Popen +import pytest from flaky import flaky from jupyter_client import BlockingKernelClient from jupyter_core import paths @@ -18,6 +19,10 @@ TIMEOUT = 15 +if os.name == "nt": + pytest.skip("skipping tests on windows", allow_module_level=True) + + @contextmanager def setup_kernel(cmd): """start an embedded kernel in a subprocess, and wait for it to be ready diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index fa17eb23c..8f345cc1c 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -2,6 +2,7 @@ import asyncio import os +import sys import threading import time @@ -54,6 +55,7 @@ def test_asyncio_interrupt(): @windows_skip +@pytest.mark.skipif(sys.platform == "darwin", reason="hangs on macos") def test_tk_loop(kernel): def do_thing(): time.sleep(1) diff --git a/ipykernel/tests/test_start_kernel.py b/ipykernel/tests/test_start_kernel.py index 6d6c64e47..b2b2d3b8c 100644 --- a/ipykernel/tests/test_start_kernel.py +++ b/ipykernel/tests/test_start_kernel.py @@ -1,11 +1,16 @@ +import os from textwrap import dedent +import pytest from flaky import flaky from .test_embed_kernel import setup_kernel TIMEOUT = 15 +if os.name == "nt": + pytest.skip("skipping tests on windows", allow_module_level=True) + @flaky(max_runs=3) def test_ipython_start_kernel_userns(): diff --git a/pyproject.toml b/pyproject.toml index c6cf0b4b8..67cde9766 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,15 +76,14 @@ build = "make -C docs html SPHINXOPTS='-W'" features = ["test"] [tool.hatch.envs.test.scripts] test = "python -m pytest -vv {args}" -nowarn = "python -m pytest -vv -W default {args}" +nowarn = "test -W default {args}" [tool.hatch.envs.cov] features = ["test"] -dependencies = ["coverage", "pytest-cov"] -[tool.hatch.envs.cov.env-vars] -ARGS = "-vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered" +dependencies = ["coverage", "pytest-cov", "matplotlib", "curio", "trio"] [tool.hatch.envs.cov.scripts] -test = "python -m pytest $ARGS --cov-fail-under 50 {args}" +test = "python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered {args}" +nowarn = "test -W default {args}" [tool.mypy] check_untyped_defs = true From 6607b9d2edf37cc5731b4709e1ccdad5d6132a5d Mon Sep 17 00:00:00 2001 From: martinRenou Date: Fri, 18 Nov 2022 13:30:15 -0800 Subject: [PATCH 0924/1195] Extract the Comm Python package (#973) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/comm/__init__.py | 7 +- ipykernel/comm/comm.py | 167 +++---------------------------------- ipykernel/comm/manager.py | 129 +--------------------------- ipykernel/ipkernel.py | 13 ++- pyproject.toml | 1 + 5 files changed, 31 insertions(+), 286 deletions(-) diff --git a/ipykernel/comm/__init__.py b/ipykernel/comm/__init__.py index f82cf5448..b986d0c17 100644 --- a/ipykernel/comm/__init__.py +++ b/ipykernel/comm/__init__.py @@ -1,2 +1,5 @@ -from .comm import * # noqa -from .manager import * # noqa +__all__ = ["Comm", "CommManager"] + +from comm.base_comm import CommManager # noqa + +from .comm import Comm # noqa diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 266dc048b..4926fcefe 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -3,71 +3,32 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import uuid - -from traitlets import Any, Bool, Bytes, Dict, Instance, Unicode, default -from traitlets.config import LoggingConfigurable +from comm.base_comm import BaseComm from ipykernel.jsonutil import json_clean from ipykernel.kernelbase import Kernel -class Comm(LoggingConfigurable): +class Comm(BaseComm): """Class for communicating between a Frontend and a Kernel""" - kernel = Instance("ipykernel.kernelbase.Kernel", allow_none=True) - - @default("kernel") - def _default_kernel(self): - if Kernel.initialized(): - return Kernel.instance() - - comm_id = Unicode() - - @default("comm_id") - def _default_comm_id(self): - return uuid.uuid4().hex - - primary = Bool(True, help="Am I the primary or secondary Comm?") - - target_name = Unicode("comm") - target_module = Unicode( - None, - allow_none=True, - help="""requirejs module from - which to load comm target.""", - ) - - topic = Bytes() - - @default("topic") - def _default_topic(self): - return ("comm-%s" % self.comm_id).encode("ascii") - - _open_data = Dict(help="data dict, if any, to be included in comm_open") - _close_data = Dict(help="data dict, if any, to be included in comm_close") - - _msg_callback = Any() - _close_callback = Any() + def __init__(self, *args, **kwargs): + self.kernel = None - _closed = Bool(True) + super().__init__(*args, **kwargs) - def __init__(self, target_name="", data=None, metadata=None, buffers=None, **kwargs): - if target_name: - kwargs["target_name"] = target_name - super().__init__(**kwargs) - if self.kernel: - if self.primary: - # I am primary, open my peer. - self.open(data=data, metadata=metadata, buffers=buffers) - else: - self._closed = False - - def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): + def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): """Helper for sending a comm message on IOPub""" + if not Kernel.initialized(): + return + data = {} if data is None else data metadata = {} if metadata is None else metadata content = json_clean(dict(data=data, comm_id=self.comm_id, **keys)) + + if self.kernel is None: + self.kernel = Kernel.instance() + self.kernel.session.send( self.kernel.iopub_socket, msg_type, @@ -78,107 +39,5 @@ def _publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys) buffers=buffers, ) - def __del__(self): - """trigger close on gc""" - self.close(deleting=True) - - # publishing messages - - def open(self, data=None, metadata=None, buffers=None): - """Open the frontend-side version of this comm""" - if data is None: - data = self._open_data - comm_manager = getattr(self.kernel, "comm_manager", None) - if comm_manager is None: - raise RuntimeError( - "Comms cannot be opened without a kernel " - "and a comm_manager attached to that kernel." - ) - - comm_manager.register_comm(self) - try: - self._publish_msg( - "comm_open", - data=data, - metadata=metadata, - buffers=buffers, - target_name=self.target_name, - target_module=self.target_module, - ) - self._closed = False - except Exception: - comm_manager.unregister_comm(self) - raise - - def close(self, data=None, metadata=None, buffers=None, deleting=False): - """Close the frontend-side version of this comm""" - if self._closed: - # only close once - return - self._closed = True - # nothing to send if we have no kernel - # can be None during interpreter cleanup - if not self.kernel: - return - if data is None: - data = self._close_data - self._publish_msg( - "comm_close", - data=data, - metadata=metadata, - buffers=buffers, - ) - if not deleting: - # If deleting, the comm can't be registered - self.kernel.comm_manager.unregister_comm(self) - - def send(self, data=None, metadata=None, buffers=None): - """Send a message to the frontend-side version of this comm""" - self._publish_msg( - "comm_msg", - data=data, - metadata=metadata, - buffers=buffers, - ) - - # registering callbacks - - def on_close(self, callback): - """Register a callback for comm_close - - Will be called with the `data` of the close message. - - Call `on_close(None)` to disable an existing callback. - """ - self._close_callback = callback - - def on_msg(self, callback): - """Register a callback for comm_msg - - Will be called with the `data` of any comm_msg messages. - - Call `on_msg(None)` to disable an existing callback. - """ - self._msg_callback = callback - - # handling of incoming messages - - def handle_close(self, msg): - """Handle a comm_close message""" - self.log.debug("handle_close[%s](%s)", self.comm_id, msg) - if self._close_callback: - self._close_callback(msg) - - def handle_msg(self, msg): - """Handle a comm_msg message""" - self.log.debug("handle_msg[%s](%s)", self.comm_id, msg) - if self._msg_callback: - shell = self.kernel.shell - if shell: - shell.events.trigger("pre_execute") - self._msg_callback(msg) - if shell: - shell.events.trigger("post_execute") - __all__ = ["Comm"] diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 8dae16235..91bd477b7 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -3,131 +3,4 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import logging - -from traitlets import Dict, Instance -from traitlets.config import LoggingConfigurable -from traitlets.utils.importstring import import_item - -from .comm import Comm - - -class CommManager(LoggingConfigurable): - """Manager for Comms in the Kernel""" - - kernel = Instance("ipykernel.kernelbase.Kernel") - comms = Dict() - targets = Dict() - - # Public APIs - - def register_target(self, target_name, f): - """Register a callable f for a given target name - - f will be called with two arguments when a comm_open message is received with `target`: - - - the Comm instance - - the `comm_open` message itself. - - f can be a Python callable or an import string for one. - """ - if isinstance(f, str): - f = import_item(f) - - self.targets[target_name] = f - - def unregister_target(self, target_name, f): - """Unregister a callable registered with register_target""" - return self.targets.pop(target_name) - - def register_comm(self, comm): - """Register a new comm""" - comm_id = comm.comm_id - comm.kernel = self.kernel - self.comms[comm_id] = comm - return comm_id - - def unregister_comm(self, comm): - """Unregister a comm, and close its counterpart""" - # unlike get_comm, this should raise a KeyError - comm = self.comms.pop(comm.comm_id) - - def get_comm(self, comm_id): - """Get a comm with a particular id - - Returns the comm if found, otherwise None. - - This will not raise an error, - it will log messages if the comm cannot be found. - """ - try: - return self.comms[comm_id] - except KeyError: - self.log.warning("No such comm: %s", comm_id) - if self.log.isEnabledFor(logging.DEBUG): - # don't create the list of keys if debug messages aren't enabled - self.log.debug("Current comms: %s", list(self.comms.keys())) - - # Message handlers - def comm_open(self, stream, ident, msg): - """Handler for comm_open messages""" - content = msg["content"] - comm_id = content["comm_id"] - target_name = content["target_name"] - f = self.targets.get(target_name, None) - comm = Comm( - comm_id=comm_id, - primary=False, - target_name=target_name, - ) - self.register_comm(comm) - if f is None: - self.log.error("No such comm target registered: %s", target_name) - else: - try: - f(comm, msg) - return - except Exception: - self.log.error("Exception opening comm with target: %s", target_name, exc_info=True) - - # Failure. - try: - comm.close() - except Exception: - self.log.error( - """Could not close comm during `comm_open` failure - clean-up. The comm may not have been opened yet.""", - exc_info=True, - ) - - def comm_msg(self, stream, ident, msg): - """Handler for comm_msg messages""" - content = msg["content"] - comm_id = content["comm_id"] - comm = self.get_comm(comm_id) - if comm is None: - return - - try: - comm.handle_msg(msg) - except Exception: - self.log.error("Exception in comm_msg for %s", comm_id, exc_info=True) - - def comm_close(self, stream, ident, msg): - """Handler for comm_close messages""" - content = msg["content"] - comm_id = content["comm_id"] - comm = self.get_comm(comm_id) - if comm is None: - return - - self.comms[comm_id]._closed = True - del self.comms[comm_id] - - try: - comm.handle_close(msg) - except Exception: - self.log.error("Exception in comm_close for %s", comm_id, exc_info=True) - - -__all__ = ["CommManager"] +from comm.base_comm import CommManager # noqa diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index c9d86b4c7..c16cc0ed5 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -9,12 +9,13 @@ from contextlib import contextmanager from functools import partial +import comm from IPython.core import release from IPython.utils.tokenutil import line_at_cursor, token_at_cursor from traitlets import Any, Bool, Instance, List, Type, observe, observe_compat from zmq.eventloop.zmqstream import ZMQStream -from .comm import CommManager +from .comm import Comm from .compiler import XCachingCompiler from .debugger import Debugger, _is_debugpy_available from .eventloops import _use_appnope @@ -39,6 +40,14 @@ _EXPERIMENTAL_KEY_NAME = "_jupyter_types_experimental" +def create_comm(*args, **kwargs): + """Create a new Comm.""" + return Comm(*args, **kwargs) + + +comm.create_comm = create_comm + + class IPythonKernel(KernelBase): shell = Instance("IPython.core.interactiveshell.InteractiveShellABC", allow_none=True) shell_class = Type(ZMQInteractiveShell) @@ -101,7 +110,7 @@ def __init__(self, **kwargs): self.shell.display_pub.session = self.session self.shell.display_pub.pub_socket = self.iopub_socket - self.comm_manager = CommManager(parent=self, kernel=self) + self.comm_manager = comm.get_comm_manager() self.shell.configurables.append(self.comm_manager) comm_msg_types = ["comm_open", "comm_msg", "comm_close"] diff --git a/pyproject.toml b/pyproject.toml index 67cde9766..984988002 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ requires-python = ">=3.8" dependencies = [ "debugpy>=1.0", "ipython>=7.23.1", + "comm>=0.1", "traitlets>=5.1.0", "jupyter_client>=6.1.12", "tornado>=6.1", From ce0b6c296bc19223d426892657878f28af0ec206 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 21 Nov 2022 10:52:24 -0600 Subject: [PATCH 0925/1195] Add terminal color support (#1025) Fixes https://github.com/ipython/ipykernel/issues/1024 --- ipykernel/zmqshell.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 1aaa591a4..f79090c95 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -482,6 +482,9 @@ def init_environment(self): # These two ensure 'ls' produces nice coloring on BSD-derived systems env["TERM"] = "xterm-color" env["CLICOLOR"] = "1" + # These two add terminal color in tools that support it. + env["FORCE_COLOR"] = "1" + env["CLICOLOR_FORCE"] = "1" # Since normal pagers don't work at all (over pexpect we don't have # single-key control of the subprocess), try to disable paging in # subprocesses as much as possible. From b601090cab873c152ab65d138426e10a0af4fec3 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 21 Nov 2022 16:56:35 +0000 Subject: [PATCH 0926/1195] Publish 6.18.0 SHA256 hashes: ipykernel-6.18.0-py3-none-any.whl: 3b430f9da5643e272727f839763cca96396333b0db3ac4c0293208aabf8adfbf ipykernel-6.18.0.tar.gz: 89bed6d97ddaf5f3c47f2fce7981f6dc5a6bc847ff165e19e350a7cd16f3249f --- CHANGELOG.md | 25 +++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a05aff15..bb92a07eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ +## 6.18.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.17.1...ce0b6c296bc19223d426892657878f28af0ec206)) + +### Enhancements made + +- Add terminal color support [#1025](https://github.com/ipython/ipykernel/pull/1025) ([@blink1073](https://github.com/blink1073)) +- Extract the Comm Python package [#973](https://github.com/ipython/ipykernel/pull/973) ([@martinRenou](https://github.com/martinRenou)) + +### Maintenance and upkeep improvements + +- Add windows coverage and clean up workflows [#1023](https://github.com/ipython/ipykernel/pull/1023) ([@blink1073](https://github.com/blink1073)) +- Increase coverage [#1021](https://github.com/ipython/ipykernel/pull/1021) ([@blink1073](https://github.com/blink1073)) +- Allow releasing from repo [#1020](https://github.com/ipython/ipykernel/pull/1020) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-11-09&to=2022-11-21&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-11-09..2022-11-21&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AmartinRenou+updated%3A2022-11-09..2022-11-21&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-11-09..2022-11-21&type=Issues) + + + ## 6.17.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.17.0...a06867786eaf0c5d9454d2df61f354c7012a625e)) @@ -19,8 +42,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-10-31..2022-11-09&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2022-10-31..2022-11-09&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2022-10-31..2022-11-09&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-10-31..2022-11-09&type=Issues) - - ## 6.17.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.16.2...db00586a25a4f047a90386f4947e60ff1dbee2b6)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 314c9c554..fb643e455 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.17.1" +__version__ = "6.18.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 1cf06b12e9779b07d8897430fae8d7329ada3ba1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 21 Nov 2022 20:57:04 -0600 Subject: [PATCH 0927/1195] [pre-commit.ci] pre-commit autoupdate (#1027) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2781ddda4..72acb394e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -62,7 +62,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.990 + rev: v0.991 hooks: - id: mypy exclude: "ipykernel.*tests" @@ -72,7 +72,7 @@ repos: stages: [manual] - repo: https://github.com/john-hen/Flake8-pyproject - rev: 1.1.0.post0 + rev: 1.2.0 hooks: - id: Flake8-pyproject alias: flake8 @@ -81,13 +81,13 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.27.0 + rev: v8.28.0 hooks: - id: eslint stages: [manual] - repo: https://github.com/sirosen/check-jsonschema - rev: 0.19.1 + rev: 0.19.2 hooks: - id: check-jsonschema name: "Check GitHub Workflows" From 747259c3aa7769a32e29807e2faf67b181b26cde Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 23 Nov 2022 21:24:01 -0600 Subject: [PATCH 0928/1195] Use base setup dependency type (#1029) --- .github/workflows/ci.yml | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6fbbb4f9e..cf533e2da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,7 @@ jobs: - name: Coverage run: | - pip install codecov + pip install codecov coverage[toml] codecov - name: Check Launcher @@ -125,14 +125,9 @@ jobs: - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: - python_version: "3.8" - - name: Install miniumum versions - uses: jupyterlab/maintainer-tools/.github/actions/install-minimums@v1 - with: - only_create_file: 1 + dependency_type: minimum - name: Run the unit tests run: | - export PIP_CONSTRAINT="./contraints_file.txt" hatch run test:nowarn || hatch run test:nowarn --lf test_prereleases: @@ -144,9 +139,10 @@ jobs: uses: actions/checkout@v3 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + with: + dependency_type: pre - name: Run the tests run: | - export PIP_PRE=1 hatch run test:nowarn || hatch run test:nowarn --lf make_sdist: From c5a045fe5ef1ce45125d4bdee2dda4f78c9ad8cf Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Thu, 24 Nov 2022 14:30:11 +0100 Subject: [PATCH 0929/1195] fix: use comm package in backwards compatible way (#1028) * fix: use comm package in backwards compatible way * feat: do not use the LoggingConfigurable class in create_comm * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * lint * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- ipykernel/comm/__init__.py | 3 +-- ipykernel/comm/comm.py | 26 ++++++++++++++++++-------- ipykernel/comm/manager.py | 11 ++++++++++- ipykernel/ipkernel.py | 4 ++-- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/ipykernel/comm/__init__.py b/ipykernel/comm/__init__.py index b986d0c17..36b419d5e 100644 --- a/ipykernel/comm/__init__.py +++ b/ipykernel/comm/__init__.py @@ -1,5 +1,4 @@ __all__ = ["Comm", "CommManager"] -from comm.base_comm import CommManager # noqa - from .comm import Comm # noqa +from .manager import CommManager diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 4926fcefe..a143cfed2 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -3,19 +3,18 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from comm.base_comm import BaseComm +from typing import Optional + +import comm.base_comm +import traitlets.config from ipykernel.jsonutil import json_clean from ipykernel.kernelbase import Kernel -class Comm(BaseComm): - """Class for communicating between a Frontend and a Kernel""" - - def __init__(self, *args, **kwargs): - self.kernel = None - - super().__init__(*args, **kwargs) +# this is the class that will be created if we do comm.create_comm +class BaseComm(comm.base_comm.BaseComm): + kernel: Optional[Kernel] def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): """Helper for sending a comm message on IOPub""" @@ -40,4 +39,15 @@ def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): ) +# but for backwards compatibility, we need to inherit from LoggingConfigurable +class Comm(traitlets.config.LoggingConfigurable, BaseComm): + """Class for communicating between a Frontend and a Kernel""" + + def __init__(self, *args, **kwargs): + self.kernel = None + # Comm takes positional arguments, LoggingConfigurable does not, so we explicitly forward arguments + traitlets.config.LoggingConfigurable.__init__(self, **kwargs) + BaseComm.__init__(self, *args, **kwargs) + + __all__ = ["Comm"] diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 91bd477b7..0314d6ffa 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -3,4 +3,13 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from comm.base_comm import CommManager # noqa + +import comm.base_comm +import traitlets.config + + +class CommManager(traitlets.config.LoggingConfigurable, comm.base_comm.CommManager): + def __init__(self, **kwargs): + # CommManager doesn't take arguments, so we explicitly forward arguments + traitlets.config.LoggingConfigurable.__init__(self, **kwargs) + comm.base_comm.CommManager.__init__(self) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index c16cc0ed5..a2f69f52b 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -15,7 +15,7 @@ from traitlets import Any, Bool, Instance, List, Type, observe, observe_compat from zmq.eventloop.zmqstream import ZMQStream -from .comm import Comm +from .comm.comm import BaseComm from .compiler import XCachingCompiler from .debugger import Debugger, _is_debugpy_available from .eventloops import _use_appnope @@ -42,7 +42,7 @@ def create_comm(*args, **kwargs): """Create a new Comm.""" - return Comm(*args, **kwargs) + return BaseComm(*args, **kwargs) comm.create_comm = create_comm From 4c0c0375b8fdba4de7a522b7d43bcd079956b990 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 24 Nov 2022 22:19:10 -0600 Subject: [PATCH 0930/1195] Clean up testing and coverage (#1030) --- .coveragerc | 2 - ipykernel/tests/test_kernel_direct.py | 65 +++++++++++++++++++++++++++ pyproject.toml | 9 +++- 3 files changed, 73 insertions(+), 3 deletions(-) delete mode 100644 .coveragerc diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 6b3faf553..000000000 --- a/.coveragerc +++ /dev/null @@ -1,2 +0,0 @@ -[run] -omit = ipykernel/tests/* diff --git a/ipykernel/tests/test_kernel_direct.py b/ipykernel/tests/test_kernel_direct.py index e0308707c..276c02143 100644 --- a/ipykernel/tests/test_kernel_direct.py +++ b/ipykernel/tests/test_kernel_direct.py @@ -3,7 +3,9 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import asyncio import os +import warnings import pytest @@ -72,6 +74,69 @@ async def test_direct_debug_request(kernel): assert reply["header"]["msg_type"] == "debug_reply" +async def test_deprecated_features(kernel): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + header = kernel._parent_header + assert isinstance(header, dict) + shell_streams = kernel.shell_streams + assert len(shell_streams) == 1 + assert shell_streams[0] == kernel.shell_stream + warnings.simplefilter("ignore", RuntimeWarning) + kernel.shell_streams = [kernel.shell_stream, kernel.shell_stream] + + +async def test_process_control(kernel): + from jupyter_client.session import DELIM + + class FakeMsg: + def __init__(self, bytes): + self.bytes = bytes + + await kernel.process_control([FakeMsg(DELIM), 1]) + msg = kernel._prep_msg("does_not_exist") + await kernel.process_control(msg) + + +def test_should_handle(kernel): + msg = kernel.session.msg("debug_request", {}) + kernel.aborted.add(msg["header"]["msg_id"]) + assert not kernel.should_handle(kernel.control_stream, msg, []) + + +async def test_dispatch_shell(kernel): + from jupyter_client.session import DELIM + + class FakeMsg: + def __init__(self, bytes): + self.bytes = bytes + + await kernel.dispatch_shell([FakeMsg(DELIM), 1]) + msg = kernel._prep_msg("does_not_exist") + await kernel.dispatch_shell(msg) + + +async def test_enter_eventloop(kernel): + kernel.eventloop = None + kernel.enter_eventloop() + kernel.eventloop = asyncio.get_running_loop() + kernel.enter_eventloop() + called = 0 + + def check_status(): + nonlocal called + if called == 0: + msg = kernel.session.msg("debug_request", {}) + kernel.msg_queue.put(msg) + called += 1 + kernel.io_loop.call_later(0.001, check_status) + + kernel.io_loop.call_later(0.001, check_status) + kernel.start() + while called < 2: + await asyncio.sleep(0.1) + + # TODO: this causes deadlock # async def test_direct_usage_request(kernel): # reply = await kernel.test_control_message("usage_request", {}) diff --git a/pyproject.toml b/pyproject.toml index 984988002..d626535af 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ nowarn = "test -W default {args}" [tool.hatch.envs.cov] features = ["test"] -dependencies = ["coverage", "pytest-cov", "matplotlib", "curio", "trio"] +dependencies = ["coverage[toml]", "pytest-cov", "matplotlib", "curio", "trio"] [tool.hatch.envs.cov.scripts] test = "python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered {args}" nowarn = "test -W default {args}" @@ -143,6 +143,13 @@ exclude_lines = [ "@(abc\\.)?abstractmethod", ] +[tool.coverage.run] +omit = [ + "ipykernel/tests/*", + "ipykernel/debugger.py", + "ipykernel/eventloops.py", +] + [tool.flake8] ignore = "E501, W503, E402" builtins = "c, get_config" From a3285d6df283c842a651f149341f26a89a71fe49 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 25 Nov 2022 06:58:54 -0600 Subject: [PATCH 0931/1195] Add more coverage and add Readme badges (#1031) --- README.md | 4 + ipykernel/ipkernel.py | 2 +- ipykernel/tests/conftest.py | 49 +++++++++---- ipykernel/tests/test_ipkernel_direct.py | 98 +++++++++++++++++++++++++ pyproject.toml | 11 ++- 5 files changed, 148 insertions(+), 16 deletions(-) create mode 100644 ipykernel/tests/test_ipkernel_direct.py diff --git a/README.md b/README.md index a6a02a812..0dbf03b3b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # IPython Kernel for Jupyter +[![Build Status](https://github.com/ipython/ipykernel/actions/workflows/ci.yml/badge.svg?query=branch%3Amain++)](https://github.com/ipython/ipykernel/actions/workflows/ci.yml/badge.svg?query=branch%3Amain++) +[![codecov](https://codecov.io/gh/ipython/ipykernel/branch/main/graph/badge.svg?token=SyksDOcIJa)](https://codecov.io/gh/ipython/ipykernel) +[![Documentation Status](https://readthedocs.org/projects/ipython/badge/?version=latest)](http://ipython.readthedocs.io/en/latest/?badge=latest) + This package provides the IPython kernel for Jupyter. ## Installation from source diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index a2f69f52b..5a188e640 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -659,7 +659,7 @@ def do_clear(self): class Kernel(IPythonKernel): - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs): # pragma: no cover import warnings warnings.warn( diff --git a/ipykernel/tests/conftest.py b/ipykernel/tests/conftest.py index 2ec97a592..7d1516715 100644 --- a/ipykernel/tests/conftest.py +++ b/ipykernel/tests/conftest.py @@ -8,7 +8,9 @@ from tornado.ioloop import IOLoop from zmq.eventloop.zmqstream import ZMQStream +from ipykernel.ipkernel import IPythonKernel from ipykernel.kernelbase import Kernel +from ipykernel.zmqshell import ZMQInteractiveShell try: import resource @@ -37,20 +39,10 @@ asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) -class TestKernel(Kernel): - implementation = "test" - implementation_version = "1.0" - language = "no-op" - language_version = "0.1" - language_info = { - "name": "test", - "mimetype": "text/plain", - "file_extension": ".txt", - } - banner = "test kernel" +class KernelMixin: log = logging.getLogger() - def __init__(self, *args, **kwargs): + def _initialize(self): self.context = context = zmq.Context() self.iopub_socket = context.socket(zmq.PUB) self.session = Session() @@ -64,7 +56,6 @@ def __init__(self, *args, **kwargs): self.test_sockets.append(socket) self.test_streams.append(stream) setattr(self, f"{name}_stream", stream) - super().__init__(*args, **kwargs) def do_execute( self, code, silent, store_history=True, user_expressions=None, allow_stdin=False @@ -123,9 +114,41 @@ def _send_interupt_children(self): pass +class TestKernel(KernelMixin, Kernel): + implementation = "test" + implementation_version = "1.0" + language = "no-op" + language_version = "0.1" + language_info = { + "name": "test", + "mimetype": "text/plain", + "file_extension": ".txt", + } + banner = "test kernel" + + def __init__(self, *args, **kwargs): + self._initialize() + super().__init__(*args, **kwargs) + + +class TestIPyKernel(KernelMixin, IPythonKernel): + def __init__(self, *args, **kwargs): + self._initialize() + super().__init__(*args, **kwargs) + + @pytest.fixture async def kernel(): kernel = TestKernel() kernel.io_loop = IOLoop.current() yield kernel kernel.destroy() + + +@pytest.fixture +async def ipkernel(): + kernel = TestIPyKernel() + kernel.io_loop = IOLoop.current() + yield kernel + kernel.destroy() + ZMQInteractiveShell.clear_instance() diff --git a/ipykernel/tests/test_ipkernel_direct.py b/ipykernel/tests/test_ipkernel_direct.py new file mode 100644 index 000000000..8a0357de3 --- /dev/null +++ b/ipykernel/tests/test_ipkernel_direct.py @@ -0,0 +1,98 @@ +"""Test IPythonKernel directly""" + +import asyncio +import os + +import pytest + +from ipykernel.ipkernel import IPythonKernel + +if os.name == "nt": + pytest.skip("skipping tests on windows", allow_module_level=True) + + +class user_mod: + __dict__ = {} + + +async def test_properities(ipkernel: IPythonKernel): + ipkernel.user_module = user_mod() + ipkernel.user_ns = {} + + +async def test_direct_kernel_info_request(ipkernel): + reply = await ipkernel.test_shell_message("kernel_info_request", {}) + assert reply["header"]["msg_type"] == "kernel_info_reply" + + +async def test_direct_execute_request(ipkernel): + reply = await ipkernel.test_shell_message("execute_request", dict(code="hello", silent=False)) + assert reply["header"]["msg_type"] == "execute_reply" + + +async def test_direct_execute_request_aborting(ipkernel): + ipkernel._aborting = True + reply = await ipkernel.test_shell_message("execute_request", dict(code="hello", silent=False)) + assert reply["header"]["msg_type"] == "execute_reply" + assert reply["content"]["status"] == "aborted" + + +async def test_complete_request(ipkernel): + reply = await ipkernel.test_shell_message("complete_request", dict(code="hello", cursor_pos=0)) + assert reply["header"]["msg_type"] == "complete_reply" + + +async def test_inspect_request(ipkernel): + reply = await ipkernel.test_shell_message("inspect_request", dict(code="hello", cursor_pos=0)) + assert reply["header"]["msg_type"] == "inspect_reply" + + +async def test_history_request(ipkernel): + reply = await ipkernel.test_shell_message( + "history_request", dict(hist_access_type="", output="", raw="") + ) + assert reply["header"]["msg_type"] == "history_reply" + + +async def test_comm_info_request(ipkernel): + reply = await ipkernel.test_shell_message("comm_info_request") + assert reply["header"]["msg_type"] == "comm_info_reply" + + +async def test_direct_interrupt_request(ipkernel): + reply = await ipkernel.test_shell_message("interrupt_request", {}) + assert reply["header"]["msg_type"] == "interrupt_reply" + + +# TODO: this causes deadlock +# async def test_direct_shutdown_request(ipkernel): +# reply = await ipkernel.test_shell_message("shutdown_request", dict(restart=False)) +# assert reply["header"]["msg_type"] == "shutdown_reply" +# reply = await ipkernel.test_shell_message("shutdown_request", dict(restart=True)) +# assert reply["header"]["msg_type"] == "shutdown_reply" + +# TODO: this causes deadlock +# async def test_direct_usage_request(kernel): +# reply = await kernel.test_control_message("usage_request", {}) +# assert reply['header']['msg_type'] == 'usage_reply' + + +async def test_is_complete_request(ipkernel): + reply = await ipkernel.test_shell_message("is_complete_request", dict(code="hello")) + assert reply["header"]["msg_type"] == "is_complete_reply" + + +async def test_direct_debug_request(ipkernel): + reply = await ipkernel.test_control_message("debug_request", {}) + assert reply["header"]["msg_type"] == "debug_reply" + + +async def test_direct_clear(ipkernel): + ipkernel.do_clear() + + +async def test_cancel_on_sigint(ipkernel: IPythonKernel): + future = asyncio.Future() + with ipkernel._cancel_on_sigint(future): + pass + future.set_result(None) diff --git a/pyproject.toml b/pyproject.toml index d626535af..cedc9a3ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,13 @@ test = [ "pytest-asyncio", "pytest-timeout" ] +cov = [ + "coverage[toml]", + "pytest-cov", + "matplotlib", + "curio", + "trio", +] [tool.hatch.version] path = "ipykernel/_version.py" @@ -80,8 +87,7 @@ test = "python -m pytest -vv {args}" nowarn = "test -W default {args}" [tool.hatch.envs.cov] -features = ["test"] -dependencies = ["coverage[toml]", "pytest-cov", "matplotlib", "curio", "trio"] +features = ["test", "cov"] [tool.hatch.envs.cov.scripts] test = "python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered {args}" nowarn = "test -W default {args}" @@ -148,6 +154,7 @@ omit = [ "ipykernel/tests/*", "ipykernel/debugger.py", "ipykernel/eventloops.py", + "ipykernel/pickleutil.py" ] [tool.flake8] From fe8b7b0f3ca0e7a3cb4b7cb1660e949c121905b7 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 25 Nov 2022 20:00:43 -0600 Subject: [PATCH 0932/1195] Add more kernel tests (#1032) --- ipykernel/ipkernel.py | 7 +- ipykernel/tests/conftest.py | 53 ++++++------ ipykernel/tests/test_ipkernel_direct.py | 110 +++++++++++++++++++++++- ipykernel/tests/test_kernel_direct.py | 33 +++++++ pyproject.toml | 3 + 5 files changed, 174 insertions(+), 32 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 5a188e640..bf07bd006 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -460,7 +460,6 @@ def do_complete(self, code, cursor_pos): cursor_pos = len(code) line, offset = line_at_cursor(code, cursor_pos) line_cursor = cursor_pos - offset - txt, matches = self.shell.complete("", line, line_cursor) return { "matches": matches, @@ -589,14 +588,16 @@ def do_is_complete(self, code): return r def do_apply(self, content, bufs, msg_id, reply_metadata): - from .serialize import serialize_object, unpack_apply_message + try: + from ipyparallel.serialize import serialize_object, unpack_apply_message + except ImportError: + from .serialize import serialize_object, unpack_apply_message # type:ignore shell = self.shell try: working = shell.user_ns prefix = "_" + str(msg_id).replace("-", "") + "_" - f, args, kwargs = unpack_apply_message(bufs, working, copy=False) fname = getattr(f, "__name__", "f") diff --git a/ipykernel/tests/conftest.py b/ipykernel/tests/conftest.py index 7d1516715..5e1e192ad 100644 --- a/ipykernel/tests/conftest.py +++ b/ipykernel/tests/conftest.py @@ -45,6 +45,7 @@ class KernelMixin: def _initialize(self): self.context = context = zmq.Context() self.iopub_socket = context.socket(zmq.PUB) + self.stdin_socket = context.socket(zmq.ROUTER) self.session = Session() self.test_sockets = [self.iopub_socket] self.test_streams = [] @@ -57,24 +58,16 @@ def _initialize(self): self.test_streams.append(stream) setattr(self, f"{name}_stream", stream) - def do_execute( - self, code, silent, store_history=True, user_expressions=None, allow_stdin=False - ): - if not silent: - stream_content = {"name": "stdout", "text": code} - self.send_response(self.iopub_socket, "stream", stream_content) - - return { - "status": "ok", - # The base class increments the execution count - "execution_count": self.execution_count, - "payload": [], - "user_expressions": {}, - } - async def do_debug_request(self, msg): return {} + def destroy(self): + for stream in self.test_streams: + stream.close() + for socket in self.test_sockets: + socket.close() + self.context.destroy() + async def test_shell_message(self, *args, **kwargs): msg_list = self._prep_msg(*args, **kwargs) await self.dispatch_shell(msg_list) @@ -87,13 +80,6 @@ async def test_control_message(self, *args, **kwargs): self.control_stream.flush() return await self._wait_for_msg() - def destroy(self): - for stream in self.test_streams: - stream.close() - for socket in self.test_sockets: - socket.close() - self.context.destroy() - def _on_send(self, msg, *args, **kwargs): self._reply = msg @@ -114,7 +100,7 @@ def _send_interupt_children(self): pass -class TestKernel(KernelMixin, Kernel): +class MockKernel(KernelMixin, Kernel): implementation = "test" implementation_version = "1.0" language = "no-op" @@ -130,8 +116,23 @@ def __init__(self, *args, **kwargs): self._initialize() super().__init__(*args, **kwargs) + def do_execute( + self, code, silent, store_history=True, user_expressions=None, allow_stdin=False + ): + if not silent: + stream_content = {"name": "stdout", "text": code} + self.send_response(self.iopub_socket, "stream", stream_content) + + return { + "status": "ok", + # The base class increments the execution count + "execution_count": self.execution_count, + "payload": [], + "user_expressions": {}, + } + -class TestIPyKernel(KernelMixin, IPythonKernel): +class MockIPyKernel(KernelMixin, IPythonKernel): def __init__(self, *args, **kwargs): self._initialize() super().__init__(*args, **kwargs) @@ -139,7 +140,7 @@ def __init__(self, *args, **kwargs): @pytest.fixture async def kernel(): - kernel = TestKernel() + kernel = MockKernel() kernel.io_loop = IOLoop.current() yield kernel kernel.destroy() @@ -147,7 +148,7 @@ async def kernel(): @pytest.fixture async def ipkernel(): - kernel = TestIPyKernel() + kernel = MockIPyKernel() kernel.io_loop = IOLoop.current() yield kernel kernel.destroy() diff --git a/ipykernel/tests/test_ipkernel_direct.py b/ipykernel/tests/test_ipkernel_direct.py index 8a0357de3..903d72f3e 100644 --- a/ipykernel/tests/test_ipkernel_direct.py +++ b/ipykernel/tests/test_ipkernel_direct.py @@ -4,8 +4,12 @@ import os import pytest +import zmq +from IPython.core.history import DummyDB -from ipykernel.ipkernel import IPythonKernel +from ipykernel.ipkernel import BaseComm, IPythonKernel, create_comm + +from .conftest import MockIPyKernel if os.name == "nt": pytest.skip("skipping tests on windows", allow_module_level=True) @@ -25,7 +29,14 @@ async def test_direct_kernel_info_request(ipkernel): assert reply["header"]["msg_type"] == "kernel_info_reply" -async def test_direct_execute_request(ipkernel): +async def test_direct_execute_request(ipkernel: MockIPyKernel): + reply = await ipkernel.test_shell_message("execute_request", dict(code="hello", silent=False)) + assert reply["header"]["msg_type"] == "execute_reply" + reply = await ipkernel.test_shell_message( + "execute_request", dict(code="trigger_error", silent=False) + ) + assert reply["content"]["status"] == "aborted" + reply = await ipkernel.test_shell_message("execute_request", dict(code="hello", silent=False)) assert reply["header"]["msg_type"] == "execute_reply" @@ -40,6 +51,11 @@ async def test_direct_execute_request_aborting(ipkernel): async def test_complete_request(ipkernel): reply = await ipkernel.test_shell_message("complete_request", dict(code="hello", cursor_pos=0)) assert reply["header"]["msg_type"] == "complete_reply" + ipkernel.use_experimental_completions = False + reply = await ipkernel.test_shell_message( + "complete_request", dict(code="hello", cursor_pos=None) + ) + assert reply["header"]["msg_type"] == "complete_reply" async def test_inspect_request(ipkernel): @@ -48,10 +64,23 @@ async def test_inspect_request(ipkernel): async def test_history_request(ipkernel): + ipkernel.shell.history_manager.db = DummyDB() reply = await ipkernel.test_shell_message( "history_request", dict(hist_access_type="", output="", raw="") ) assert reply["header"]["msg_type"] == "history_reply" + reply = await ipkernel.test_shell_message( + "history_request", dict(hist_access_type="tail", output="", raw="") + ) + assert reply["header"]["msg_type"] == "history_reply" + reply = await ipkernel.test_shell_message( + "history_request", dict(hist_access_type="range", output="", raw="") + ) + assert reply["header"]["msg_type"] == "history_reply" + reply = await ipkernel.test_shell_message( + "history_request", dict(hist_access_type="search", output="", raw="") + ) + assert reply["header"]["msg_type"] == "history_reply" async def test_comm_info_request(ipkernel): @@ -77,11 +106,25 @@ async def test_direct_interrupt_request(ipkernel): # assert reply['header']['msg_type'] == 'usage_reply' -async def test_is_complete_request(ipkernel): +async def test_is_complete_request(ipkernel: MockIPyKernel): + reply = await ipkernel.test_shell_message("is_complete_request", dict(code="hello")) + assert reply["header"]["msg_type"] == "is_complete_reply" + setattr(ipkernel, "shell.input_transformer_manager", None) reply = await ipkernel.test_shell_message("is_complete_request", dict(code="hello")) assert reply["header"]["msg_type"] == "is_complete_reply" +def test_do_apply(ipkernel: MockIPyKernel): + from ipyparallel import pack_apply_message + + def hello(): + pass + + msg = pack_apply_message(hello, (), {}) + ipkernel.do_apply(None, msg, "1", {}) + ipkernel.do_apply(None, [], "1", {}) + + async def test_direct_debug_request(ipkernel): reply = await ipkernel.test_control_message("debug_request", {}) assert reply["header"]["msg_type"] == "debug_reply" @@ -96,3 +139,64 @@ async def test_cancel_on_sigint(ipkernel: IPythonKernel): with ipkernel._cancel_on_sigint(future): pass future.set_result(None) + + +def test_dispatch_debugpy(ipkernel: IPythonKernel): + msg = ipkernel.session.msg("debug_request", {}) + msg_list = ipkernel.session.serialize(msg) + ipkernel.dispatch_debugpy([zmq.Message(m) for m in msg_list]) + + +async def test_start(ipkernel: IPythonKernel): + shell_future = asyncio.Future() + control_future = asyncio.Future() + + async def fake_dispatch_queue(): + shell_future.set_result(None) + + async def fake_poll_control_queue(): + control_future.set_result(None) + + ipkernel.dispatch_queue = fake_dispatch_queue + ipkernel.poll_control_queue = fake_poll_control_queue + ipkernel.start() + ipkernel.debugpy_stream = None + ipkernel.start() + await ipkernel.process_one(False) + await shell_future + await control_future + + +async def test_start_no_debugpy(ipkernel: IPythonKernel): + shell_future = asyncio.Future() + control_future = asyncio.Future() + + async def fake_dispatch_queue(): + shell_future.set_result(None) + + async def fake_poll_control_queue(): + control_future.set_result(None) + + ipkernel.dispatch_queue = fake_dispatch_queue + ipkernel.poll_control_queue = fake_poll_control_queue + ipkernel.debugpy_stream = None + ipkernel.start() + + await shell_future + await control_future + + +def test_create_comm(): + assert isinstance(create_comm(), BaseComm) + + +def test_finish_metadata(ipkernel: IPythonKernel): + reply_content = dict(status="error", ename="UnmetDependency") + metadata = ipkernel.finish_metadata({}, {}, reply_content) + assert metadata["dependencies_met"] is False + + +async def test_do_debug_request(ipkernel: IPythonKernel): + msg = ipkernel.session.msg("debug_request", {}) + msg_list = ipkernel.session.serialize(msg) + await ipkernel.do_debug_request(msg) diff --git a/ipykernel/tests/test_kernel_direct.py b/ipykernel/tests/test_kernel_direct.py index 276c02143..b0a09a860 100644 --- a/ipykernel/tests/test_kernel_direct.py +++ b/ipykernel/tests/test_kernel_direct.py @@ -30,6 +30,10 @@ async def test_direct_execute_request_aborting(kernel): assert reply["content"]["status"] == "aborted" +async def test_direct_execute_request_error(kernel): + await kernel.execute_request(None, None, None) + + async def test_complete_request(kernel): reply = await kernel.test_shell_message("complete_request", dict(code="hello", cursor_pos=0)) assert reply["header"]["msg_type"] == "complete_reply" @@ -45,6 +49,18 @@ async def test_history_request(kernel): "history_request", dict(hist_access_type="", output="", raw="") ) assert reply["header"]["msg_type"] == "history_reply" + reply = await kernel.test_shell_message( + "history_request", dict(hist_access_type="tail", output="", raw="") + ) + assert reply["header"]["msg_type"] == "history_reply" + reply = await kernel.test_shell_message( + "history_request", dict(hist_access_type="range", output="", raw="") + ) + assert reply["header"]["msg_type"] == "history_reply" + reply = await kernel.test_shell_message( + "history_request", dict(hist_access_type="search", output="", raw="") + ) + assert reply["header"]["msg_type"] == "history_reply" async def test_comm_info_request(kernel): @@ -137,6 +153,23 @@ def check_status(): await asyncio.sleep(0.1) +async def test_do_one_iteration(kernel): + kernel.msg_queue = asyncio.Queue() + await kernel.do_one_iteration() + + +async def test_publish_debug_event(kernel): + kernel._publish_debug_event({}) + + +async def test_connect_request(kernel): + await kernel.connect_request(kernel.shell_stream, "foo", {}) + + +async def test_send_interupt_children(kernel): + kernel._send_interupt_children() + + # TODO: this causes deadlock # async def test_direct_usage_request(kernel): # reply = await kernel.test_control_message("usage_request", {}) diff --git a/pyproject.toml b/pyproject.toml index cedc9a3ff..984b8b477 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,6 +128,9 @@ filterwarnings= [ # Ignore our own warnings "ignore:The `stream` parameter of `getpass.getpass` will have no effect:UserWarning", + # IPython warnings + "ignore: `Completer.complete` is pending deprecation since IPython 6.0 and will be replaced by `Completer.completions`:PendingDeprecationWarning", + # Ignore jupyter_client warnings "ignore:unclosed Date: Fri, 25 Nov 2022 21:35:09 -0600 Subject: [PATCH 0933/1195] Add more tests (#1034) --- ipykernel/iostream.py | 2 +- ipykernel/tests/test_io.py | 57 ++++++++++++++++++++++++++++++- ipykernel/tests/test_zmq_shell.py | 55 ++++++++++++++++++++++++++++- ipykernel/zmqshell.py | 5 +-- 4 files changed, 114 insertions(+), 5 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index e6a03d1ef..5ea77452e 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -264,7 +264,7 @@ def __getattr__(self, attr): super().__getattr__(attr) # type:ignore[misc] def __setattr__(self, attr, value): - if attr == "io_thread" or (attr.startswith("__" and attr.endswith("__"))): + if attr == "io_thread" or (attr.startswith("__") and attr.endswith("__")): super().__setattr__(attr, value) else: warnings.warn( diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index 7e2950976..d0e70e10f 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -1,12 +1,13 @@ """Test IO capturing functionality""" import io +import warnings import pytest import zmq from jupyter_client.session import Session -from ipykernel.iostream import IOPubThread, OutStream +from ipykernel.iostream import MASTER, BackgroundSocket, IOPubThread, OutStream def test_io_api(): @@ -51,3 +52,57 @@ def test_io_isatty(): stream = OutStream(session, thread, "stdout", isatty=True) assert stream.isatty() + + +def test_io_thread(): + ctx = zmq.Context() + pub = ctx.socket(zmq.PUB) + thread = IOPubThread(pub) + thread._setup_pipe_in() + msg = [thread._pipe_uuid, b"a"] + thread._handle_pipe_msg(msg) + ctx1, pipe = thread._setup_pipe_out() + pipe.close() + thread._pipe_in.close() + thread._check_mp_mode = lambda: MASTER + thread._really_send([b"hi"]) + ctx1.destroy() + thread.close() + thread.close() + thread._really_send(None) + + +def test_background_socket(): + ctx = zmq.Context() + pub = ctx.socket(zmq.PUB) + thread = IOPubThread(pub) + sock = BackgroundSocket(thread) + assert sock.__class__ == BackgroundSocket + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + sock.linger = 101 + assert thread.socket.linger == 101 + assert sock.io_thread == thread + sock.send(b"hi") + + +def test_outstream(): + session = Session() + ctx = zmq.Context() + pub = ctx.socket(zmq.PUB) + thread = IOPubThread(pub) + thread.start() + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + stream = OutStream(session, pub, "stdout") + stream = OutStream(session, thread, "stdout", pipe=object()) + + stream = OutStream(session, thread, "stdout", isatty=True, echo=io.StringIO()) + with pytest.raises(io.UnsupportedOperation): + stream.fileno() + stream._watch_pipe_fd() + stream.flush() + stream.write("hi") + stream.writelines(["ab", "cd"]) + assert stream.writable diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index 1a637f53e..8d6780ba6 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -3,15 +3,24 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import os import unittest +import warnings from queue import Queue from threading import Thread +from unittest.mock import MagicMock +import pytest import zmq from jupyter_client.session import Session from traitlets import Int -from ipykernel.zmqshell import ZMQDisplayPublisher +from ipykernel.zmqshell import ( + InteractiveShell, + KernelMagics, + ZMQDisplayPublisher, + ZMQInteractiveShell, +) class NoReturnDisplayHook: @@ -201,5 +210,49 @@ def test_unregister_hook(self): self.assertFalse(second) +def test_magics(tmp_path): + context = zmq.Context() + socket = context.socket(zmq.PUB) + shell = InteractiveShell() + shell.user_ns["hi"] = 1 + magics = KernelMagics(shell) + + def find_edit_target(*args): + return str(tmp_path), 0, 1 + + tmp_file = tmp_path / "test.txt" + tmp_file.write_text("hi", "utf8") + magics._find_edit_target = find_edit_target + magics.edit("hi") + magics.clear([]) + magics.less(str(tmp_file)) + if os.name == "posix": + magics.man("ls") + magics.autosave("10") + + socket.close() + context.destroy() + + +def test_zmq_interactive_shell(kernel): + shell = ZMQInteractiveShell() + + with pytest.raises(RuntimeError): + shell.enable_gui("tk") + + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + shell.data_pub_class = MagicMock() + shell.data_pub + shell.kernel = kernel + shell.set_next_input("hi") + assert shell.get_parent() is None + if os.name == "posix": + shell.system_piped("ls") + else: + shell.system_piped("dir") + shell.ask_exit() + + if __name__ == "__main__": unittest.main() diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index f79090c95..23458d304 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -285,7 +285,7 @@ def edit(self, parameter_s="", last_call=None): opts, args = self.parse_options(parameter_s, "prn:") try: - filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call) + filename, lineno, _ = self._find_edit_target(self.shell, args, opts, last_call) except MacroToEdit: # TODO: Implement macro editing over 2 processes. print("Macro editing not yet implemented in 2-process model.") @@ -326,7 +326,8 @@ def less(self, arg_s): if arg_s.endswith(".py"): cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False)) else: - cont = open(arg_s).read() + with open(arg_s) as fid: + cont = fid.read() page.page(cont) more = line_magic("more")(less) From ad778689e1197e4458b21312161f4438f2f0d46f Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 26 Nov 2022 07:45:19 -0600 Subject: [PATCH 0934/1195] More coverage improvements (#1035) --- ipykernel/jsonutil.py | 2 +- ipykernel/tests/test_parentpoller.py | 39 ++++++++++++++++++++++++++++ pyproject.toml | 3 ++- 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 ipykernel/tests/test_parentpoller.py diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index eafc171c3..a60032d30 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -74,7 +74,7 @@ def encode_images(format_dict): return format_dict -def json_clean(obj): +def json_clean(obj): # pragma: no cover """Deprecated, this is a no-op for jupyter-client>=7. Clean an object to ensure it's safe to encode in JSON. diff --git a/ipykernel/tests/test_parentpoller.py b/ipykernel/tests/test_parentpoller.py new file mode 100644 index 000000000..a83cc392a --- /dev/null +++ b/ipykernel/tests/test_parentpoller.py @@ -0,0 +1,39 @@ +import os +import sys +import warnings +from unittest import mock + +import pytest + +from ipykernel.parentpoller import ParentPollerUnix, ParentPollerWindows + + +@pytest.mark.skipif(os.name == "nt", reason="only works on posix") +def test_parent_poller_unix(): + poller = ParentPollerUnix() + with mock.patch("os.getppid", lambda: 1): + + def exit_mock(*args): + sys.exit(1) + + with mock.patch("os._exit", exit_mock), pytest.raises(SystemExit): + poller.run() + + def mock_getppid(): + raise ValueError("hi") + + with mock.patch("os.getppid", mock_getppid), pytest.raises(ValueError): + poller.run() + + +@pytest.mark.skipif(os.name != "nt", reason="only works on windows") +def test_parent_poller_windows(): + poller = ParentPollerWindows(interrupt_handle=1) + + def mock_wait(*args, **kwargs): + return -1 + + with mock.patch("ctypes.windll.kernel32.WaitForMultipleObjects", mock_wait): + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + poller.run() diff --git a/pyproject.toml b/pyproject.toml index 984b8b477..a1f575cb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,7 +157,8 @@ omit = [ "ipykernel/tests/*", "ipykernel/debugger.py", "ipykernel/eventloops.py", - "ipykernel/pickleutil.py" + "ipykernel/pickleutil.py", + "ipykernel/serialize.py" ] [tool.flake8] From 252c406a82fb9bab4071bfbc287b7a24a51752d8 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 26 Nov 2022 17:17:03 -0600 Subject: [PATCH 0935/1195] Add more testing and deprecate the Gtk event loops (#1036) --- ipykernel/comm/comm.py | 2 +- ipykernel/gui/gtk3embed.py | 3 + ipykernel/gui/gtkembed.py | 3 + ipykernel/inprocess/tests/test_kernel.py | 199 ++++++++++------------- ipykernel/kernelapp.py | 6 +- ipykernel/tests/test_comm.py | 7 + ipykernel/tests/test_embed_kernel.py | 22 ++- ipykernel/tests/test_eventloop.py | 9 +- ipykernel/tests/test_kernel.py | 4 + ipykernel/tests/test_kernelapp.py | 62 +++++++ ipykernel/trio_runner.py | 1 + pyproject.toml | 10 +- 12 files changed, 204 insertions(+), 124 deletions(-) create mode 100644 ipykernel/tests/test_comm.py create mode 100644 ipykernel/tests/test_kernelapp.py diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index a143cfed2..97c2f1b86 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -14,7 +14,7 @@ # this is the class that will be created if we do comm.create_comm class BaseComm(comm.base_comm.BaseComm): - kernel: Optional[Kernel] + kernel: Optional[Kernel] = None def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): """Helper for sending a comm message on IOPub""" diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index baef9fd71..42c7e181f 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -12,6 +12,9 @@ # ----------------------------------------------------------------------------- # stdlib import sys +import warnings + +warnings.warn("The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning) # Third-party import gi diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index ed6647226..a97a62a06 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -12,6 +12,9 @@ # ----------------------------------------------------------------------------- # stdlib import sys +import warnings + +warnings.warn("The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning) # Third-party import gobject diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 2a2311fe0..47dab33d2 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -1,14 +1,11 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import asyncio import sys -import unittest from contextlib import contextmanager from io import StringIO import pytest -import tornado from IPython.utils.io import capture_output from jupyter_client.session import Session @@ -20,44 +17,6 @@ orig_msg = Session.msg -def _init_asyncio_patch(): - """set default asyncio policy to be compatible with tornado - - Tornado 6 (at least) is not compatible with the default - asyncio implementation on Windows - - Pick the older SelectorEventLoopPolicy on Windows - if the known-incompatible default policy is in use. - - do this as early as possible to make it a low priority and overrideable - - ref: https://github.com/tornadoweb/tornado/issues/2608 - - FIXME: if/when tornado supports the defaults in asyncio, - remove and bump tornado requirement for py38 - """ - if ( - sys.platform.startswith("win") - and sys.version_info >= (3, 8) - and tornado.version_info < (6, 1) - ): - import asyncio - - try: - from asyncio import ( - WindowsProactorEventLoopPolicy, - WindowsSelectorEventLoopPolicy, - ) - except ImportError: - pass - # not affected - else: - if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: - # WindowsProactorEventLoopPolicy is not compatible with tornado 6 - # fallback to the pre-3.8 default of Selector - asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) - - def _inject_cell_id(_self, *args, **kwargs): """ This patch jupyter_client.session:Session.msg to add a cell_id to the return message metadata @@ -78,79 +37,85 @@ def patch_cell_id(): Session.msg = orig_msg -class InProcessKernelTestCase(unittest.TestCase): - def setUp(self): - _init_asyncio_patch() - self.km = InProcessKernelManager() - self.km.start_kernel() - self.kc = self.km.client() - self.kc.start_channels() - self.kc.wait_for_ready() - - def test_with_cell_id(self): - - with patch_cell_id(): - self.kc.execute("1+1") - - def test_pylab(self): - """Does %pylab work in the in-process kernel?""" - _ = pytest.importorskip("matplotlib", reason="This test requires matplotlib") - kc = self.kc - kc.execute("%pylab") - out, err = assemble_output(kc.get_iopub_msg) - self.assertIn("matplotlib", out) - - def test_raw_input(self): - """Does the in-process kernel handle raw_input correctly?""" - io = StringIO("foobar\n") - sys_stdin = sys.stdin - sys.stdin = io - try: - self.kc.execute("x = input()") - finally: - sys.stdin = sys_stdin - assert self.km.kernel.shell.user_ns.get("x") == "foobar" - - @pytest.mark.skipif("__pypy__" in sys.builtin_module_names, reason="fails on pypy") - def test_stdout(self): - """Does the in-process kernel correctly capture IO?""" - kernel = InProcessKernel() - - with capture_output() as io: - kernel.shell.run_cell('print("foo")') - assert io.stdout == "foo\n" - - kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) - kernel.frontends.append(kc) - kc.execute('print("bar")') - out, err = assemble_output(kc.get_iopub_msg) - assert out == "bar\n" - - @pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing") - def test_capfd(self): - """Does correctly capture fd""" - kernel = InProcessKernel() - - with capture_output() as io: - kernel.shell.run_cell('print("foo")') - assert io.stdout == "foo\n" - - kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) - kernel.frontends.append(kc) - kc.execute("import os") - kc.execute('os.system("echo capfd")') - out, err = assemble_output(kc.iopub_channel) - assert out == "capfd\n" - - def test_getpass_stream(self): - "Tests that kernel getpass accept the stream parameter" - kernel = InProcessKernel() - kernel._allow_stdin = True - kernel._input_request = lambda *args, **kwargs: None - - kernel.getpass(stream="non empty") - - def test_do_execute(self): - kernel = InProcessKernel() - asyncio.run(kernel.do_execute("a=1", True)) - assert kernel.shell.user_ns["a"] == 1 +@pytest.fixture() +def kc(): + km = InProcessKernelManager() + km.start_kernel() + kc = km.client() + kc.start_channels() + kc.wait_for_ready() + yield kc + + +def test_with_cell_id(kc): + + with patch_cell_id(): + kc.execute("1+1") + + +def test_pylab(kc): + """Does %pylab work in the in-process kernel?""" + _ = pytest.importorskip("matplotlib", reason="This test requires matplotlib") + kc.execute("%pylab") + out, err = assemble_output(kc.get_iopub_msg) + assert "matplotlib" in out + + +def test_raw_input(kc): + """Does the in-process kernel handle raw_input correctly?""" + io = StringIO("foobar\n") + sys_stdin = sys.stdin + sys.stdin = io + try: + kc.execute("x = input()") + finally: + sys.stdin = sys_stdin + assert kc.kernel.shell.user_ns.get("x") == "foobar" + + +@pytest.mark.skipif("__pypy__" in sys.builtin_module_names, reason="fails on pypy") +def test_stdout(kc): + """Does the in-process kernel correctly capture IO?""" + kernel = InProcessKernel() + + with capture_output() as io: + kernel.shell.run_cell('print("foo")') + assert io.stdout == "foo\n" + + kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) + kernel.frontends.append(kc) + kc.execute('print("bar")') + out, err = assemble_output(kc.get_iopub_msg) + assert out == "bar\n" + + +@pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing") +def test_capfd(kc): + """Does correctly capture fd""" + kernel = InProcessKernel() + + with capture_output() as io: + kernel.shell.run_cell('print("foo")') + assert io.stdout == "foo\n" + + kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) + kernel.frontends.append(kc) + kc.execute("import os") + kc.execute('os.system("echo capfd")') + out, err = assemble_output(kc.iopub_channel) + assert out == "capfd\n" + + +def test_getpass_stream(kc): + "Tests that kernel getpass accept the stream parameter" + kernel = InProcessKernel() + kernel._allow_stdin = True + kernel._input_request = lambda *args, **kwargs: None + + kernel.getpass(stream="non empty") + + +async def test_do_execute(kc): + kernel = InProcessKernel() + await kernel.do_execute("a=1", True) + assert kernel.shell.user_ns["a"] == 1 diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index bb96c1b35..8b4a5ca55 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -168,7 +168,9 @@ def abs_connection_file(self): trio_loop = Bool(False, help="Set main event loop.").tag(config=True) quiet = Bool(True, help="Only send stdout/stderr to output stream").tag(config=True) outstream_class = DottedObjectName( - "ipykernel.iostream.OutStream", help="The importstring for the OutStream factory" + "ipykernel.iostream.OutStream", + help="The importstring for the OutStream factory", + allow_none=True, ).tag(config=True) displayhook_class = DottedObjectName( "ipykernel.displayhook.ZMQDisplayHook", help="The importstring for the DisplayHook factory" @@ -717,7 +719,7 @@ def start(self): launch_new_instance = IPKernelApp.launch_instance -def main(): +def main(): # pragma: no cover """Run an IPKernel as an application""" app = IPKernelApp.instance() app.initialize() diff --git a/ipykernel/tests/test_comm.py b/ipykernel/tests/test_comm.py new file mode 100644 index 000000000..0fc5991bc --- /dev/null +++ b/ipykernel/tests/test_comm.py @@ -0,0 +1,7 @@ +from ipykernel.comm import Comm + + +async def test_comm(kernel): + c = Comm() + c.kernel = kernel + c.publish_msg("foo") diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index cb8229fed..6ce306cd2 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -1,4 +1,4 @@ -"""test IPython.embed_kernel()""" +"""test embed_kernel""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. @@ -6,6 +6,7 @@ import json import os import sys +import threading import time from contextlib import contextmanager from subprocess import PIPE, Popen @@ -15,6 +16,8 @@ from jupyter_client import BlockingKernelClient from jupyter_core import paths +from ipykernel.embed import IPKernelApp, embed_kernel + SETUP_TIMEOUT = 60 TIMEOUT = 15 @@ -193,3 +196,20 @@ def test_embed_kernel_reentrant(): client.execute("get_ipython().exit_now = True") msg = client.get_shell_msg(timeout=TIMEOUT) time.sleep(0.2) + + +def test_embed_kernel_func(): + from types import ModuleType + + module = ModuleType("test") + + def trigger_stop(): + time.sleep(1) + app = IPKernelApp.instance() + app.io_loop.add_callback(app.io_loop.stop) + IPKernelApp.clear_instance() + + thread = threading.Thread(target=trigger_stop) + thread.start() + + embed_kernel(module, outstream_class=None) diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index 8f345cc1c..13a536ed0 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -5,11 +5,12 @@ import sys import threading import time +from unittest.mock import MagicMock import pytest import tornado -from ipykernel.eventloops import enable_gui, loop_asyncio, loop_tk +from ipykernel.eventloops import enable_gui, loop_asyncio, loop_cocoa, loop_tk from .utils import execute, flush_channels, start_new_kernel @@ -88,3 +89,9 @@ def do_thing(): @windows_skip def test_enable_gui(kernel): enable_gui("tk", kernel) + + +@pytest.mark.skipif(sys.platform != "darwin", reason="MacOS-only") +def test_cocoa_loop(kernel): + kernel.shell = MagicMock() + loop_cocoa(kernel) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index ad38f8797..0647ef442 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -534,6 +534,10 @@ def _start_children(): platform.python_implementation() == "PyPy", reason="does not work on PyPy", ) +@pytest.mark.skipif( + sys.platform.lower() == "linux", + reason="Stalls on linux", +) def test_shutdown_subprocesses(): """Kernel exits after polite shutdown_request""" with new_kernel() as kc: diff --git a/ipykernel/tests/test_kernelapp.py b/ipykernel/tests/test_kernelapp.py new file mode 100644 index 000000000..319672709 --- /dev/null +++ b/ipykernel/tests/test_kernelapp.py @@ -0,0 +1,62 @@ +import os +import threading +import time +from unittest.mock import MagicMock, patch + +import pytest + +from ipykernel.kernelapp import IPKernelApp + +from .conftest import MockKernel + +try: + import trio +except ImportError: + trio = None + + +@pytest.mark.skipif(os.name == "nt", reason="requires ipc") +def test_init_ipc_socket(): + app = IPKernelApp(transport="ipc") + app.init_sockets() + app.cleanup_connection_file() + app.close() + + +def test_blackhole(): + app = IPKernelApp() + app.no_stderr = True + app.no_stdout = True + app.init_blackhole() + + +def test_start_app(): + app = IPKernelApp() + app.kernel = MockKernel() + app.kernel.shell = MagicMock() + + def trigger_stop(): + time.sleep(1) + app.io_loop.add_callback(app.io_loop.stop) + + thread = threading.Thread(target=trigger_stop) + thread.start() + app.init_sockets() + app.start() + app.cleanup_connection_file() + app.kernel.destroy() + app.close() + + +@pytest.mark.skipif(trio is None, reason="requires trio") +def test_trio_loop(): + app = IPKernelApp(trio_loop=True) + app.kernel = MockKernel() + app.kernel.shell = MagicMock() + app.init_sockets() + with patch("ipykernel.trio_runner.TrioRunner.run", lambda _: None): + app.start() + app.cleanup_connection_file() + app.io_loop.add_callback(app.io_loop.stop) + app.kernel.destroy() + app.close() diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index 7b8f2166c..280501acf 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -19,6 +19,7 @@ def initialize(self, kernel, io_loop): kernel.shell.magics_manager.magics["line"]["autoawait"] = lambda _: warnings.warn( "Autoawait isn't allowed in Trio background loop mode." ) + self._interrupted = False bg_thread = threading.Thread(target=io_loop.start, daemon=True, name="TornadoBackground") bg_thread.start() diff --git a/pyproject.toml b/pyproject.toml index a1f575cb7..0a65a8a97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,13 +120,14 @@ testpaths = [ asyncio_mode = "auto" timeout = 300 # Restore this setting to debug failures -# timeout_method = "thread" +#timeout_method = "thread" filterwarnings= [ # Fail on warnings "error", # Ignore our own warnings "ignore:The `stream` parameter of `getpass.getpass` will have no effect:UserWarning", + "ignore:has moved to ipyparallel:DeprecationWarning", # IPython warnings "ignore: `Completer.complete` is pending deprecation since IPython 6.0 and will be replaced by `Completer.completions`:PendingDeprecationWarning", @@ -155,10 +156,15 @@ exclude_lines = [ [tool.coverage.run] omit = [ "ipykernel/tests/*", + "ipykernel/datapub.py", "ipykernel/debugger.py", "ipykernel/eventloops.py", + "ipykernel/log.py", "ipykernel/pickleutil.py", - "ipykernel/serialize.py" + "ipykernel/serialize.py", + "ipykernel/inprocess/tests/*", + "ipykernel/gui/*", + "ipykernel/pylab/*", ] [tool.flake8] From 6ad420bd46db36e7b76bb6df2e702c588e77bc4e Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 28 Nov 2022 13:35:46 +0000 Subject: [PATCH 0936/1195] Publish 6.18.1 SHA256 hashes: ipykernel-6.18.1-py3-none-any.whl: 18c298565218e602939dd03b56206912433ebdb6b5800afd9177bbce8d96318b ipykernel-6.18.1.tar.gz: 71f21ce281da5a4e73ec4a7ecdf98802d9e65d58cdb7e22ff824ca994ce5114b --- CHANGELOG.md | 28 ++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb92a07eb..0256833b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ +## 6.18.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.0...252c406a82fb9bab4071bfbc287b7a24a51752d8)) + +### Bugs fixed + +- fix: use comm package in backwards compatible way [#1028](https://github.com/ipython/ipykernel/pull/1028) ([@maartenbreddels](https://github.com/maartenbreddels)) + +### Maintenance and upkeep improvements + +- Add more testing and deprecate the Gtk event loops [#1036](https://github.com/ipython/ipykernel/pull/1036) ([@blink1073](https://github.com/blink1073)) +- More coverage improvements [#1035](https://github.com/ipython/ipykernel/pull/1035) ([@blink1073](https://github.com/blink1073)) +- Add more tests [#1034](https://github.com/ipython/ipykernel/pull/1034) ([@blink1073](https://github.com/blink1073)) +- Add more kernel tests [#1032](https://github.com/ipython/ipykernel/pull/1032) ([@blink1073](https://github.com/blink1073)) +- Add more coverage and add Readme badges [#1031](https://github.com/ipython/ipykernel/pull/1031) ([@blink1073](https://github.com/blink1073)) +- Clean up testing and coverage [#1030](https://github.com/ipython/ipykernel/pull/1030) ([@blink1073](https://github.com/blink1073)) +- Use base setup dependency type [#1029](https://github.com/ipython/ipykernel/pull/1029) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-11-21&to=2022-11-28&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-11-21..2022-11-28&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-11-21..2022-11-28&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AmartinRenou+updated%3A2022-11-21..2022-11-28&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-11-21..2022-11-28&type=Issues) + + + ## 6.18.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.17.1...ce0b6c296bc19223d426892657878f28af0ec206)) @@ -23,8 +49,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-11-09..2022-11-21&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AmartinRenou+updated%3A2022-11-09..2022-11-21&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-11-09..2022-11-21&type=Issues) - - ## 6.17.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.17.0...a06867786eaf0c5d9454d2df61f354c7012a625e)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index fb643e455..bc6104d84 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.18.0" +__version__ = "6.18.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From e7aecf55211b29c03d45c593dcc232b988e7b971 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Nov 2022 20:14:35 -0600 Subject: [PATCH 0937/1195] [pre-commit.ci] pre-commit autoupdate (#1039) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 72acb394e..eeb5a93b2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: end-of-file-fixer - id: check-case-conflict @@ -72,7 +72,7 @@ repos: stages: [manual] - repo: https://github.com/john-hen/Flake8-pyproject - rev: 1.2.0 + rev: 1.2.1 hooks: - id: Flake8-pyproject alias: flake8 From a38167b1c689130df231fa77d712827bc75a8ba6 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 29 Nov 2022 04:56:27 +0100 Subject: [PATCH 0938/1195] Configurables needs to be configurable (#1037) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- ipykernel/ipkernel.py | 23 ++++++++++++++++++++--- ipykernel/tests/test_ipkernel_direct.py | 4 ++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index bf07bd006..3bacf0a3b 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -12,10 +12,20 @@ import comm from IPython.core import release from IPython.utils.tokenutil import line_at_cursor, token_at_cursor -from traitlets import Any, Bool, Instance, List, Type, observe, observe_compat +from traitlets import ( + Any, + Bool, + HasTraits, + Instance, + List, + Type, + observe, + observe_compat, +) from zmq.eventloop.zmqstream import ZMQStream from .comm.comm import BaseComm +from .comm.manager import CommManager from .compiler import XCachingCompiler from .debugger import Debugger, _is_debugpy_available from .eventloops import _use_appnope @@ -40,12 +50,18 @@ _EXPERIMENTAL_KEY_NAME = "_jupyter_types_experimental" -def create_comm(*args, **kwargs): +def _create_comm(*args, **kwargs): """Create a new Comm.""" return BaseComm(*args, **kwargs) -comm.create_comm = create_comm +def _get_comm_manager(*args, **kwargs): + """Create a new CommManager.""" + return CommManager(*args, **kwargs) + + +comm.create_comm = _create_comm +comm.get_comm_manager = _get_comm_manager class IPythonKernel(KernelBase): @@ -112,6 +128,7 @@ def __init__(self, **kwargs): self.comm_manager = comm.get_comm_manager() + assert isinstance(self.comm_manager, HasTraits) self.shell.configurables.append(self.comm_manager) comm_msg_types = ["comm_open", "comm_msg", "comm_close"] for msg_type in comm_msg_types: diff --git a/ipykernel/tests/test_ipkernel_direct.py b/ipykernel/tests/test_ipkernel_direct.py index 903d72f3e..7e42a35b5 100644 --- a/ipykernel/tests/test_ipkernel_direct.py +++ b/ipykernel/tests/test_ipkernel_direct.py @@ -7,7 +7,7 @@ import zmq from IPython.core.history import DummyDB -from ipykernel.ipkernel import BaseComm, IPythonKernel, create_comm +from ipykernel.ipkernel import BaseComm, IPythonKernel, _create_comm from .conftest import MockIPyKernel @@ -187,7 +187,7 @@ async def fake_poll_control_queue(): def test_create_comm(): - assert isinstance(create_comm(), BaseComm) + assert isinstance(_create_comm(), BaseComm) def test_finish_metadata(ipkernel: IPythonKernel): From 8f54589f7197a12d053c8dc315bceb2b3d7a4b12 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 29 Nov 2022 11:40:05 +0000 Subject: [PATCH 0939/1195] Publish 6.18.2 SHA256 hashes: ipykernel-6.18.2-py3-none-any.whl: 4ae64026a3cca0e1843d0fccce6690d7fda2b05267e4609547185797de999823 ipykernel-6.18.2.tar.gz: 53714bb8b2207fda4a9fac645460c38612e04b63be34b4ba70febbebc8cfc4a0 --- CHANGELOG.md | 20 ++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0256833b0..0f57240a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ +## 6.18.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.1...a38167b1c689130df231fa77d712827bc75a8ba6)) + +### Bugs fixed + +- Configurables needs to be configurable [#1037](https://github.com/ipython/ipykernel/pull/1037) ([@Carreau](https://github.com/Carreau)) + +### Maintenance and upkeep improvements + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-11-28&to=2022-11-29&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-11-28..2022-11-29&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2022-11-28..2022-11-29&type=Issues) | [@fperez](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afperez+updated%3A2022-11-28..2022-11-29&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-11-28..2022-11-29&type=Issues) + + + ## 6.18.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.0...252c406a82fb9bab4071bfbc287b7a24a51752d8)) @@ -26,8 +44,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-11-21..2022-11-28&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-11-21..2022-11-28&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AmartinRenou+updated%3A2022-11-21..2022-11-28&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-11-21..2022-11-28&type=Issues) - - ## 6.18.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.17.1...ce0b6c296bc19223d426892657878f28af0ec206)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index bc6104d84..1d09df410 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.18.1" +__version__ = "6.18.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From c0f5b7e3a5287c288eff477ae70848decf25332d Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Wed, 30 Nov 2022 00:18:59 +0100 Subject: [PATCH 0940/1195] Fix Comm interface for downstream users (#1042) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester Fixes https://github.com/ipython/ipykernel/issues/1040 --- .github/workflows/downstream.yml | 29 +++++++++++++++++++++++++++++ ipykernel/comm/comm.py | 26 +++++++++++++++++++++++++- ipykernel/comm/manager.py | 6 ++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 0d314fa44..ab1f10350 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -25,6 +25,20 @@ jobs: package_name: nbclient env_values: IPYKERNEL_CELL_NAME=\ + ipywidgets: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Run Test + uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 + with: + package_name: ipywidgets + jupyter_client: runs-on: ubuntu-latest steps: @@ -69,3 +83,18 @@ jobs: cd jupyter_kernel_test pip install -e ".[test]" python test_ipykernel.py + + downstream_check: # This job does nothing and is only used for the branch protection + if: always() + needs: + - nbclient + - ipywidgets + - jupyter_client + - ipyparallel + - jupyter_kernel_test + runs-on: ubuntu-latest + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 97c2f1b86..f1fb0c88f 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -3,10 +3,12 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import uuid from typing import Optional import comm.base_comm import traitlets.config +from traitlets import Bool, Bytes, Instance, Unicode, default from ipykernel.jsonutil import json_clean from ipykernel.kernelbase import Kernel @@ -43,8 +45,30 @@ def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): class Comm(traitlets.config.LoggingConfigurable, BaseComm): """Class for communicating between a Frontend and a Kernel""" + kernel = Instance("ipykernel.kernelbase.Kernel", allow_none=True) # type:ignore[assignment] + comm_id = Unicode() + primary = Bool(True, help="Am I the primary or secondary Comm?") + + target_name = Unicode("comm") + target_module = Unicode( + None, + allow_none=True, + help="""requirejs module from + which to load comm target.""", + ) + + topic = Bytes() + + @default("kernel") + def _default_kernel(self): + if Kernel.initialized(): + return Kernel.instance() + + @default("comm_id") + def _default_comm_id(self): + return uuid.uuid4().hex + def __init__(self, *args, **kwargs): - self.kernel = None # Comm takes positional arguments, LoggingConfigurable does not, so we explicitly forward arguments traitlets.config.LoggingConfigurable.__init__(self, **kwargs) BaseComm.__init__(self, *args, **kwargs) diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 0314d6ffa..6bf73ad81 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -5,10 +5,16 @@ import comm.base_comm +import traitlets import traitlets.config class CommManager(traitlets.config.LoggingConfigurable, comm.base_comm.CommManager): + + kernel = traitlets.Instance("ipykernel.kernelbase.Kernel") + comms = traitlets.Dict() + targets = traitlets.Dict() + def __init__(self, **kwargs): # CommManager doesn't take arguments, so we explicitly forward arguments traitlets.config.LoggingConfigurable.__init__(self, **kwargs) From a356bcc75198d069d26ff6b6c2a4e7a7887fd347 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 29 Nov 2022 23:26:35 +0000 Subject: [PATCH 0941/1195] Publish 6.18.3 SHA256 hashes: ipykernel-6.18.3-py3-none-any.whl: 7a11fbe6748ef89c6bf8e40c522cafc101b70e371277b30e964a522d628dae5d ipykernel-6.18.3.tar.gz: 9a3e57aaf00058721c27daa8323f53b6a04697d087eb78b1f739468cab50988c --- CHANGELOG.md | 18 ++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f57240a7..9a6ff8347 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.18.3 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.2...c0f5b7e3a5287c288eff477ae70848decf25332d)) + +### Bugs fixed + +- Fix Comm interface for downstream users [#1042](https://github.com/ipython/ipykernel/pull/1042) ([@maartenbreddels](https://github.com/maartenbreddels)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-11-29&to=2022-11-29&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-11-29..2022-11-29&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-11-29..2022-11-29&type=Issues) + + + ## 6.18.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.1...a38167b1c689130df231fa77d712827bc75a8ba6)) @@ -18,8 +34,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-11-28..2022-11-29&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2022-11-28..2022-11-29&type=Issues) | [@fperez](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afperez+updated%3A2022-11-28..2022-11-29&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-11-28..2022-11-29&type=Issues) - - ## 6.18.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.0...252c406a82fb9bab4071bfbc287b7a24a51752d8)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 1d09df410..e3b496c5d 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.18.2" +__version__ = "6.18.3" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From f38738d941683cfecd9af0efa7410eeeaac1b5ed Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Dec 2022 06:23:13 -0600 Subject: [PATCH 0942/1195] [pre-commit.ci] pre-commit autoupdate (#1045) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eeb5a93b2..1091dafa5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -49,7 +49,7 @@ repos: [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - repo: https://github.com/asottile/pyupgrade - rev: v3.2.2 + rev: v3.3.0 hooks: - id: pyupgrade args: [--py38-plus] @@ -72,7 +72,7 @@ repos: stages: [manual] - repo: https://github.com/john-hen/Flake8-pyproject - rev: 1.2.1 + rev: 1.2.2 hooks: - id: Flake8-pyproject alias: flake8 @@ -81,7 +81,7 @@ repos: stages: [manual] - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.28.0 + rev: v8.29.0 hooks: - id: eslint stages: [manual] From 4dc303364ca73b5185685807de41c7fe4b4a4dc7 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 7 Dec 2022 07:58:23 -0600 Subject: [PATCH 0943/1195] Adopt ruff and address lint (#1046) --- .github/dependabot.yml | 6 +- .github/workflows/ci.yml | 12 ++- .pre-commit-config.yaml | 84 +++------------ docs/conf.py | 2 +- examples/embedding/inprocess_qtconsole.py | 5 +- examples/embedding/inprocess_terminal.py | 5 +- examples/embedding/internal_ipkernel.py | 2 +- examples/embedding/ipkernel_wxapp.py | 1 + ipykernel/_eventloop_macos.py | 2 +- ipykernel/comm/__init__.py | 2 +- ipykernel/connect.py | 2 +- ipykernel/embed.py | 1 + ipykernel/eventloops.py | 2 +- ipykernel/gui/gtk3embed.py | 1 + ipykernel/gui/gtkembed.py | 1 + ipykernel/heartbeat.py | 3 +- ipykernel/inprocess/channels.py | 1 + ipykernel/inprocess/client.py | 1 + ipykernel/inprocess/ipkernel.py | 1 + ipykernel/inprocess/socket.py | 1 + ipykernel/inprocess/tests/test_kernel.py | 8 +- .../inprocess/tests/test_kernelmanager.py | 1 + ipykernel/ipkernel.py | 13 +-- ipykernel/kernelapp.py | 7 +- ipykernel/serialize.py | 9 +- ipykernel/tests/__init__.py | 4 +- ipykernel/tests/conftest.py | 17 +-- ipykernel/tests/test_async.py | 4 + ipykernel/tests/test_comm.py | 2 +- ipykernel/tests/test_connect.py | 6 +- ipykernel/tests/test_debugger.py | 10 +- ipykernel/tests/test_embed_kernel.py | 4 +- ipykernel/tests/test_eventloop.py | 6 +- ipykernel/tests/test_heartbeat.py | 2 + ipykernel/tests/test_io.py | 6 +- ipykernel/tests/test_ipkernel_direct.py | 41 +++---- ipykernel/tests/test_jsonutil.py | 4 +- ipykernel/tests/test_kernelapp.py | 4 +- ipykernel/tests/test_kernelspec.py | 8 +- ipykernel/tests/test_message_spec.py | 25 ++--- ipykernel/tests/test_zmq_shell.py | 4 +- ipykernel/tests/utils.py | 9 +- ipykernel/zmqshell.py | 1 + pyproject.toml | 100 ++++++++++++++---- 44 files changed, 222 insertions(+), 208 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f10a5bc1f..cbd920f6b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,10 @@ version: 2 updates: - # Set update schedule for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: - # Check for updates to GitHub Actions every weekday + interval: "weekly" + - package-ecosystem: "pip" + directory: "/" + schedule: interval: "weekly" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf533e2da..759a13cc8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -66,12 +66,18 @@ jobs: cd $HOME python -m ipykernel_launcher --help - pre_commit: + test_lint: + name: Test Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - uses: jupyterlab/maintainer-tools/.github/actions/pre-commit@v1 + - name: Run Linters + run: | + hatch run typing:test + hatch run lint:style + pipx run 'validate-pyproject[all]' pyproject.toml + pipx run doc8 --max-line-length=200 check_release: runs-on: ubuntu-latest @@ -177,7 +183,7 @@ jobs: - test_docs - test_without_debugpy - test_miniumum_versions - - pre_commit + - test_lint - test_prereleases - check_release - link_check diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1091dafa5..d048f624f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,6 @@ +ci: + autoupdate_schedule: monthly + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 @@ -10,88 +13,27 @@ repos: - id: check-case-conflict - id: check-toml - id: check-yaml - - id: debug-statements - exclude: ipykernel/kernelapp.py - id: forbid-new-submodules - id: check-builtin-literals - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 22.10.0 - hooks: - - id: black - args: ["--line-length", "100"] - - - repo: https://github.com/Carreau/velin - rev: 0.0.12 - hooks: - - id: velin - args: ["ipykernel"] - - - repo: https://github.com/PyCQA/isort - rev: 5.10.1 - hooks: - - id: isort - files: \.py$ - args: [--profile=black] - - - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.10.1 + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.19.2 hooks: - - id: validate-pyproject - stages: [manual] + - id: check-github-workflows - repo: https://github.com/executablebooks/mdformat rev: 0.7.16 hooks: - id: mdformat - additional_dependencies: - [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.0 - hooks: - - id: pyupgrade - args: [--py38-plus] - - - repo: https://github.com/PyCQA/doc8 - rev: v1.0.0 - hooks: - - id: doc8 - args: [--max-line-length=200] - stages: [manual] - - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.991 - hooks: - - id: mypy - exclude: "ipykernel.*tests" - args: ["--config-file", "pyproject.toml"] - additional_dependencies: - [tornado, jupyter_client, pytest, traitlets, jupyter_core] - stages: [manual] - - - repo: https://github.com/john-hen/Flake8-pyproject - rev: 1.2.2 - hooks: - - id: Flake8-pyproject - alias: flake8 - additional_dependencies: - ["flake8-bugbear==22.6.22", "flake8-implicit-str-concat==0.2.0"] - stages: [manual] - - - repo: https://github.com/pre-commit/mirrors-eslint - rev: v8.29.0 + - repo: https://github.com/psf/black + rev: 22.10.0 hooks: - - id: eslint - stages: [manual] + - id: black - - repo: https://github.com/sirosen/check-jsonschema - rev: 0.19.2 + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.165 hooks: - - id: check-jsonschema - name: "Check GitHub Workflows" - files: ^\.github/workflows/ - types: [yaml] - args: ["--schemafile", "https://json.schemastore.org/github-workflow"] - stages: [manual] + - id: ruff + args: ["--fix"] diff --git a/docs/conf.py b/docs/conf.py index 4a1a6fa5f..9a47e1769 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -66,7 +66,7 @@ here = os.path.dirname(__file__) version_py = os.path.join(here, os.pardir, "ipykernel", "_version.py") with open(version_py) as f: - exec(compile(f.read(), version_py, "exec"), version_ns) + exec(compile(f.read(), version_py, "exec"), version_ns) # noqa # The short X.Y version. version = "%i.%i" % version_ns["version_info"][:2] diff --git a/examples/embedding/inprocess_qtconsole.py b/examples/embedding/inprocess_qtconsole.py index 55e514343..3ac4ae485 100644 --- a/examples/embedding/inprocess_qtconsole.py +++ b/examples/embedding/inprocess_qtconsole.py @@ -30,10 +30,7 @@ def init_asyncio_patch(): import asyncio try: - from asyncio import ( - WindowsProactorEventLoopPolicy, - WindowsSelectorEventLoopPolicy, - ) + from asyncio import WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy except ImportError: pass # not affected diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index 4121619d0..2f8d5453d 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -30,10 +30,7 @@ def init_asyncio_patch(): import asyncio try: - from asyncio import ( - WindowsProactorEventLoopPolicy, - WindowsSelectorEventLoopPolicy, - ) + from asyncio import WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy except ImportError: pass # not affected diff --git a/examples/embedding/internal_ipkernel.py b/examples/embedding/internal_ipkernel.py index 7d126c17f..9aa2783a0 100644 --- a/examples/embedding/internal_ipkernel.py +++ b/examples/embedding/internal_ipkernel.py @@ -19,7 +19,7 @@ def mpl_kernel(gui): [ "python", "--matplotlib=%s" % gui, - #'--log-level=10' # noqa + #'--log-level=10' ] ) return kernel diff --git a/examples/embedding/ipkernel_wxapp.py b/examples/embedding/ipkernel_wxapp.py index 2caad5be2..fce802b64 100755 --- a/examples/embedding/ipkernel_wxapp.py +++ b/examples/embedding/ipkernel_wxapp.py @@ -24,6 +24,7 @@ import wx from internal_ipkernel import InternalIPKernel + # ----------------------------------------------------------------------------- # Functions and classes # ----------------------------------------------------------------------------- diff --git a/ipykernel/_eventloop_macos.py b/ipykernel/_eventloop_macos.py index 3a6692fce..f07ce6ffc 100644 --- a/ipykernel/_eventloop_macos.py +++ b/ipykernel/_eventloop_macos.py @@ -75,7 +75,7 @@ def C(classname): CFRunLoopAddTimer.restype = None CFRunLoopAddTimer.argtypes = [void_p, void_p, void_p] -kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, "kCFRunLoopCommonModes") +kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, "kCFRunLoopCommonModes") # noqa def _NSApp(): diff --git a/ipykernel/comm/__init__.py b/ipykernel/comm/__init__.py index 36b419d5e..d56cbe308 100644 --- a/ipykernel/comm/__init__.py +++ b/ipykernel/comm/__init__.py @@ -1,4 +1,4 @@ __all__ = ["Comm", "CommManager"] -from .comm import Comm # noqa +from .comm import Comm from .manager import CommManager diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 6baae872a..f76091674 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -120,7 +120,7 @@ def connect_qtconsole(connection_file=None, argv=None): stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != "win32"), - **kwargs + **kwargs, ) diff --git a/ipykernel/embed.py b/ipykernel/embed.py index 53c11119b..72f5d6c1b 100644 --- a/ipykernel/embed.py +++ b/ipykernel/embed.py @@ -10,6 +10,7 @@ from .kernelapp import IPKernelApp + # ----------------------------------------------------------------------------- # Code # ----------------------------------------------------------------------------- diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 0329926a3..b2a312d90 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -9,7 +9,7 @@ from functools import partial import zmq -from packaging.version import Version as V +from packaging.version import Version as V # noqa from traitlets.config.application import Application diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index 42c7e181f..eb190c845 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -23,6 +23,7 @@ gi.require_version("Gtk", "3.0") from gi.repository import GObject, Gtk + # ----------------------------------------------------------------------------- # Classes and functions # ----------------------------------------------------------------------------- diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index a97a62a06..e7a19c3b8 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -20,6 +20,7 @@ import gobject import gtk + # ----------------------------------------------------------------------------- # Classes and functions # ----------------------------------------------------------------------------- diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 091cdd372..1835c0d67 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -20,13 +20,14 @@ import zmq from jupyter_client.localinterfaces import localhost + # ----------------------------------------------------------------------------- # Code # ----------------------------------------------------------------------------- class Heartbeat(Thread): - "A simple ping-pong style heartbeat that runs in a thread." + """A simple ping-pong style heartbeat that runs in a thread.""" def __init__(self, context, addr=None): if addr is None: diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 84629ff5d..6fd8ede62 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -7,6 +7,7 @@ from jupyter_client.channelsabc import HBChannelABC + # ----------------------------------------------------------------------------- # Channel classes # ----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index d6bfc9a5f..9377fd1d3 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -27,6 +27,7 @@ # Local imports from .channels import InProcessChannel, InProcessHBChannel + # ----------------------------------------------------------------------------- # Main kernel Client class # ----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index f185ddc35..074a2588e 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -18,6 +18,7 @@ from .constants import INPROCESS_KEY from .socket import DummySocket + # ----------------------------------------------------------------------------- # Main kernel class # ----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index 477c36a47..e33e1d251 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -8,6 +8,7 @@ import zmq from traitlets import HasTraits, Instance, Int + # ----------------------------------------------------------------------------- # Dummy socket class # ----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 47dab33d2..947b5823e 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -31,10 +31,10 @@ def _inject_cell_id(_self, *args, **kwargs): @contextmanager def patch_cell_id(): try: - Session.msg = _inject_cell_id + Session.msg = _inject_cell_id # type:ignore yield finally: - Session.msg = orig_msg + Session.msg = orig_msg # type:ignore @pytest.fixture() @@ -107,10 +107,10 @@ def test_capfd(kc): def test_getpass_stream(kc): - "Tests that kernel getpass accept the stream parameter" + """Tests that kernel getpass accept the stream parameter""" kernel = InProcessKernel() kernel._allow_stdin = True - kernel._input_request = lambda *args, **kwargs: None + kernel._input_request = lambda *args, **kwargs: None # type:ignore kernel.getpass(stream="non empty") diff --git a/ipykernel/inprocess/tests/test_kernelmanager.py b/ipykernel/inprocess/tests/test_kernelmanager.py index 850f543ce..fb34c76b0 100644 --- a/ipykernel/inprocess/tests/test_kernelmanager.py +++ b/ipykernel/inprocess/tests/test_kernelmanager.py @@ -5,6 +5,7 @@ from ipykernel.inprocess.manager import InProcessKernelManager + # ----------------------------------------------------------------------------- # Test case # ----------------------------------------------------------------------------- diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 3bacf0a3b..f86f30451 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -12,16 +12,7 @@ import comm from IPython.core import release from IPython.utils.tokenutil import line_at_cursor, token_at_cursor -from traitlets import ( - Any, - Bool, - HasTraits, - Instance, - List, - Type, - observe, - observe_compat, -) +from traitlets import Any, Bool, HasTraits, Instance, List, Type, observe, observe_compat from zmq.eventloop.zmqstream import ZMQStream from .comm.comm import BaseComm @@ -629,7 +620,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): working.update(ns) code = f"{resultname} = {fname}(*{argname},**{kwargname})" try: - exec(code, shell.user_global_ns, shell.user_ns) + exec(code, shell.user_global_ns, shell.user_ns) # noqa result = working.get(resultname) finally: for key in ns: diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 8b4a5ca55..3f4aa708a 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -625,10 +625,7 @@ def _init_asyncio_patch(self): import asyncio try: - from asyncio import ( - WindowsProactorEventLoopPolicy, - WindowsSelectorEventLoopPolicy, - ) + from asyncio import WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy except ImportError: pass # not affected @@ -644,7 +641,7 @@ def init_pdb(self): With the non-interruptible version, stopping pdb() locks up the kernel in a non-recoverable state. """ - import pdb + import pdb # noqa from IPython.core import debugger diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 6b77d8536..cd7fa51ac 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -29,18 +29,19 @@ except ImportError: # Deprecated since ipykernel 4.3.0 from ipykernel.pickleutil import ( + PICKLE_PROTOCOL, + CannedObject, can, - uncan, can_sequence, - uncan_sequence, - CannedObject, istype, sequence_types, - PICKLE_PROTOCOL, + uncan, + uncan_sequence, ) from jupyter_client.session import MAX_BYTES, MAX_ITEMS + # ----------------------------------------------------------------------------- # Serialization Functions # ----------------------------------------------------------------------------- diff --git a/ipykernel/tests/__init__.py b/ipykernel/tests/__init__.py index d2606e108..013114bd1 100644 --- a/ipykernel/tests/__init__.py +++ b/ipykernel/tests/__init__.py @@ -12,7 +12,7 @@ pjoin = os.path.join tmp = None -patchers = [] +patchers: list = [] def setup(): @@ -41,7 +41,7 @@ def teardown(): p.stop() try: - shutil.rmtree(tmp) + shutil.rmtree(tmp) # type:ignore except OSError: # no such file pass diff --git a/ipykernel/tests/conftest.py b/ipykernel/tests/conftest.py index 5e1e192ad..45341cef6 100644 --- a/ipykernel/tests/conftest.py +++ b/ipykernel/tests/conftest.py @@ -1,6 +1,8 @@ import asyncio import logging import os +from typing import no_type_check +from unittest.mock import MagicMock import pytest import zmq @@ -16,7 +18,7 @@ import resource except ImportError: # Windows - resource = None + resource = None # type:ignore # Handle resource limit @@ -36,7 +38,7 @@ # Enforce selector event loop on Windows. if os.name == "nt": - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # type:ignore class KernelMixin: @@ -68,12 +70,14 @@ def destroy(self): socket.close() self.context.destroy() + @no_type_check async def test_shell_message(self, *args, **kwargs): msg_list = self._prep_msg(*args, **kwargs) await self.dispatch_shell(msg_list) self.shell_stream.flush() return await self._wait_for_msg() + @no_type_check async def test_control_message(self, *args, **kwargs): msg_list = self._prep_msg(*args, **kwargs) await self.process_control(msg_list) @@ -85,8 +89,8 @@ def _on_send(self, msg, *args, **kwargs): def _prep_msg(self, *args, **kwargs): self._reply = None - msg = self.session.msg(*args, **kwargs) - msg = self.session.serialize(msg) + raw_msg = self.session.msg(*args, **kwargs) + msg = self.session.serialize(raw_msg) return [zmq.Message(m) for m in msg] async def _wait_for_msg(self): @@ -100,7 +104,7 @@ def _send_interupt_children(self): pass -class MockKernel(KernelMixin, Kernel): +class MockKernel(KernelMixin, Kernel): # type:ignore implementation = "test" implementation_version = "1.0" language = "no-op" @@ -114,6 +118,7 @@ class MockKernel(KernelMixin, Kernel): def __init__(self, *args, **kwargs): self._initialize() + self.shell = MagicMock() super().__init__(*args, **kwargs) def do_execute( @@ -132,7 +137,7 @@ def do_execute( } -class MockIPyKernel(KernelMixin, IPythonKernel): +class MockIPyKernel(KernelMixin, IPythonKernel): # type:ignore def __init__(self, *args, **kwargs): self._initialize() super().__init__(*args, **kwargs) diff --git a/ipykernel/tests/test_async.py b/ipykernel/tests/test_async.py index 81a8e3d95..c58a24d9d 100644 --- a/ipykernel/tests/test_async.py +++ b/ipykernel/tests/test_async.py @@ -16,6 +16,8 @@ def setup_function(): def teardown_function(): + assert KC is not None + assert KM is not None KC.stop_channels() KM.shutdown_kernel(now=True) @@ -28,6 +30,8 @@ def test_async_await(): @pytest.mark.parametrize("asynclib", ["asyncio", "trio", "curio"]) def test_async_interrupt(asynclib, request): + assert KC is not None + assert KM is not None try: __import__(asynclib) except ImportError: diff --git a/ipykernel/tests/test_comm.py b/ipykernel/tests/test_comm.py index 0fc5991bc..3ab1e5c24 100644 --- a/ipykernel/tests/test_comm.py +++ b/ipykernel/tests/test_comm.py @@ -3,5 +3,5 @@ async def test_comm(kernel): c = Comm() - c.kernel = kernel + c.kernel = kernel # type:ignore c.publish_msg("foo") diff --git a/ipykernel/tests/test_connect.py b/ipykernel/tests/test_connect.py index 4bb998ae1..b4b739d97 100644 --- a/ipykernel/tests/test_connect.py +++ b/ipykernel/tests/test_connect.py @@ -7,18 +7,19 @@ import json import os from tempfile import TemporaryDirectory +from typing import no_type_check from unittest.mock import patch import pytest import zmq -from traitlets.config import Config +from traitlets.config.loader import Config from ipykernel import connect from ipykernel.kernelapp import IPKernelApp from .utils import TemporaryWorkingDirectory -sample_info = { +sample_info: dict = { "ip": "1.2.3.4", "transport": "ipc", "shell_port": 1, @@ -91,6 +92,7 @@ def test_port_bind_failure_raises(request): assert mock_try_bind.call_count == 1 +@no_type_check def test_port_bind_failure_recovery(request): try: errno.WSAEADDRINUSE diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index 6b9c817cd..200154cfc 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -121,7 +121,7 @@ def test_set_breakpoints(kernel_with_debug): assert reply["body"]["breakpoints"][0]["source"]["path"] == source r = wait_for_debug_request(kernel_with_debug, "debugInfo") - assert source in map(lambda b: b["source"], r["body"]["breakpoints"]) + assert source in map(lambda b: b["source"], r["body"]["breakpoints"]) # type:ignore # noqa r = wait_for_debug_request(kernel_with_debug, "configurationDone") assert r["success"] @@ -154,7 +154,7 @@ def test_stop_on_breakpoint(kernel_with_debug): kernel_with_debug.execute(code) # Wait for stop on breakpoint - msg = {"msg_type": "", "content": {}} + msg: dict = {"msg_type": "", "content": {}} while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) @@ -190,7 +190,7 @@ def f(a, b): kernel_with_debug.execute(code) # Wait for stop on breakpoint - msg = {"msg_type": "", "content": {}} + msg: dict = {"msg_type": "", "content": {}} while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) @@ -208,7 +208,7 @@ def test_rich_inspect_not_at_breakpoint(kernel_with_debug): get_reply(kernel_with_debug, msg_id) r = wait_for_debug_request(kernel_with_debug, "inspectVariables") - assert var_name in list(map(lambda v: v["name"], r["body"]["variables"])) + assert var_name in list(map(lambda v: v["name"], r["body"]["variables"])) # type:ignore # noqa reply = wait_for_debug_request( kernel_with_debug, @@ -246,7 +246,7 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): kernel_with_debug.execute(code) # Wait for stop on breakpoint - msg = {"msg_type": "", "content": {}} + msg: dict = {"msg_type": "", "content": {}} while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) diff --git a/ipykernel/tests/test_embed_kernel.py b/ipykernel/tests/test_embed_kernel.py index 6ce306cd2..ff97edfa5 100644 --- a/ipykernel/tests/test_embed_kernel.py +++ b/ipykernel/tests/test_embed_kernel.py @@ -13,10 +13,10 @@ import pytest from flaky import flaky -from jupyter_client import BlockingKernelClient +from jupyter_client.blocking.client import BlockingKernelClient from jupyter_core import paths -from ipykernel.embed import IPKernelApp, embed_kernel +from ipykernel.embed import IPKernelApp, embed_kernel # type:ignore[attr-defined] SETUP_TIMEOUT = 60 TIMEOUT = 15 diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index 13a536ed0..7d0063e61 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -5,7 +5,6 @@ import sys import threading import time -from unittest.mock import MagicMock import pytest import tornado @@ -25,6 +24,8 @@ def setup(): def teardown(): + assert KM is not None + assert KC is not None KC.stop_channels() KM.shutdown_kernel(now=True) @@ -37,6 +38,8 @@ def teardown(): @pytest.mark.skipif(tornado.version_info < (5,), reason="only relevant on tornado 5") def test_asyncio_interrupt(): + assert KM is not None + assert KC is not None flush_channels(KC) msg_id, content = execute("%gui asyncio", KC) assert content["status"] == "ok", content @@ -93,5 +96,4 @@ def test_enable_gui(kernel): @pytest.mark.skipif(sys.platform != "darwin", reason="MacOS-only") def test_cocoa_loop(kernel): - kernel.shell = MagicMock() loop_cocoa(kernel) diff --git a/ipykernel/tests/test_heartbeat.py b/ipykernel/tests/test_heartbeat.py index 7847595e8..e093d5eb2 100644 --- a/ipykernel/tests/test_heartbeat.py +++ b/ipykernel/tests/test_heartbeat.py @@ -4,6 +4,7 @@ # Distributed under the terms of the Modified BSD License. import errno +from typing import no_type_check from unittest.mock import patch import pytest @@ -28,6 +29,7 @@ def test_port_bind_success(): assert mock_try_bind.call_count == 1 +@no_type_check def test_port_bind_failure_recovery(): try: errno.WSAEADDRINUSE diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index d0e70e10f..f7fc8b3f4 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -40,7 +40,7 @@ def test_io_api(): with pytest.raises(io.UnsupportedOperation): stream.tell() with pytest.raises(TypeError): - stream.write(b"") + stream.write(b"") # type:ignore def test_io_isatty(): @@ -64,7 +64,7 @@ def test_io_thread(): ctx1, pipe = thread._setup_pipe_out() pipe.close() thread._pipe_in.close() - thread._check_mp_mode = lambda: MASTER + thread._check_mp_mode = lambda: MASTER # type:ignore thread._really_send([b"hi"]) ctx1.destroy() thread.close() @@ -105,4 +105,4 @@ def test_outstream(): stream.flush() stream.write("hi") stream.writelines(["ab", "cd"]) - assert stream.writable + assert stream.writable() diff --git a/ipykernel/tests/test_ipkernel_direct.py b/ipykernel/tests/test_ipkernel_direct.py index 7e42a35b5..0e65c9a8a 100644 --- a/ipykernel/tests/test_ipkernel_direct.py +++ b/ipykernel/tests/test_ipkernel_direct.py @@ -7,7 +7,8 @@ import zmq from IPython.core.history import DummyDB -from ipykernel.ipkernel import BaseComm, IPythonKernel, _create_comm +from ipykernel.comm.comm import BaseComm +from ipykernel.ipkernel import IPythonKernel, _create_comm from .conftest import MockIPyKernel @@ -19,7 +20,7 @@ class user_mod: __dict__ = {} -async def test_properities(ipkernel: IPythonKernel): +async def test_properities(ipkernel: IPythonKernel) -> None: ipkernel.user_module = user_mod() ipkernel.user_ns = {} @@ -29,7 +30,7 @@ async def test_direct_kernel_info_request(ipkernel): assert reply["header"]["msg_type"] == "kernel_info_reply" -async def test_direct_execute_request(ipkernel: MockIPyKernel): +async def test_direct_execute_request(ipkernel: MockIPyKernel) -> None: reply = await ipkernel.test_shell_message("execute_request", dict(code="hello", silent=False)) assert reply["header"]["msg_type"] == "execute_reply" reply = await ipkernel.test_shell_message( @@ -106,7 +107,7 @@ async def test_direct_interrupt_request(ipkernel): # assert reply['header']['msg_type'] == 'usage_reply' -async def test_is_complete_request(ipkernel: MockIPyKernel): +async def test_is_complete_request(ipkernel: MockIPyKernel) -> None: reply = await ipkernel.test_shell_message("is_complete_request", dict(code="hello")) assert reply["header"]["msg_type"] == "is_complete_reply" setattr(ipkernel, "shell.input_transformer_manager", None) @@ -114,7 +115,7 @@ async def test_is_complete_request(ipkernel: MockIPyKernel): assert reply["header"]["msg_type"] == "is_complete_reply" -def test_do_apply(ipkernel: MockIPyKernel): +def test_do_apply(ipkernel: MockIPyKernel) -> None: from ipyparallel import pack_apply_message def hello(): @@ -134,22 +135,22 @@ async def test_direct_clear(ipkernel): ipkernel.do_clear() -async def test_cancel_on_sigint(ipkernel: IPythonKernel): - future = asyncio.Future() +async def test_cancel_on_sigint(ipkernel: IPythonKernel) -> None: + future: asyncio.Future = asyncio.Future() with ipkernel._cancel_on_sigint(future): pass future.set_result(None) -def test_dispatch_debugpy(ipkernel: IPythonKernel): +def test_dispatch_debugpy(ipkernel: IPythonKernel) -> None: msg = ipkernel.session.msg("debug_request", {}) msg_list = ipkernel.session.serialize(msg) ipkernel.dispatch_debugpy([zmq.Message(m) for m in msg_list]) -async def test_start(ipkernel: IPythonKernel): - shell_future = asyncio.Future() - control_future = asyncio.Future() +async def test_start(ipkernel: IPythonKernel) -> None: + shell_future: asyncio.Future = asyncio.Future() + control_future: asyncio.Future = asyncio.Future() async def fake_dispatch_queue(): shell_future.set_result(None) @@ -157,8 +158,8 @@ async def fake_dispatch_queue(): async def fake_poll_control_queue(): control_future.set_result(None) - ipkernel.dispatch_queue = fake_dispatch_queue - ipkernel.poll_control_queue = fake_poll_control_queue + ipkernel.dispatch_queue = fake_dispatch_queue # type:ignore + ipkernel.poll_control_queue = fake_poll_control_queue # type:ignore ipkernel.start() ipkernel.debugpy_stream = None ipkernel.start() @@ -167,9 +168,9 @@ async def fake_poll_control_queue(): await control_future -async def test_start_no_debugpy(ipkernel: IPythonKernel): - shell_future = asyncio.Future() - control_future = asyncio.Future() +async def test_start_no_debugpy(ipkernel: IPythonKernel) -> None: + shell_future: asyncio.Future = asyncio.Future() + control_future: asyncio.Future = asyncio.Future() async def fake_dispatch_queue(): shell_future.set_result(None) @@ -177,8 +178,8 @@ async def fake_dispatch_queue(): async def fake_poll_control_queue(): control_future.set_result(None) - ipkernel.dispatch_queue = fake_dispatch_queue - ipkernel.poll_control_queue = fake_poll_control_queue + ipkernel.dispatch_queue = fake_dispatch_queue # type:ignore + ipkernel.poll_control_queue = fake_poll_control_queue # type:ignore ipkernel.debugpy_stream = None ipkernel.start() @@ -190,13 +191,13 @@ def test_create_comm(): assert isinstance(_create_comm(), BaseComm) -def test_finish_metadata(ipkernel: IPythonKernel): +def test_finish_metadata(ipkernel: IPythonKernel) -> None: reply_content = dict(status="error", ename="UnmetDependency") metadata = ipkernel.finish_metadata({}, {}, reply_content) assert metadata["dependencies_met"] is False -async def test_do_debug_request(ipkernel: IPythonKernel): +async def test_do_debug_request(ipkernel: IPythonKernel) -> None: msg = ipkernel.session.msg("debug_request", {}) msg_list = ipkernel.session.serialize(msg) await ipkernel.do_debug_request(msg) diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 0d8edbb71..8fdb35495 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -14,7 +14,7 @@ from .. import jsonutil from ..jsonutil import encode_images, json_clean -JUPYTER_CLIENT_MAJOR_VERSION = jupyter_client_version[0] +JUPYTER_CLIENT_MAJOR_VERSION: int = jupyter_client_version[0] # type:ignore class MyInt: @@ -60,7 +60,7 @@ def test(): for val, jval in pairs: if jval is None: - jval = val + jval = val # type:ignore out = json_clean(val) # validate our cleanup assert out == jval diff --git a/ipykernel/tests/test_kernelapp.py b/ipykernel/tests/test_kernelapp.py index 319672709..9a7b1f92e 100644 --- a/ipykernel/tests/test_kernelapp.py +++ b/ipykernel/tests/test_kernelapp.py @@ -1,7 +1,7 @@ import os import threading import time -from unittest.mock import MagicMock, patch +from unittest.mock import patch import pytest @@ -33,7 +33,6 @@ def test_blackhole(): def test_start_app(): app = IPKernelApp() app.kernel = MockKernel() - app.kernel.shell = MagicMock() def trigger_stop(): time.sleep(1) @@ -52,7 +51,6 @@ def trigger_stop(): def test_trio_loop(): app = IPKernelApp(trio_loop=True) app.kernel = MockKernel() - app.kernel.shell = MagicMock() app.init_sockets() with patch("ipykernel.trio_runner.TrioRunner.run", lambda _: None): app.start() diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index 31edff5bc..24771bc1d 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -110,8 +110,8 @@ def test_install_profile(): with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]): install(profile="Test") - spec = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json") - with open(spec) as f: + spec_file = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json") + with open(spec_file) as f: spec = json.load(f) assert spec["display_name"].endswith(" [profile=Test]") assert spec["argv"][-2:] == ["--profile", "Test"] @@ -123,8 +123,8 @@ def test_install_display_name_overrides_profile(): with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]): install(display_name="Display", profile="Test") - spec = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json") - with open(spec) as f: + spec_file = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json") + with open(spec_file) as f: spec = json.load(f) assert spec["display_name"] == "Display" diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 9ab41ae0f..1ba5f7651 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -7,27 +7,18 @@ import sys from queue import Empty -import jupyter_client import pytest -from packaging.version import Version as V -from traitlets import ( - Bool, - Dict, - Enum, - HasTraits, - Integer, - List, - TraitError, - Unicode, - observe, -) +from jupyter_client._version import version_info +from jupyter_client.blocking.client import BlockingKernelClient +from packaging.version import Version as V # noqa +from traitlets import Bool, Dict, Enum, HasTraits, Integer, List, TraitError, Unicode, observe from .utils import TIMEOUT, execute, flush_channels, get_reply, start_global_kernel # ----------------------------------------------------------------------------- # Globals # ----------------------------------------------------------------------------- -KC = None +KC: BlockingKernelClient = None # type:ignore def setup(): @@ -168,7 +159,7 @@ class CompleteReply(Reply): matches = List(Unicode()) cursor_start = Integer() cursor_end = Integer() - status = Unicode() + status = Unicode() # type:ignore class LanguageInfo(Reference): @@ -224,7 +215,7 @@ class ExecuteInput(Reference): class Error(ExecuteReplyError): """Errors are the same as ExecuteReply, but without status""" - status = None # no status field + status = None # type:ignore # no status field class Stream(Reference): @@ -515,7 +506,7 @@ def test_connect_request(): @pytest.mark.skipif( - jupyter_client.version_info < (5, 0), + version_info < (5, 0), reason="earlier Jupyter Client don't have comm_info", ) def test_comm_info_request(): diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index 8d6780ba6..c0cc50516 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -15,7 +15,7 @@ from jupyter_client.session import Session from traitlets import Int -from ipykernel.zmqshell import ( +from ipykernel.zmqshell import ( # type:ignore InteractiveShell, KernelMagics, ZMQDisplayPublisher, @@ -111,7 +111,7 @@ def hook(msg): self.disp_pub.register_hook(hook) assert self.disp_pub._hooks == [hook] - q = Queue() + q: Queue = Queue() def set_thread_hooks(): q.put(self.disp_pub._hooks) diff --git a/ipykernel/tests/utils.py b/ipykernel/tests/utils.py index 8ca55b1e7..b1b4119f0 100644 --- a/ipykernel/tests/utils.py +++ b/ipykernel/tests/utils.py @@ -13,12 +13,13 @@ from time import time from jupyter_client import manager +from jupyter_client.blocking.client import BlockingKernelClient STARTUP_TIMEOUT = 60 TIMEOUT = 100 -KM = None -KC = None +KM: manager.KernelManager = None # type:ignore +KC: BlockingKernelClient = None # type:ignore def start_new_kernel(**kwargs): @@ -132,11 +133,11 @@ def stop_global_kernel(): """Stop the global shared kernel instance, if it exists""" global KM, KC KC.stop_channels() - KC = None + KC = None # type:ignore if KM is None: return KM.shutdown_kernel(now=True) - KM = None + KM = None # type:ignore def new_kernel(argv=None): diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 23458d304..acc1c95d3 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -38,6 +38,7 @@ from ipykernel.displayhook import ZMQShellDisplayHook from ipykernel.jsonutil import encode_images, json_clean + # ----------------------------------------------------------------------------- # Functions and classes # ----------------------------------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 0a65a8a97..33c05466e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,6 +62,8 @@ cov = [ "curio", "trio", ] +lint = ["black>=22.6.0", "mdformat>0.7", "ruff>=0.0.156"] +typing = ["mypy>=0.990"] [tool.hatch.version] path = "ipykernel/_version.py" @@ -92,9 +94,28 @@ features = ["test", "cov"] test = "python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered {args}" nowarn = "test -W default {args}" +[tool.hatch.envs.typing] +features = ["test", "typing"] +dependencies = ["mypy>=0.990"] +[tool.hatch.envs.typing.scripts] +test = "mypy --install-types --non-interactive {args:.}" + +[tool.hatch.envs.lint] +features = ["lint"] +[tool.hatch.envs.lint.scripts] +style = [ + "ruff {args:.}", + "black --check --diff {args:.}", + "mdformat --check {args:docs *.md}" +] +fmt = [ + "black {args:.}", + "ruff --fix {args:.}", + "mdformat {args:docs *.md}" +] + [tool.mypy] check_untyped_defs = true -disallow_any_generics = true disallow_incomplete_defs = true disallow_untyped_decorators = true follow_imports = "normal" @@ -167,23 +188,66 @@ omit = [ "ipykernel/pylab/*", ] -[tool.flake8] -ignore = "E501, W503, E402" -builtins = "c, get_config" -exclude = [ - ".cache", - ".github", - "docs", - "setup.py", +[tool.black] +line-length = 100 +skip-string-normalization = true +target-version = ["py37"] + +[tool.ruff] +target-version = "py37" +line-length = 100 +select = [ + "A", "B", "C", "E", "F", "FBT", "I", "N", "Q", "RUF", "S", "T", + "UP", "W", "YTT", ] -enable-extensions = "G" -extend-ignore = [ - "G001", "G002", "G004", "G200", "G201", "G202", - # black adds spaces around ':' - "E203", +ignore = [ + # Allow non-abstract empty methods in abstract base classes + "B027", + # Ignore McCabe complexity + "C901", + # Allow boolean positional values in function calls, like `dict.get(... True)` + "FBT003", + # Use of `assert` detected + "S101", + # Line too long + "E501", + # Relative imports are banned + "I252", + # Boolean ... in function definition + "FBT001", "FBT002", + # Module level import not at top of file + "E402", + # A001/A002/A003 .. is shadowing a python builtin + "A001", "A002", "A003", + # Possible hardcoded password + "S105", "S106", + # Q000 Single quotes found but double quotes preferred + "Q000", + # N806 Variable `B` in function should be lowercase + "N806", + # T201 `print` found + "T201", + # N802 Function name `CreateWellKnownSid` should be lowercase + "N802", "N803", + # C408 Unnecessary `dict` call (rewrite as a literal) + "C408", + # N801 Class name `directional_link` should use CapWords convention + "N801", ] -per-file-ignores = [ - # B011: Do not call assert False since python -O removes these calls - # F841 local variable 'foo' is assigned to but never used - "ipykernel/tests/*: B011", "F841", +unfixable = [ + # Don't touch print statements + "T201", + # Don't touch noqa lines + "RUF100", ] + +[tool.ruff.per-file-ignores] +# B011 Do not call assert False since python -O removes these calls +# F841 local variable 'foo' is assigned to but never used +# C408 Unnecessary `dict` call +# E402 Module level import not at top of file +# T201 `print` found +# B007 Loop control variable `i` not used within the loop body. +# N802 Function name `assertIn` should be lowercase +# F841 Local variable `t` is assigned to but never used +"ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841"] From 2c80e6c31e4912b2deaf5276b27568ba5088ad97 Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Wed, 7 Dec 2022 22:26:25 +0100 Subject: [PATCH 0944/1195] Fix: there can be only one comm_manager (#1049) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester Fixes https://github.com/ipython/ipykernel/issues/1043 --- ipykernel/comm/comm.py | 3 ++ ipykernel/ipkernel.py | 13 +++++- ipykernel/tests/test_comm.py | 88 ++++++++++++++++++++++++++++++++++-- pyproject.toml | 4 +- 4 files changed, 101 insertions(+), 7 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index f1fb0c88f..c06d233bb 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -71,6 +71,9 @@ def _default_comm_id(self): def __init__(self, *args, **kwargs): # Comm takes positional arguments, LoggingConfigurable does not, so we explicitly forward arguments traitlets.config.LoggingConfigurable.__init__(self, **kwargs) + for name in self.trait_names(): + if name in kwargs: + kwargs.pop(name) BaseComm.__init__(self, *args, **kwargs) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index f86f30451..4dfe59c46 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -5,6 +5,7 @@ import getpass import signal import sys +import threading import typing as t from contextlib import contextmanager from functools import partial @@ -46,9 +47,19 @@ def _create_comm(*args, **kwargs): return BaseComm(*args, **kwargs) +# there can only be one comm manager in a ipykernel process +_comm_lock = threading.Lock() +_comm_manager: t.Optional[CommManager] = None + + def _get_comm_manager(*args, **kwargs): """Create a new CommManager.""" - return CommManager(*args, **kwargs) + global _comm_manager + if _comm_manager is None: + with _comm_lock: + if _comm_manager is None: + _comm_manager = CommManager(*args, **kwargs) + return _comm_manager comm.create_comm = _create_comm diff --git a/ipykernel/tests/test_comm.py b/ipykernel/tests/test_comm.py index 3ab1e5c24..9dd49f82e 100644 --- a/ipykernel/tests/test_comm.py +++ b/ipykernel/tests/test_comm.py @@ -1,7 +1,87 @@ -from ipykernel.comm import Comm +from ipykernel.comm import Comm, CommManager +from ipykernel.ipkernel import IPythonKernel -async def test_comm(kernel): - c = Comm() - c.kernel = kernel # type:ignore +def test_comm(kernel): + manager = CommManager(kernel=kernel) + kernel.comm_manager = manager + + c = Comm(kernel=kernel) + msgs = [] + + def on_close(msg): + msgs.append(msg) + + def on_message(msg): + msgs.append(msg) + c.publish_msg("foo") + c.open({}) + c.on_msg(on_message) + c.on_close(on_close) + c.handle_msg({}) + c.handle_close({}) + c.close() + assert len(msgs) == 2 + + +def test_comm_manager(kernel): + manager = CommManager(kernel=kernel) + msgs = [] + + def foo(comm, msg): + msgs.append(msg) + comm.close() + + def fizz(comm, msg): + raise RuntimeError('hi') + + def on_close(msg): + msgs.append(msg) + + def on_msg(msg): + msgs.append(msg) + + manager.register_target("foo", foo) + manager.register_target("fizz", fizz) + + kernel.comm_manager = manager + comm = Comm() + comm.on_msg(on_msg) + comm.on_close(on_close) + manager.register_comm(comm) + + assert manager.get_comm(comm.comm_id) == comm + assert manager.get_comm('foo') is None + + msg = dict(content=dict(comm_id=comm.comm_id, target_name='foo')) + manager.comm_open(None, None, msg) + assert len(msgs) == 1 + msg['content']['target_name'] = 'bar' + manager.comm_open(None, None, msg) + assert len(msgs) == 1 + msg = dict(content=dict(comm_id=comm.comm_id, target_name='fizz')) + manager.comm_open(None, None, msg) + assert len(msgs) == 1 + + manager.register_comm(comm) + assert manager.get_comm(comm.comm_id) == comm + msg = dict(content=dict(comm_id=comm.comm_id)) + manager.comm_msg(None, None, msg) + assert len(msgs) == 2 + msg['content']['comm_id'] = 'foo' + manager.comm_msg(None, None, msg) + assert len(msgs) == 2 + + manager.register_comm(comm) + assert manager.get_comm(comm.comm_id) == comm + msg = dict(content=dict(comm_id=comm.comm_id)) + manager.comm_close(None, None, msg) + assert len(msgs) == 3 + + assert comm._closed + + +def test_comm_in_manager(ipkernel: IPythonKernel) -> None: + comm = Comm() + assert comm.comm_id in ipkernel.comm_manager.comms diff --git a/pyproject.toml b/pyproject.toml index 33c05466e..f1e536d78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,8 +27,8 @@ requires-python = ">=3.8" dependencies = [ "debugpy>=1.0", "ipython>=7.23.1", - "comm>=0.1", - "traitlets>=5.1.0", + "comm>=0.1.1", + "traitlets>=5.4.0", "jupyter_client>=6.1.12", "tornado>=6.1", "matplotlib-inline>=0.1", From b13c46ac5fa800359748386c64f89402ac0e204e Mon Sep 17 00:00:00 2001 From: blink1073 Date: Wed, 7 Dec 2022 22:15:51 +0000 Subject: [PATCH 0945/1195] Publish 6.19.0 SHA256 hashes: ipykernel-6.19.0-py3-none-any.whl: 851aa3f9cbbec6918136ada733069a6709934b8106a743495070cf46917eb9a9 ipykernel-6.19.0.tar.gz: 7aabde9e201c4a8f43000f4be0d057f91df13b906ea646acd9047fcb85600b9b --- CHANGELOG.md | 22 ++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a6ff8347..193fce941 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ +## 6.19.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.3...2c80e6c31e4912b2deaf5276b27568ba5088ad97)) + +### Bugs fixed + +- Fix: there can be only one comm_manager [#1049](https://github.com/ipython/ipykernel/pull/1049) ([@maartenbreddels](https://github.com/maartenbreddels)) + +### Maintenance and upkeep improvements + +- Adopt ruff and address lint [#1046](https://github.com/ipython/ipykernel/pull/1046) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-11-29&to=2022-12-07&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-11-29..2022-12-07&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-11-29..2022-12-07&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-11-29..2022-12-07&type=Issues) + + + ## 6.18.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.2...c0f5b7e3a5287c288eff477ae70848decf25332d)) @@ -16,8 +36,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-11-29..2022-11-29&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-11-29..2022-11-29&type=Issues) - - ## 6.18.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.1...a38167b1c689130df231fa77d712827bc75a8ba6)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index e3b496c5d..3d8287018 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.18.3" +__version__ = "6.19.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 5e1b155207c506f01df5808b1ba41f868a10f097 Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Thu, 8 Dec 2022 13:24:32 +0100 Subject: [PATCH 0946/1195] fix: too many arguments dropped when passing to base comm constructor (#1051) Co-authored-by: martinRenou Closes https://github.com/ipython/ipykernel/issues/1050 --- ipykernel/comm/comm.py | 5 ++--- ipykernel/tests/test_comm.py | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index c06d233bb..295232e14 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -71,9 +71,8 @@ def _default_comm_id(self): def __init__(self, *args, **kwargs): # Comm takes positional arguments, LoggingConfigurable does not, so we explicitly forward arguments traitlets.config.LoggingConfigurable.__init__(self, **kwargs) - for name in self.trait_names(): - if name in kwargs: - kwargs.pop(name) + # drop arguments not in BaseComm + kwargs.pop("kernel", None) BaseComm.__init__(self, *args, **kwargs) diff --git a/ipykernel/tests/test_comm.py b/ipykernel/tests/test_comm.py index 9dd49f82e..f8b076413 100644 --- a/ipykernel/tests/test_comm.py +++ b/ipykernel/tests/test_comm.py @@ -6,7 +6,7 @@ def test_comm(kernel): manager = CommManager(kernel=kernel) kernel.comm_manager = manager - c = Comm(kernel=kernel) + c = Comm(kernel=kernel, target_name="bar") msgs = [] def on_close(msg): @@ -23,6 +23,7 @@ def on_message(msg): c.handle_close({}) c.close() assert len(msgs) == 2 + assert c.target_name == "bar" def test_comm_manager(kernel): From 6bb9279be1c8677a706fa2f03cfe7d3c6d19143e Mon Sep 17 00:00:00 2001 From: blink1073 Date: Thu, 8 Dec 2022 12:30:22 +0000 Subject: [PATCH 0947/1195] Publish 6.19.1 SHA256 hashes: ipykernel-6.19.1-py3-none-any.whl: 8b358a5aaa77c7e4b6cec22b03a910ab6c2968a449dced12082b6a6c56404083 ipykernel-6.19.1.tar.gz: d472029d14408273265a7b0ec6d1923f1b1f51d8643f125c6a6881e5e6e56a39 --- CHANGELOG.md | 18 ++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 193fce941..b2a929765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.19.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.0...5e1b155207c506f01df5808b1ba41f868a10f097)) + +### Bugs fixed + +- fix: too many arguments dropped when passing to base comm constructor [#1051](https://github.com/ipython/ipykernel/pull/1051) ([@maartenbreddels](https://github.com/maartenbreddels)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-12-07&to=2022-12-08&type=c)) + +[@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-12-07..2022-12-08&type=Issues) + + + ## 6.19.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.3...2c80e6c31e4912b2deaf5276b27568ba5088ad97)) @@ -20,8 +36,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-11-29..2022-12-07&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-11-29..2022-12-07&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-11-29..2022-12-07&type=Issues) - - ## 6.18.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.2...c0f5b7e3a5287c288eff477ae70848decf25332d)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 3d8287018..73e9e1cfe 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.19.0" +__version__ = "6.19.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 3c125ad5aa27de2ff412d7690de051115f175104 Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Thu, 8 Dec 2022 11:14:14 -0500 Subject: [PATCH 0948/1195] Fix error in `%edit` magic (#1053) --- ipykernel/tests/test_zmq_shell.py | 6 +----- ipykernel/zmqshell.py | 4 +--- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index c0cc50516..52e11e43a 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -217,13 +217,9 @@ def test_magics(tmp_path): shell.user_ns["hi"] = 1 magics = KernelMagics(shell) - def find_edit_target(*args): - return str(tmp_path), 0, 1 - tmp_file = tmp_path / "test.txt" tmp_file.write_text("hi", "utf8") - magics._find_edit_target = find_edit_target - magics.edit("hi") + magics.edit(str(tmp_file)) magics.clear([]) magics.less(str(tmp_file)) if os.name == "posix": diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index acc1c95d3..4e7b9ecc5 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -203,8 +203,6 @@ class KernelMagics(Magics): # the magics which this class needs to implement differently from the base # class, or that are unique to it. - _find_edit_target = CodeMagics._find_edit_target - @line_magic def edit(self, parameter_s="", last_call=None): """Bring up an editor and execute the resulting code. @@ -286,7 +284,7 @@ def edit(self, parameter_s="", last_call=None): opts, args = self.parse_options(parameter_s, "prn:") try: - filename, lineno, _ = self._find_edit_target(self.shell, args, opts, last_call) + filename, lineno, _ = CodeMagics._find_edit_target(self.shell, args, opts, last_call) except MacroToEdit: # TODO: Implement macro editing over 2 processes. print("Macro editing not yet implemented in 2-process model.") From 1239e95c4e46890618d870dca7be90849a4cd489 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Thu, 8 Dec 2022 16:17:36 +0000 Subject: [PATCH 0949/1195] Publish 6.19.2 SHA256 hashes: ipykernel-6.19.2-py3-none-any.whl: 1374a55c57ca7a7286c3d8b15799cd76e1a2381b6b1fea99c494b955988926b6 ipykernel-6.19.2.tar.gz: 1ab68d3d3654196266baa93990055413e167263ffbe4cfe834f871bcd3d3506d --- CHANGELOG.md | 18 ++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2a929765..2ea418337 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.19.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.1...3c125ad5aa27de2ff412d7690de051115f175104)) + +### Bugs fixed + +- Fix error in `%edit` magic [#1053](https://github.com/ipython/ipykernel/pull/1053) ([@ccordoba12](https://github.com/ccordoba12)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-12-08&to=2022-12-08&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-12-08..2022-12-08&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2022-12-08..2022-12-08&type=Issues) + + + ## 6.19.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.0...5e1b155207c506f01df5808b1ba41f868a10f097)) @@ -16,8 +32,6 @@ [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-12-07..2022-12-08&type=Issues) - - ## 6.19.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.18.3...2c80e6c31e4912b2deaf5276b27568ba5088ad97)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 73e9e1cfe..68c195cd1 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.19.1" +__version__ = "6.19.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From a7455bcb428ebbab1b9e78ed1539695878cad63e Mon Sep 17 00:00:00 2001 From: GRcharles <115260549+GRcharles@users.noreply.github.com> Date: Wed, 14 Dec 2022 15:31:00 +0000 Subject: [PATCH 0950/1195] format dates as ISO8601 (#1057) * format dates as ISO8601 format dates as ISO8601 - currently works for datetimes, but not dates. Follow up to https://github.com/ipython/ipykernel/pull/18 * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update test_jsonutil.py * Update jsonutil.py * Update test_jsonutil.py * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update test_jsonutil.py * fix date test Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/jsonutil.py | 4 ++-- ipykernel/tests/test_jsonutil.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index a60032d30..41f63ac5b 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -8,7 +8,7 @@ import re import types from binascii import b2a_base64 -from datetime import datetime +from datetime import date, datetime from jupyter_client._version import version_info as jupyter_client_version @@ -155,7 +155,7 @@ def json_clean(obj): # pragma: no cover for k, v in obj.items(): out[str(k)] = json_clean(v) return out - if isinstance(obj, datetime): + if isinstance(obj, datetime) or isinstance(obj, date): return obj.strftime(ISO8601) # we don't understand it, it's probably an unserializable object diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 8fdb35495..0189bc83b 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -6,7 +6,7 @@ import json import numbers from binascii import a2b_base64 -from datetime import datetime +from datetime import date, datetime import pytest from jupyter_client._version import version_info as jupyter_client_version @@ -54,6 +54,7 @@ def test(): ((x for x in range(3)), [0, 1, 2]), (iter([1, 2]), [1, 2]), (datetime(1991, 7, 3, 12, 00), "1991-07-03T12:00:00.000000"), + (date(1991, 7, 3), "1991-07-03T00:00:00.000000"), (MyFloat(), 3.14), (MyInt(), 389), ] From 04e1d753e328194be391b62ffd22662f0fe6dc56 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 14 Dec 2022 09:41:08 -0600 Subject: [PATCH 0951/1195] Fix lint (#1058) fix lint --- .pre-commit-config.yaml | 2 +- examples/embedding/ipkernel_wxapp.py | 1 - ipykernel/embed.py | 1 - ipykernel/gui/gtk3embed.py | 1 - ipykernel/gui/gtkembed.py | 1 - ipykernel/heartbeat.py | 1 - ipykernel/inprocess/channels.py | 1 - ipykernel/inprocess/client.py | 1 - ipykernel/inprocess/ipkernel.py | 1 - ipykernel/inprocess/socket.py | 1 - ipykernel/inprocess/tests/test_kernelmanager.py | 1 - ipykernel/serialize.py | 1 - ipykernel/zmqshell.py | 1 - pyproject.toml | 9 ++++----- 14 files changed, 5 insertions(+), 18 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d048f624f..f5fe3326b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -33,7 +33,7 @@ repos: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.165 + rev: v0.0.177 hooks: - id: ruff args: ["--fix"] diff --git a/examples/embedding/ipkernel_wxapp.py b/examples/embedding/ipkernel_wxapp.py index fce802b64..2caad5be2 100755 --- a/examples/embedding/ipkernel_wxapp.py +++ b/examples/embedding/ipkernel_wxapp.py @@ -24,7 +24,6 @@ import wx from internal_ipkernel import InternalIPKernel - # ----------------------------------------------------------------------------- # Functions and classes # ----------------------------------------------------------------------------- diff --git a/ipykernel/embed.py b/ipykernel/embed.py index 72f5d6c1b..53c11119b 100644 --- a/ipykernel/embed.py +++ b/ipykernel/embed.py @@ -10,7 +10,6 @@ from .kernelapp import IPKernelApp - # ----------------------------------------------------------------------------- # Code # ----------------------------------------------------------------------------- diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index eb190c845..42c7e181f 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -23,7 +23,6 @@ gi.require_version("Gtk", "3.0") from gi.repository import GObject, Gtk - # ----------------------------------------------------------------------------- # Classes and functions # ----------------------------------------------------------------------------- diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index e7a19c3b8..a97a62a06 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -20,7 +20,6 @@ import gobject import gtk - # ----------------------------------------------------------------------------- # Classes and functions # ----------------------------------------------------------------------------- diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 1835c0d67..3f10997c6 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -20,7 +20,6 @@ import zmq from jupyter_client.localinterfaces import localhost - # ----------------------------------------------------------------------------- # Code # ----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 6fd8ede62..84629ff5d 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -7,7 +7,6 @@ from jupyter_client.channelsabc import HBChannelABC - # ----------------------------------------------------------------------------- # Channel classes # ----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 9377fd1d3..d6bfc9a5f 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -27,7 +27,6 @@ # Local imports from .channels import InProcessChannel, InProcessHBChannel - # ----------------------------------------------------------------------------- # Main kernel Client class # ----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 074a2588e..f185ddc35 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -18,7 +18,6 @@ from .constants import INPROCESS_KEY from .socket import DummySocket - # ----------------------------------------------------------------------------- # Main kernel class # ----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index e33e1d251..477c36a47 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -8,7 +8,6 @@ import zmq from traitlets import HasTraits, Instance, Int - # ----------------------------------------------------------------------------- # Dummy socket class # ----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/tests/test_kernelmanager.py b/ipykernel/inprocess/tests/test_kernelmanager.py index fb34c76b0..850f543ce 100644 --- a/ipykernel/inprocess/tests/test_kernelmanager.py +++ b/ipykernel/inprocess/tests/test_kernelmanager.py @@ -5,7 +5,6 @@ from ipykernel.inprocess.manager import InProcessKernelManager - # ----------------------------------------------------------------------------- # Test case # ----------------------------------------------------------------------------- diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index cd7fa51ac..616410c81 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -41,7 +41,6 @@ from jupyter_client.session import MAX_BYTES, MAX_ITEMS - # ----------------------------------------------------------------------------- # Serialization Functions # ----------------------------------------------------------------------------- diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 4e7b9ecc5..ff418b863 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -38,7 +38,6 @@ from ipykernel.displayhook import ZMQShellDisplayHook from ipykernel.jsonutil import encode_images, json_clean - # ----------------------------------------------------------------------------- # Functions and classes # ----------------------------------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index f1e536d78..9904b54b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -62,8 +62,6 @@ cov = [ "curio", "trio", ] -lint = ["black>=22.6.0", "mdformat>0.7", "ruff>=0.0.156"] -typing = ["mypy>=0.990"] [tool.hatch.version] path = "ipykernel/_version.py" @@ -95,13 +93,14 @@ test = "python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-miss nowarn = "test -W default {args}" [tool.hatch.envs.typing] -features = ["test", "typing"] +features = ["test"] dependencies = ["mypy>=0.990"] [tool.hatch.envs.typing.scripts] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -features = ["lint"] +dependencies = ["black==22.10.0", "mdformat>0.7", "ruff==0.0.177"] +detached = true [tool.hatch.envs.lint.scripts] style = [ "ruff {args:.}", @@ -212,7 +211,7 @@ ignore = [ # Line too long "E501", # Relative imports are banned - "I252", + "TID252", # Boolean ... in function definition "FBT001", "FBT002", # Module level import not at top of file From 0925d09075280beb23c009ca0d361f73e5402e27 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 19 Dec 2022 08:22:08 -0600 Subject: [PATCH 0952/1195] Fix comms and add qtconsole downstream test (#1056) Fixes https://github.com/ipython/ipykernel/issues/1059 --- .github/workflows/downstream.yml | 36 ++++++++++++++++++++++++++++++++ ipykernel/comm/comm.py | 16 ++++++++------ ipykernel/comm/manager.py | 4 ++-- ipykernel/tests/test_comm.py | 12 +++++++---- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index ab1f10350..57c856155 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -84,6 +84,41 @@ jobs: pip install -e ".[test]" python test_ipykernel.py + qtconsole: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" + architecture: "x64" + + - name: Install System Packages + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev + - name: Install qtconsole dependencies + shell: bash -l {0} + run: | + cd ${GITHUB_WORKSPACE}/.. + git clone https://github.com/jupyter/qtconsole.git + cd qtconsole + ${pythonLocation}/bin/python -m pip install -e ".[test]" + ${pythonLocation}/bin/python -m pip install pyqt5 + - name: Install Jupyter-Client changes + shell: bash -l {0} + run: ${pythonLocation}/bin/python -m pip install -e . + + - name: Test qtconsole + shell: bash -l {0} + run: | + cd ${GITHUB_WORKSPACE}/../qtconsole + xvfb-run --auto-servernum ${pythonLocation}/bin/python -m pytest -x -vv -s --full-trace --color=yes qtconsole + downstream_check: # This job does nothing and is only used for the branch protection if: always() needs: @@ -92,6 +127,7 @@ jobs: - jupyter_client - ipyparallel - jupyter_kernel_test + - qtconsole runs-on: ubuntu-latest steps: - name: Decide whether the needed jobs succeeded or failed diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 295232e14..77f9f072f 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -42,7 +42,7 @@ def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): # but for backwards compatibility, we need to inherit from LoggingConfigurable -class Comm(traitlets.config.LoggingConfigurable, BaseComm): +class Comm(BaseComm, traitlets.config.LoggingConfigurable): """Class for communicating between a Frontend and a Kernel""" kernel = Instance("ipykernel.kernelbase.Kernel", allow_none=True) # type:ignore[assignment] @@ -68,12 +68,16 @@ def _default_kernel(self): def _default_comm_id(self): return uuid.uuid4().hex - def __init__(self, *args, **kwargs): - # Comm takes positional arguments, LoggingConfigurable does not, so we explicitly forward arguments + def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwargs): + # Handle differing arguments between base classes. + kernel = kwargs.pop('kernel', None) + if target_name: + kwargs['target_name'] = target_name + BaseComm.__init__( + self, data=data, metadata=metadata, buffers=buffers, **kwargs + ) # type:ignore[call-arg] + kwargs['kernel'] = kernel traitlets.config.LoggingConfigurable.__init__(self, **kwargs) - # drop arguments not in BaseComm - kwargs.pop("kernel", None) - BaseComm.__init__(self, *args, **kwargs) __all__ = ["Comm"] diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 6bf73ad81..c11b56205 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -9,7 +9,7 @@ import traitlets.config -class CommManager(traitlets.config.LoggingConfigurable, comm.base_comm.CommManager): +class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): kernel = traitlets.Instance("ipykernel.kernelbase.Kernel") comms = traitlets.Dict() @@ -17,5 +17,5 @@ class CommManager(traitlets.config.LoggingConfigurable, comm.base_comm.CommManag def __init__(self, **kwargs): # CommManager doesn't take arguments, so we explicitly forward arguments - traitlets.config.LoggingConfigurable.__init__(self, **kwargs) comm.base_comm.CommManager.__init__(self) + traitlets.config.LoggingConfigurable.__init__(self, **kwargs) diff --git a/ipykernel/tests/test_comm.py b/ipykernel/tests/test_comm.py index f8b076413..405841f0c 100644 --- a/ipykernel/tests/test_comm.py +++ b/ipykernel/tests/test_comm.py @@ -1,3 +1,5 @@ +import unittest.mock + from ipykernel.comm import Comm, CommManager from ipykernel.ipkernel import IPythonKernel @@ -47,10 +49,12 @@ def on_msg(msg): manager.register_target("fizz", fizz) kernel.comm_manager = manager - comm = Comm() - comm.on_msg(on_msg) - comm.on_close(on_close) - manager.register_comm(comm) + with unittest.mock.patch.object(Comm, "publish_msg") as publish_msg: + comm = Comm() + comm.on_msg(on_msg) + comm.on_close(on_close) + manager.register_comm(comm) + assert publish_msg.call_count == 1 assert manager.get_comm(comm.comm_id) == comm assert manager.get_comm('foo') is None From 2c66b2de835856b19b25c6a890d9665902dda907 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 19 Dec 2022 14:25:26 +0000 Subject: [PATCH 0953/1195] Publish 6.19.3 SHA256 hashes: ipykernel-6.19.3-py3-none-any.whl: 67daadf01c461d23aaefe2f5670779866b9bde7269b7a02ae4ef5b37e1adbf8f ipykernel-6.19.3.tar.gz: 70953ffa209fefc7166d365d230b710d90cdc2d2ad56e535d4c775d46da07089 --- CHANGELOG.md | 24 ++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ea418337..8eac49f61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ +## 6.19.3 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.2...0925d09075280beb23c009ca0d361f73e5402e27)) + +### Bugs fixed + +- format dates as ISO8601 [#1057](https://github.com/ipython/ipykernel/pull/1057) ([@GRcharles](https://github.com/GRcharles)) +- Fix comms and add qtconsole downstream test [#1056](https://github.com/ipython/ipykernel/pull/1056) ([@blink1073](https://github.com/blink1073)) + +### Maintenance and upkeep improvements + +- Fix lint [#1058](https://github.com/ipython/ipykernel/pull/1058) ([@blink1073](https://github.com/blink1073)) +- Fix comms and add qtconsole downstream test [#1056](https://github.com/ipython/ipykernel/pull/1056) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-12-08&to=2022-12-19&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-12-08..2022-12-19&type=Issues) | [@GRcharles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AGRcharles+updated%3A2022-12-08..2022-12-19&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-12-08..2022-12-19&type=Issues) + + + ## 6.19.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.1...3c125ad5aa27de2ff412d7690de051115f175104)) @@ -16,8 +38,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-12-08..2022-12-08&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2022-12-08..2022-12-08&type=Issues) - - ## 6.19.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.0...5e1b155207c506f01df5808b1ba41f868a10f097)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 68c195cd1..1e5a7b566 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.19.2" +__version__ = "6.19.3" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 07da48e686b5906525c2a6b8cfc11cd7c3d96a5f Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 20 Dec 2022 10:22:46 -0600 Subject: [PATCH 0954/1195] Don't pass `None` kernels to logging configurable in `Comm` (#1061) Co-authored-by: Steven Silvester fixes undefined --- ipykernel/comm/comm.py | 5 ++++- ipykernel/tests/test_comm.py | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 77f9f072f..6ec6ff3d3 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -70,13 +70,16 @@ def _default_comm_id(self): def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwargs): # Handle differing arguments between base classes. + had_kernel = 'kernel' in kwargs kernel = kwargs.pop('kernel', None) if target_name: kwargs['target_name'] = target_name BaseComm.__init__( self, data=data, metadata=metadata, buffers=buffers, **kwargs ) # type:ignore[call-arg] - kwargs['kernel'] = kernel + # only re-add kernel if explicitly provided + if had_kernel: + kwargs['kernel'] = kernel traitlets.config.LoggingConfigurable.__init__(self, **kwargs) diff --git a/ipykernel/tests/test_comm.py b/ipykernel/tests/test_comm.py index 405841f0c..5ec0d455a 100644 --- a/ipykernel/tests/test_comm.py +++ b/ipykernel/tests/test_comm.py @@ -2,15 +2,18 @@ from ipykernel.comm import Comm, CommManager from ipykernel.ipkernel import IPythonKernel +from ipykernel.kernelbase import Kernel -def test_comm(kernel): +def test_comm(kernel: Kernel) -> None: manager = CommManager(kernel=kernel) - kernel.comm_manager = manager + kernel.comm_manager = manager # type:ignore c = Comm(kernel=kernel, target_name="bar") msgs = [] + assert kernel is c.kernel # type:ignore + def on_close(msg): msgs.append(msg) @@ -28,7 +31,7 @@ def on_message(msg): assert c.target_name == "bar" -def test_comm_manager(kernel): +def test_comm_manager(kernel: Kernel) -> None: manager = CommManager(kernel=kernel) msgs = [] @@ -48,7 +51,7 @@ def on_msg(msg): manager.register_target("foo", foo) manager.register_target("fizz", fizz) - kernel.comm_manager = manager + kernel.comm_manager = manager # type:ignore with unittest.mock.patch.object(Comm, "publish_msg") as publish_msg: comm = Comm() comm.on_msg(on_msg) @@ -56,6 +59,11 @@ def on_msg(msg): manager.register_comm(comm) assert publish_msg.call_count == 1 + # make sure that when we don't pass a kernel, the 'default' kernel is taken + Kernel._instance = kernel # type:ignore + assert comm.kernel is kernel # type:ignore + Kernel.clear_instance() + assert manager.get_comm(comm.comm_id) == comm assert manager.get_comm('foo') is None From 40c55672f18b3b901aeb7f24d10bbe04e8f61e1b Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 20 Dec 2022 16:30:00 +0000 Subject: [PATCH 0955/1195] Publish 6.19.4 SHA256 hashes: ipykernel-6.19.4-py3-none-any.whl: 0ecdae0060da61c5222ad221681f3b99b5bef739e11a3b1eb5778aa47f056f1f ipykernel-6.19.4.tar.gz: 4140c282a6c71cdde59abe5eae2c71bf1eeb4a69316ab76e1c4c25150a49722b --- CHANGELOG.md | 18 ++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eac49f61..77eb4705f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.19.4 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.3...07da48e686b5906525c2a6b8cfc11cd7c3d96a5f)) + +### Bugs fixed + +- Don't pass `None` kernels to logging configurable in `Comm` [#1061](https://github.com/ipython/ipykernel/pull/1061) ([@bollwyvl](https://github.com/bollwyvl)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-12-19&to=2022-12-20&type=c)) + +[@bollwyvl](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abollwyvl+updated%3A2022-12-19..2022-12-20&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-12-19..2022-12-20&type=Issues) + + + ## 6.19.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.2...0925d09075280beb23c009ca0d361f73e5402e27)) @@ -22,8 +38,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-12-08..2022-12-19&type=Issues) | [@GRcharles](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AGRcharles+updated%3A2022-12-08..2022-12-19&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-12-08..2022-12-19&type=Issues) - - ## 6.19.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.1...3c125ad5aa27de2ff412d7690de051115f175104)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 1e5a7b566..f999d5aff 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.19.3" +__version__ = "6.19.4" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 9b434e91e3182a55cc041e924e9b55da92947c2d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 22 Dec 2022 18:05:37 -0600 Subject: [PATCH 0956/1195] Add more ci checks (#1063) * add more ci checks * fixes * lint * lint --- .github/workflows/ci.yml | 4 +- .pre-commit-config.yaml | 13 ++++--- docs/conf.py | 8 ++++ examples/embedding/inprocess_qtconsole.py | 3 ++ examples/embedding/inprocess_terminal.py | 3 ++ examples/embedding/internal_ipkernel.py | 8 +++- examples/embedding/ipkernel_qtapp.py | 4 ++ examples/embedding/ipkernel_wxapp.py | 4 ++ hatch_build.py | 4 ++ ipykernel/__main__.py | 1 + ipykernel/comm/comm.py | 3 ++ ipykernel/comm/manager.py | 2 + ipykernel/compiler.py | 9 +++++ ipykernel/control.py | 5 +++ ipykernel/datapub.py | 1 + ipykernel/debugger.py | 46 +++++++++++++++++++++++ ipykernel/displayhook.py | 5 +++ ipykernel/eventloops.py | 6 +++ ipykernel/gui/gtk3embed.py | 3 ++ ipykernel/gui/gtkembed.py | 3 ++ ipykernel/heartbeat.py | 3 ++ ipykernel/inprocess/blocking.py | 9 ++++- ipykernel/inprocess/channels.py | 12 ++++++ ipykernel/inprocess/client.py | 13 +++++++ ipykernel/inprocess/ipkernel.py | 3 ++ ipykernel/inprocess/manager.py | 7 ++++ ipykernel/inprocess/socket.py | 2 + ipykernel/iostream.py | 8 ++++ ipykernel/ipkernel.py | 17 +++++++++ ipykernel/kernelapp.py | 15 +++++++- ipykernel/kernelbase.py | 15 ++++++++ ipykernel/kernelspec.py | 2 + ipykernel/log.py | 2 + ipykernel/parentpoller.py | 3 ++ ipykernel/pickleutil.py | 32 ++++++++++++++++ ipykernel/tests/test_message_spec.py | 7 ++-- ipykernel/trio_runner.py | 11 ++++++ ipykernel/zmqshell.py | 8 ++++ pyproject.toml | 18 ++++++++- 39 files changed, 307 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 759a13cc8..0b4e2b015 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -76,7 +76,7 @@ jobs: run: | hatch run typing:test hatch run lint:style - pipx run 'validate-pyproject[all]' pyproject.toml + pipx run interrogate -vv . pipx run doc8 --max-line-length=200 check_release: @@ -89,7 +89,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} test_docs: - runs-on: ubuntu-latest + runs-on: windows-latest steps: - uses: actions/checkout@v3 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f5fe3326b..419f3dc57 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,16 +5,19 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - - id: end-of-file-fixer - id: check-case-conflict + - id: check-ast + - id: check-docstring-first - id: check-executables-have-shebangs - - id: requirements-txt-fixer - id: check-added-large-files - id: check-case-conflict + - id: check-merge-conflict + - id: check-json - id: check-toml - id: check-yaml - - id: forbid-new-submodules - - id: check-builtin-literals + - id: debug-statements + exclude: ipykernel/kernelapp.py + - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema @@ -33,7 +36,7 @@ repos: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.177 + rev: v0.0.189 hooks: - id: ruff args: ["--fix"] diff --git a/docs/conf.py b/docs/conf.py index 9a47e1769..b5edf98a9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,6 +36,14 @@ "sphinxcontrib_github_alt", ] +try: + import enchant # noqa + + extensions += ["sphinxcontrib.spelling"] +except ImportError: + pass + + github_project_url = "https://github.com/ipython/ipykernel" # Add any paths that contain templates here, relative to this directory. diff --git a/examples/embedding/inprocess_qtconsole.py b/examples/embedding/inprocess_qtconsole.py index 3ac4ae485..c346a97fb 100644 --- a/examples/embedding/inprocess_qtconsole.py +++ b/examples/embedding/inprocess_qtconsole.py @@ -1,3 +1,4 @@ +"""An in-process qt console app.""" import os import sys @@ -8,6 +9,7 @@ def print_process_id(): + """Print the process id.""" print("Process ID is:", os.getpid()) @@ -42,6 +44,7 @@ def init_asyncio_patch(): def main(): + """The main entry point.""" # Print the ID of the main process print_process_id() diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index 2f8d5453d..79e11e03a 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -1,3 +1,4 @@ +"""An in-process terminal example.""" import os import sys @@ -8,6 +9,7 @@ def print_process_id(): + """Print the process id.""" print("Process ID is:", os.getpid()) @@ -42,6 +44,7 @@ def init_asyncio_patch(): def main(): + """The main function.""" print_process_id() # Create an in-process kernel diff --git a/examples/embedding/internal_ipkernel.py b/examples/embedding/internal_ipkernel.py index 9aa2783a0..ee4424288 100644 --- a/examples/embedding/internal_ipkernel.py +++ b/examples/embedding/internal_ipkernel.py @@ -1,3 +1,4 @@ +"""An internal ipykernel example.""" # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- @@ -26,8 +27,10 @@ def mpl_kernel(gui): class InternalIPKernel: + """An internal ipykernel class.""" + def init_ipkernel(self, backend): - # Start IPython kernel with GUI event loop and mpl support + """Start IPython kernel with GUI event loop and mpl support.""" self.ipkernel = mpl_kernel(backend) # To create and track active qt consoles self.consoles = [] @@ -41,6 +44,7 @@ def init_ipkernel(self, backend): # self.namespace['ipkernel'] = self.ipkernel # dbg def print_namespace(self, evt=None): + """Print the namespace.""" print("\n***Variables in User namespace***") for k, v in self.namespace.items(): if not k.startswith("_"): @@ -52,8 +56,10 @@ def new_qt_console(self, evt=None): return connect_qtconsole(self.ipkernel.abs_connection_file, profile=self.ipkernel.profile) def count(self, evt=None): + """Get the app counter value.""" self.namespace["app_counter"] += 1 def cleanup_consoles(self, evt=None): + """Clean up the consoles.""" for c in self.consoles: c.kill() diff --git a/examples/embedding/ipkernel_qtapp.py b/examples/embedding/ipkernel_qtapp.py index 7d6e61cf0..608c4e426 100755 --- a/examples/embedding/ipkernel_qtapp.py +++ b/examples/embedding/ipkernel_qtapp.py @@ -26,13 +26,17 @@ # Functions and classes # ----------------------------------------------------------------------------- class SimpleWindow(Qt.QWidget, InternalIPKernel): + """A custom Qt widget for IPykernel.""" + def __init__(self, app): + """Initialize the widget.""" Qt.QWidget.__init__(self) self.app = app self.add_widgets() self.init_ipkernel("qt") def add_widgets(self): + """Add the widget.""" self.setGeometry(300, 300, 400, 70) self.setWindowTitle("IPython in your app") diff --git a/examples/embedding/ipkernel_wxapp.py b/examples/embedding/ipkernel_wxapp.py index 2caad5be2..f24ed9392 100755 --- a/examples/embedding/ipkernel_wxapp.py +++ b/examples/embedding/ipkernel_wxapp.py @@ -36,6 +36,7 @@ class MyFrame(wx.Frame, InternalIPKernel): """ def __init__(self, parent, title): + """Initialize the frame.""" wx.Frame.__init__(self, parent, -1, title, pos=(150, 150), size=(350, 285)) # Create the menubar @@ -99,7 +100,10 @@ def OnTimeToClose(self, evt): class MyApp(wx.App): + """A custom wx app.""" + def OnInit(self): + """Initialize app.""" frame = MyFrame(None, "Simple wxPython App") self.SetTopWindow(frame) frame.Show(True) diff --git a/hatch_build.py b/hatch_build.py index deb21ff47..71dc49be3 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -1,3 +1,4 @@ +"""A custom hatch build hook for ipykernel.""" import os import shutil import sys @@ -6,7 +7,10 @@ class CustomHook(BuildHookInterface): + """The IPykernel build hook.""" + def initialize(self, version, build_data): + """Initialize the hook.""" here = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, here) from ipykernel.kernelspec import make_ipkernel_cmd, write_kernel_spec diff --git a/ipykernel/__main__.py b/ipykernel/__main__.py index f66b36c8e..a1050e32e 100644 --- a/ipykernel/__main__.py +++ b/ipykernel/__main__.py @@ -1,3 +1,4 @@ +"""The cli entry point for ipykernel.""" if __name__ == "__main__": from ipykernel import kernelapp as app diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 6ec6ff3d3..8f3c2dff3 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -16,6 +16,8 @@ # this is the class that will be created if we do comm.create_comm class BaseComm(comm.base_comm.BaseComm): + """The base class for comms.""" + kernel: Optional[Kernel] = None def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): @@ -69,6 +71,7 @@ def _default_comm_id(self): return uuid.uuid4().hex def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwargs): + """Initialize a comm.""" # Handle differing arguments between base classes. had_kernel = 'kernel' in kwargs kernel = kwargs.pop('kernel', None) diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index c11b56205..f18e272d2 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -10,12 +10,14 @@ class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): + """A comm manager.""" kernel = traitlets.Instance("ipykernel.kernelbase.Kernel") comms = traitlets.Dict() targets = traitlets.Dict() def __init__(self, **kwargs): + """Initialize the manager.""" # CommManager doesn't take arguments, so we explicitly forward arguments comm.base_comm.CommManager.__init__(self) traitlets.config.LoggingConfigurable.__init__(self, **kwargs) diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 387e49b3e..fe5561594 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -1,3 +1,4 @@ +"""Compiler helpers for the debugger.""" import os import sys import tempfile @@ -6,6 +7,7 @@ def murmur2_x86(data, seed): + """Get the murmur2 hash.""" m = 0x5BD1E995 data = [chr(d) for d in str.encode(data, "utf8")] length = len(data) @@ -70,17 +72,20 @@ def _convert_to_long_pathname(filename): def get_tmp_directory(): + """Get a temp directory.""" tmp_dir = convert_to_long_pathname(tempfile.gettempdir()) pid = os.getpid() return tmp_dir + os.sep + "ipykernel_" + str(pid) def get_tmp_hash_seed(): + """Get a temp hash seed.""" hash_seed = 0xC70F6907 return hash_seed def get_file_name(code): + """Get a file name.""" cell_name = os.environ.get("IPYKERNEL_CELL_NAME") if cell_name is None: name = murmur2_x86(code, get_tmp_hash_seed()) @@ -89,9 +94,13 @@ def get_file_name(code): class XCachingCompiler(CachingCompiler): + """A custom caching compiler.""" + def __init__(self, *args, **kwargs): + """Initialize the compiler.""" super().__init__(*args, **kwargs) self.log = None def get_code_name(self, raw_code, code, number): + """Get the code name.""" return get_file_name(raw_code) diff --git a/ipykernel/control.py b/ipykernel/control.py index 1aaf9a7e8..d78a4ebe1 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -1,16 +1,21 @@ +"""A thread for a control channel.""" from threading import Thread from tornado.ioloop import IOLoop class ControlThread(Thread): + """A thread for a control channel.""" + def __init__(self, **kwargs): + """Initialize the thread.""" Thread.__init__(self, name="Control", **kwargs) self.io_loop = IOLoop(make_current=False) self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True def run(self): + """Run the thread.""" self.name = "Control" try: self.io_loop.start() diff --git a/ipykernel/datapub.py b/ipykernel/datapub.py index d8fe04093..4b1c7a501 100644 --- a/ipykernel/datapub.py +++ b/ipykernel/datapub.py @@ -28,6 +28,7 @@ class ZMQDataPublisher(Configurable): + """A zmq data publisher.""" topic = topic = CBytes(b"datapub") session = Instance(Session, allow_none=True) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 224678232..43ae68300 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,3 +1,4 @@ +"""Debugger implementation for the IPython kernel.""" import os import re import sys @@ -44,13 +45,19 @@ class _FakeCode: + """Fake code class.""" + def __init__(self, co_filename, co_name): + """Init.""" self.co_filename = co_filename self.co_name = co_name class _FakeFrame: + """Fake frame class.""" + def __init__(self, f_code, f_globals, f_locals): + """Init.""" self.f_code = f_code self.f_globals = f_globals self.f_locals = f_locals @@ -58,28 +65,37 @@ def __init__(self, f_code, f_globals, f_locals): class _DummyPyDB: + """Fake PyDb class.""" + def __init__(self): + """Init.""" from _pydevd_bundle.pydevd_api import PyDevdAPI self.variable_presentation = PyDevdAPI.VariablePresentation() class VariableExplorer: + """A variable explorer.""" + def __init__(self): + """Initialize the explorer.""" self.suspended_frame_manager = SuspendedFramesManager() self.py_db = _DummyPyDB() self.tracker = _FramesTracker(self.suspended_frame_manager, self.py_db) self.frame = None def track(self): + """Start tracking.""" var = get_ipython().user_ns self.frame = _FakeFrame(_FakeCode("", get_file_name("sys._getframe()")), var, var) self.tracker.track("thread1", pydevd_frame_utils.create_frames_list_from_frame(self.frame)) def untrack_all(self): + """Stop tracking.""" self.tracker.untrack_all() def get_children_variables(self, variable_ref=None): + """Get the child variables for a variable reference.""" var_ref = variable_ref if not var_ref: var_ref = id(self.frame) @@ -88,6 +104,7 @@ def get_children_variables(self, variable_ref=None): class DebugpyMessageQueue: + """A debugpy message queue.""" HEADER = "Content-Length: " HEADER_LENGTH = 16 @@ -95,6 +112,7 @@ class DebugpyMessageQueue: SEPARATOR_LENGTH = 4 def __init__(self, event_callback, log): + """Init the queue.""" self.tcp_buffer = "" self._reset_tcp_pos() self.event_callback = event_callback @@ -120,6 +138,7 @@ def _put_message(self, raw_msg): self.message_queue.put_nowait(msg) def put_tcp_frame(self, frame): + """Put a tcp frame in the queue.""" self.tcp_buffer += frame self.log.debug("QUEUE - received frame") @@ -166,11 +185,15 @@ def put_tcp_frame(self, frame): self._reset_tcp_pos() async def get_message(self): + """Get a message from the queue.""" return await self.message_queue.get() class DebugpyClient: + """A client for debugpy.""" + def __init__(self, log, debugpy_stream, event_callback): + """Initialize the client.""" self.log = log self.debugpy_stream = debugpy_stream self.event_callback = event_callback @@ -237,6 +260,7 @@ async def _handle_init_sequence(self): return attach_rep def get_host_port(self): + """Get the host debugpy port.""" if self.debugpy_port == -1: socket = self.debugpy_stream.socket socket.bind_to_random_port("tcp://" + self.debugpy_host) @@ -247,10 +271,12 @@ def get_host_port(self): return self.debugpy_host, self.debugpy_port def connect_tcp_socket(self): + """Connect to the tcp socket.""" self.debugpy_stream.socket.connect(self._get_endpoint()) self.routing_id = self.debugpy_stream.socket.getsockopt(ROUTING_ID) def disconnect_tcp_socket(self): + """Disconnect from the tcp socket.""" self.debugpy_stream.socket.disconnect(self._get_endpoint()) self.routing_id = None self.init_event = Event() @@ -258,9 +284,11 @@ def disconnect_tcp_socket(self): self.wait_for_attach = True def receive_dap_frame(self, frame): + """Receive a dap frame.""" self.message_queue.put_tcp_frame(frame) async def send_dap_request(self, msg): + """Send a dap request.""" self._send_request(msg) if self.wait_for_attach and msg["command"] == "attach": rep = await self._handle_init_sequence() @@ -274,6 +302,7 @@ async def send_dap_request(self, msg): class Debugger: + """The debugger class.""" # Requests that requires that the debugger has started started_debug_msg_types = [ @@ -292,6 +321,7 @@ class Debugger: def __init__( self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code=True ): + """Initialize the debugger.""" self.log = log self.debugpy_client = DebugpyClient(log, debugpy_stream, self._handle_event) self.shell_socket = shell_socket @@ -361,6 +391,7 @@ def _accept_stopped_thread(self, thread_name): return thread_name not in forbid_list async def handle_stopped_event(self): + """Handle a stopped event.""" # Wait for a stopped event message in the stopped queue # This message is used for triggering the 'threads' request event = await self.stopped_queue.get() @@ -376,6 +407,7 @@ def tcp_client(self): return self.debugpy_client def start(self): + """Start the debugger.""" if not self.debugpy_initialized: tmp_dir = get_tmp_directory() if not os.path.exists(tmp_dir): @@ -405,6 +437,7 @@ def start(self): return self.debugpy_initialized def stop(self): + """Stop the debugger.""" self.debugpy_client.disconnect_tcp_socket() # Restore remove cleanup transformers @@ -414,6 +447,7 @@ def stop(self): cleanup_transforms.insert(index, func) async def dumpCell(self, message): + """Handle a dump cell message.""" code = message["arguments"]["code"] file_name = get_file_name(code) @@ -430,11 +464,13 @@ async def dumpCell(self, message): return reply async def setBreakpoints(self, message): + """Handle a set breakpoints message.""" source = message["arguments"]["source"]["path"] self.breakpoint_list[source] = message["arguments"]["breakpoints"] return await self._forward_message(message) async def source(self, message): + """Handle a source message.""" reply = {"type": "response", "request_seq": message["seq"], "command": message["command"]} source_path = message["arguments"]["source"]["path"] if os.path.isfile(source_path): @@ -449,6 +485,7 @@ async def source(self, message): return reply async def stackTrace(self, message): + """Handle a stack trace message.""" reply = await self._forward_message(message) # The stackFrames array can have the following content: # { frames from the notebook} @@ -470,6 +507,7 @@ async def stackTrace(self, message): return reply def accept_variable(self, variable_name): + """Accept a variable by name.""" forbid_list = [ "__name__", "__doc__", @@ -498,6 +536,7 @@ def accept_variable(self, variable_name): return cond async def variables(self, message): + """Handle a variables message.""" reply = {} if not self.stopped_threads: variables = self.variable_explorer.get_children_variables( @@ -513,6 +552,7 @@ async def variables(self, message): return reply async def attach(self, message): + """Handle an attach message.""" host, port = self.debugpy_client.get_host_port() message["arguments"]["connect"] = {"host": host, "port": port} message["arguments"]["logToFile"] = True @@ -525,6 +565,7 @@ async def attach(self, message): return await self._forward_message(message) async def configurationDone(self, message): + """Handle a configuration done message.""" reply = { "seq": message["seq"], "type": "response", @@ -535,6 +576,7 @@ async def configurationDone(self, message): return reply async def debugInfo(self, message): + """Handle a debug info message.""" breakpoint_list = [] for key, value in self.breakpoint_list.items(): breakpoint_list.append({"source": key, "breakpoints": value}) @@ -558,6 +600,7 @@ async def debugInfo(self, message): return reply async def inspectVariables(self, message): + """Handle an insepct variables message.""" self.variable_explorer.untrack_all() # looks like the implementation of untrack_all in ptvsd # destroys objects we nee din track. We have no choice but @@ -568,6 +611,7 @@ async def inspectVariables(self, message): return self._build_variables_response(message, variables) async def richInspectVariables(self, message): + """Handle a rich inspect variables message.""" reply = { "type": "response", "sequence_seq": message["seq"], @@ -619,6 +663,7 @@ async def richInspectVariables(self, message): return reply async def modules(self, message): + """Handle a modules message.""" modules = list(sys.modules.values()) startModule = message.get("startModule", 0) moduleCount = message.get("moduleCount", len(modules)) @@ -633,6 +678,7 @@ async def modules(self, message): return reply async def process_request(self, message): + """Process a request.""" reply = {} if message["command"] == "initialize": diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index e7c98c01d..0727997cc 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -20,6 +20,7 @@ class ZMQDisplayHook: topic = b"execute_result" def __init__(self, session, pub_socket): + """Initialize the hook.""" self.session = session self.pub_socket = pub_socket self.parent_header = {} @@ -29,6 +30,7 @@ def get_execution_count(self): return 0 def __call__(self, obj): + """Handle a hook call.""" if obj is None: return @@ -45,6 +47,7 @@ def __call__(self, obj): ) def set_parent(self, parent): + """Set the parent header.""" self.parent_header = extract_header(parent) @@ -64,6 +67,7 @@ def set_parent(self, parent): self.parent_header = extract_header(parent) def start_displayhook(self): + """Start the display hook.""" self.msg = self.session.msg( "execute_result", { @@ -78,6 +82,7 @@ def write_output_prompt(self): self.msg["content"]["execution_count"] = self.prompt_count def write_format_data(self, format_dict, md_dict=None): + """Write format data to the message.""" self.msg["content"]["data"] = json_clean(encode_images(format_dict)) self.msg["content"]["metadata"] = md_dict diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index b2a312d90..76d1c700f 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -83,6 +83,7 @@ def register_integration(*toolkitnames): """ def decorator(func): + """Integration registration decorator.""" for name in toolkitnames: loop_map[name] = func @@ -221,6 +222,7 @@ def OnInit(self): @loop_wx.exit def loop_wx_exit(kernel): + """Exit the wx loop.""" import wx wx.Exit() @@ -298,6 +300,7 @@ def start(self): @loop_tk.exit def loop_tk_exit(kernel): + """Exit the tk loop.""" try: kernel.app_wrapper.app.destroy() del kernel.app_wrapper @@ -317,6 +320,7 @@ def loop_gtk(kernel): @loop_gtk.exit def loop_gtk_exit(kernel): + """Exit the gtk loop.""" kernel._gtk.stop() @@ -332,6 +336,7 @@ def loop_gtk3(kernel): @loop_gtk3.exit def loop_gtk3_exit(kernel): + """Exit the gtk3 loop.""" kernel._gtk.stop() @@ -376,6 +381,7 @@ def handle_int(etype, value, tb): @loop_cocoa.exit def loop_cocoa_exit(kernel): + """Exit the cocoa loop.""" from ._eventloop_macos import stop stop() diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index 42c7e181f..3847683cd 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -32,6 +32,7 @@ class GTKEmbed: """A class to embed a kernel into the GTK main event loop.""" def __init__(self, kernel): + """Initialize the embed.""" self.kernel = kernel # These two will later store the real gtk functions when we hijack them self.gtk_main = None @@ -63,6 +64,7 @@ def iterate_kernel(self): return True def stop(self): + """Stop the embed.""" # FIXME: this one isn't getting called because we have no reliable # kernel shutdown. We need to fix that: once the kernel has a # shutdown mechanism, it can call this. @@ -86,6 +88,7 @@ def _hijack_gtk(self): """ def dummy(*args, **kw): + """No-op.""" pass # save and trap main and main_quit from gtk diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index a97a62a06..5d9171bb8 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -29,6 +29,7 @@ class GTKEmbed: """A class to embed a kernel into the GTK main event loop.""" def __init__(self, kernel): + """Initialize the embed.""" self.kernel = kernel # These two will later store the real gtk functions when we hijack them self.gtk_main = None @@ -60,6 +61,7 @@ def iterate_kernel(self): return True def stop(self): + """Stop the embed.""" # FIXME: this one isn't getting called because we have no reliable # kernel shutdown. We need to fix that: once the kernel has a # shutdown mechanism, it can call this. @@ -83,6 +85,7 @@ def _hijack_gtk(self): """ def dummy(*args, **kw): + """No-op.""" pass # save and trap main and main_quit from gtk diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 3f10997c6..cf2d6d5ae 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -29,6 +29,7 @@ class Heartbeat(Thread): """A simple ping-pong style heartbeat that runs in a thread.""" def __init__(self, context, addr=None): + """Initialize the heartbeat thread.""" if addr is None: addr = ("tcp", localhost(), 0) Thread.__init__(self, name="Heartbeat") @@ -44,6 +45,7 @@ def __init__(self, context, addr=None): self.name = "Heartbeat" def pick_port(self): + """Pick a port for the heartbeat.""" if self.transport == "tcp": s = socket.socket() # '*' means all interfaces to 0MQ, which is '' to socket.socket @@ -89,6 +91,7 @@ def _bind_socket(self): return def run(self): + """Run the heartbeat thread.""" self.name = "Heartbeat" self.socket = self.context.socket(zmq.ROUTER) self.socket.linger = 1000 diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index 17f334f48..2274bed39 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -21,11 +21,15 @@ class BlockingInProcessChannel(InProcessChannel): + """A blocking in-process channel.""" + def __init__(self, *args, **kwds): + """Initialize the channel.""" super().__init__(*args, **kwds) self._in_queue: Queue[object] = Queue() def call_handlers(self, msg): + """Call the handlers for a message.""" self._in_queue.put(msg) def get_msg(self, block=True, timeout=None): @@ -52,6 +56,8 @@ def msg_ready(self): class BlockingInProcessStdInChannel(BlockingInProcessChannel): + """A blocking in-process stdin channel.""" + def call_handlers(self, msg): """Overridden for the in-process channel. @@ -67,6 +73,7 @@ def call_handlers(self, msg): class BlockingInProcessKernelClient(InProcessKernelClient): + """A blocking in-process kernel client.""" # The classes to use for the various channels. shell_channel_class = Type(BlockingInProcessChannel) @@ -74,7 +81,7 @@ class BlockingInProcessKernelClient(InProcessKernelClient): stdin_channel_class = Type(BlockingInProcessStdInChannel) def wait_for_ready(self): - # Wait for kernel info reply on shell channel + """Wait for kernel info reply on shell channel.""" while True: self.kernel_info() try: diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 84629ff5d..9b054e043 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -18,17 +18,21 @@ class InProcessChannel: proxy_methods: List[object] = [] def __init__(self, client=None): + """Initialze the channel.""" super().__init__() self.client = client self._is_alive = False def is_alive(self): + """Test if the channel is alive.""" return self._is_alive def start(self): + """Start the channel.""" self._is_alive = True def stop(self): + """Stop the channel.""" self._is_alive = False def call_handlers(self, msg): @@ -39,6 +43,7 @@ def call_handlers(self, msg): raise NotImplementedError("call_handlers must be defined in a subclass.") def flush(self, timeout=1.0): + """Flush the channel.""" pass def call_handlers_later(self, *args, **kwds): @@ -70,27 +75,34 @@ class InProcessHBChannel: time_to_dead = 3.0 def __init__(self, client=None): + """Initialize the channel.""" super().__init__() self.client = client self._is_alive = False self._pause = True def is_alive(self): + """Test if the channel is alive.""" return self._is_alive def start(self): + """Start the channel.""" self._is_alive = True def stop(self): + """Stop the channel.""" self._is_alive = False def pause(self): + """Pause the channel.""" self._pause = True def unpause(self): + """Unpause the channel.""" self._pause = False def is_beating(self): + """Test if the channel is beating.""" return not self._pause diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index d6bfc9a5f..124d5cf3e 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -62,11 +62,13 @@ def _default_blocking_class(self): return BlockingInProcessKernelClient def get_connection_info(self): + """Get the connection info for the client.""" d = super().get_connection_info() d["kernel"] = self.kernel return d def start_channels(self, *args, **kwargs): + """Start the channels on the client.""" super().start_channels() self.kernel.frontends.append(self) @@ -106,6 +108,7 @@ def hb_channel(self): def execute( self, code, silent=False, store_history=True, user_expressions=None, allow_stdin=None ): + """Execute code on the client.""" if allow_stdin is None: allow_stdin = self.allow_stdin content = dict( @@ -120,6 +123,7 @@ def execute( return msg["header"]["msg_id"] def complete(self, code, cursor_pos=None): + """Get code completion.""" if cursor_pos is None: cursor_pos = len(code) content = dict(code=code, cursor_pos=cursor_pos) @@ -128,6 +132,7 @@ def complete(self, code, cursor_pos=None): return msg["header"]["msg_id"] def inspect(self, code, cursor_pos=None, detail_level=0): + """Get code inspection.""" if cursor_pos is None: cursor_pos = len(code) content = dict( @@ -140,12 +145,14 @@ def inspect(self, code, cursor_pos=None, detail_level=0): return msg["header"]["msg_id"] def history(self, raw=True, output=False, hist_access_type="range", **kwds): + """Get code history.""" content = dict(raw=raw, output=output, hist_access_type=hist_access_type, **kwds) msg = self.session.msg("history_request", content) self._dispatch_to_kernel(msg) return msg["header"]["msg_id"] def shutdown(self, restart=False): + """Handle shutdown.""" # FIXME: What to do here? raise NotImplementedError("Cannot shutdown in-process kernel") @@ -166,11 +173,13 @@ def comm_info(self, target_name=None): return msg["header"]["msg_id"] def input(self, string): + """Handle kernel input.""" if self.kernel is None: raise RuntimeError("Cannot send input reply. No kernel exists.") self.kernel.raw_input_str = string def is_complete(self, code): + """Handle an is_complete request.""" msg = self.session.msg("is_complete_request", {"code": code}) self._dispatch_to_kernel(msg) return msg["header"]["msg_id"] @@ -194,15 +203,19 @@ def _dispatch_to_kernel(self, msg): self.shell_channel.call_handlers_later(reply_msg) def get_shell_msg(self, block=True, timeout=None): + """Get a shell message.""" return self.shell_channel.get_msg(block, timeout) def get_iopub_msg(self, block=True, timeout=None): + """Get an iopub message.""" return self.iopub_channel.get_msg(block, timeout) def get_stdin_msg(self, block=True, timeout=None): + """Get a stdin message.""" return self.stdin_channel.get_msg(block, timeout) def get_control_msg(self, block=True, timeout=None): + """Get a control message.""" return self.control_channel.get_msg(block, timeout) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index f185ddc35..df34303b4 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -24,6 +24,7 @@ class InProcessKernel(IPythonKernel): + """An in-process kernel.""" # ------------------------------------------------------------------------- # InProcessKernel interface @@ -67,6 +68,7 @@ def _default_iopub_socket(self): stdin_socket = Instance(DummySocket, ()) # type:ignore[assignment] def __init__(self, **traits): + """Initialize the kernel.""" super().__init__(**traits) self._underlying_iopub_socket.observe(self._io_dispatch, names=["message_sent"]) @@ -163,6 +165,7 @@ def _default_stderr(self): class InProcessInteractiveShell(ZMQInteractiveShell): + """An in-process interactive shell.""" kernel: InProcessKernel = Instance( "ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True diff --git a/ipykernel/inprocess/manager.py b/ipykernel/inprocess/manager.py index fbbd34236..5649e8cab 100644 --- a/ipykernel/inprocess/manager.py +++ b/ipykernel/inprocess/manager.py @@ -42,15 +42,18 @@ def _default_session(self): # -------------------------------------------------------------------------- def start_kernel(self, **kwds): + """Start the kernel.""" from ipykernel.inprocess.ipkernel import InProcessKernel self.kernel = InProcessKernel(parent=self, session=self.session) def shutdown_kernel(self): + """Shutdown the kernel.""" self.kernel.iopub_thread.stop() self._kill_kernel() def restart_kernel(self, now=False, **kwds): + """Restart the kernel.""" self.shutdown_kernel() self.start_kernel(**kwds) @@ -62,15 +65,19 @@ def _kill_kernel(self): self.kernel = None def interrupt_kernel(self): + """Interrupt the kernel.""" raise NotImplementedError("Cannot interrupt in-process kernel.") def signal_kernel(self, signum): + """Send a signal to the kernel.""" raise NotImplementedError("Cannot signal in-process kernel.") def is_alive(self): + """Test if the kernel is alive.""" return self.kernel is not None def client(self, **kwargs): + """Get a client for the kernel.""" kwargs["kernel"] = self.kernel return super().client(**kwargs) diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index 477c36a47..7e48789e9 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -28,9 +28,11 @@ def _context_default(self): # ------------------------------------------------------------------------- def recv_multipart(self, flags=0, copy=True, track=False): + """Recv a multipart message.""" return self.queue.get_nowait() def send_multipart(self, msg_parts, flags=0, copy=True, track=False): + """Send a multipart message.""" msg_parts = list(map(zmq.Message, msg_parts)) self.queue.put_nowait(msg_parts) self.message_sent += 1 diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 5ea77452e..22b50a2a4 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -190,6 +190,7 @@ def stop(self): event_pipe.close() def close(self): + """Close the IOPub thread.""" if self.closed: return self.socket.close() @@ -244,6 +245,7 @@ class BackgroundSocket: io_thread = None def __init__(self, io_thread): + """Initialize the socket.""" self.io_thread = io_thread def __getattr__(self, attr): @@ -264,6 +266,7 @@ def __getattr__(self, attr): super().__getattr__(attr) # type:ignore[misc] def __setattr__(self, attr, value): + """Set an attribute on the socket.""" if attr == "io_thread" or (attr.startswith("__") and attr.endswith("__")): super().__setattr__(attr, value) else: @@ -278,6 +281,7 @@ def __setattr__(self, attr, value): setattr(self.io_thread.socket, attr, value) def send(self, msg, *args, **kwargs): + """Send a message to the socket.""" return self.send_multipart([msg], *args, **kwargs) def send_multipart(self, *args, **kwargs): @@ -438,9 +442,11 @@ def _is_master_process(self): return os.getpid() == self._master_pid def set_parent(self, parent): + """Set the parent header.""" self.parent_header = extract_header(parent) def close(self): + """Close the stream.""" if sys.platform.startswith("linux") or sys.platform.startswith("darwin"): self._should_watch = False self.watch_fd_thread.join() @@ -565,6 +571,7 @@ def write(self, string: str) -> Optional[int]: # type:ignore[override] return len(string) def writelines(self, sequence): + """Write lines to the stream.""" if self.pub_thread is None: raise ValueError("I/O operation on closed file") else: @@ -572,6 +579,7 @@ def writelines(self, sequence): self.write(string) def writable(self): + """Test whether the stream is writable.""" return True def _flush_buffer(self): diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 4dfe59c46..f4b67caa2 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -67,6 +67,8 @@ def _get_comm_manager(*args, **kwargs): class IPythonKernel(KernelBase): + """The IPython Kernel class.""" + shell = Instance("IPython.core.interactiveshell.InteractiveShellABC", allow_none=True) shell_class = Type(ZMQInteractiveShell) @@ -100,6 +102,7 @@ def _user_ns_changed(self, change): _sys_eval_input = Any() def __init__(self, **kwargs): + """Initialize the kernel.""" super().__init__(**kwargs) # Initialize the Debugger @@ -200,10 +203,12 @@ def banner(self): return self.shell.banner async def poll_stopped_queue(self): + """Poll the stopped queue.""" while True: await self.debugger.handle_stopped_event() def start(self): + """Start the kernel.""" self.shell.exit_now = False if self.debugpy_stream is None: self.log.warning("debugpy_stream undefined, debugging will not be enabled") @@ -335,6 +340,7 @@ async def do_execute( *, cell_id=None, ): + """Handle code execution.""" shell = self.shell # we'll need this a lot here self._forward_input(allow_stdin) @@ -469,6 +475,7 @@ async def run_cell(*args, **kwargs): return reply_content def do_complete(self, code, cursor_pos): + """Handle code completion.""" if _use_experimental_60_completion and self.use_experimental_completions: return self._experimental_do_complete(code, cursor_pos) @@ -489,6 +496,7 @@ def do_complete(self, code, cursor_pos): } async def do_debug_request(self, msg): + """Handle a debug request.""" if _is_debugpy_available: return await self.debugger.process_request(msg) @@ -532,6 +540,7 @@ def _experimental_do_complete(self, code, cursor_pos): } def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): + """Handle code inspection.""" name = token_at_cursor(code, cursor_pos) reply_content: t.Dict[str, t.Any] = {"status": "ok"} @@ -569,6 +578,7 @@ def do_history( pattern=None, unique=False, ): + """Handle code history.""" if hist_access_type == "tail": hist = self.shell.history_manager.get_tail( n, raw=raw, output=output, include_latest=True @@ -592,10 +602,12 @@ def do_history( } def do_shutdown(self, restart): + """Handle kernel shutdown.""" self.shell.exit_now = True return dict(status="ok", restart=restart) def do_is_complete(self, code): + """Handle an is_complete request.""" transformer_manager = getattr(self.shell, "input_transformer_manager", None) if transformer_manager is None: # input_splitter attribute is deprecated @@ -607,6 +619,7 @@ def do_is_complete(self, code): return r def do_apply(self, content, bufs, msg_id, reply_metadata): + """Handle an apply request.""" try: from ipyparallel.serialize import serialize_object, unpack_apply_message except ImportError: @@ -671,6 +684,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): return reply_content, result_buf def do_clear(self): + """Clear the kernel.""" self.shell.reset(False) return dict(status="ok") @@ -679,7 +693,10 @@ def do_clear(self): class Kernel(IPythonKernel): + """DEPRECATED. An alias for the IPython kernel class.""" + def __init__(self, *args, **kwargs): # pragma: no cover + """DEPRECATED.""" import warnings warnings.warn( diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 3f4aa708a..24355b14b 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -111,6 +111,8 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMixin): + """The IPYKernel application class.""" + name = "ipython-kernel" aliases = Dict(kernel_aliases) flags = Dict(kernel_flags) @@ -197,13 +199,16 @@ def abs_connection_file(self): ).tag(config=True) def init_crash_handler(self): + """Initialize the crash handler.""" sys.excepthook = self.excepthook def excepthook(self, etype, evalue, tb): + """Handle an exception.""" # write uncaught traceback to 'real' stderr, not zmq-forwarder traceback.print_exception(etype, evalue, tb, file=sys.__stderr__) def init_poller(self): + """Initialize the poller.""" if sys.platform == "win32": if self.interrupt or self.parent_handle: self.poller = ParentPollerWindows(self.interrupt, self.parent_handle) @@ -268,6 +273,7 @@ def write_connection_file(self): ) def cleanup_connection_file(self): + """Clean up our connection file.""" cf = self.abs_connection_file self.log.debug("Cleaning up connection file: %s", cf) try: @@ -278,6 +284,7 @@ def cleanup_connection_file(self): self.cleanup_ipc_files() def init_connection_file(self): + """Initialize our connection file.""" if not self.connection_file: self.connection_file = "kernel-%s.json" % os.getpid() try: @@ -298,7 +305,7 @@ def init_connection_file(self): self.exit(1) def init_sockets(self): - # Create a context, a session, and the kernel sockets. + """Create a context, a session, and the kernel sockets.""" self.log.info("Starting the kernel at pid: %i", os.getpid()) assert self.context is None, "init_sockets cannot be called twice!" self.context = context = zmq.Context() @@ -324,6 +331,7 @@ def init_sockets(self): self.init_iopub(context) def init_control(self, context): + """Initialize the control channel.""" self.control_socket = context.socket(zmq.ROUTER) self.control_socket.linger = 1000 self.control_port = self._bind_socket(self.control_socket, self.control_port) @@ -346,6 +354,7 @@ def init_control(self, context): self.control_thread = ControlThread(daemon=True) def init_iopub(self, context): + """Initialize the iopub channel.""" self.iopub_socket = context.socket(zmq.PUB) self.iopub_socket.linger = 1000 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port) @@ -517,6 +526,7 @@ def register(signum, file=sys.__stderr__, all_threads=True, chain=False, **kwarg faulthandler.register = register def init_signal(self): + """Initialize the signal handler.""" signal.signal(signal.SIGINT, signal.SIG_IGN) def init_kernel(self): @@ -580,6 +590,7 @@ def print_tb(etype, evalue, stb): shell._showtraceback = _showtraceback def init_shell(self): + """Initialize the shell channel.""" self.shell = getattr(self.kernel, "shell", None) if self.shell: self.shell.configurables.append(self) @@ -653,6 +664,7 @@ def init_pdb(self): @catch_config_error def initialize(self, argv=None): + """Initialize the application.""" self._init_asyncio_patch() super().initialize(argv) if self.subapp is not None: @@ -691,6 +703,7 @@ def initialize(self, argv=None): sys.stderr.flush() def start(self): + """Start the application.""" if self.subapp is not None: return self.subapp.start() if self.poller is not None: diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e5032c738..768056cfd 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -68,6 +68,7 @@ def _accepts_cell_id(meth): class Kernel(SingletonConfigurable): + """The base kernel class.""" # --------------------------------------------------------------------------- # Kernel interface @@ -259,6 +260,7 @@ def _parent_header(self): ] def __init__(self, **kwargs): + """Initialize the kernel.""" super().__init__(**kwargs) # Build dict of handlers for message types self.shell_handlers = {} @@ -769,6 +771,7 @@ def do_execute( raise NotImplementedError async def complete_request(self, stream, ident, parent): + """Handle a completion request.""" content = parent["content"] code = content["code"] cursor_pos = content["cursor_pos"] @@ -791,6 +794,7 @@ def do_complete(self, code, cursor_pos): } async def inspect_request(self, stream, ident, parent): + """Handle an inspect request.""" content = parent["content"] reply_content = self.do_inspect( @@ -812,6 +816,7 @@ def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): return {"status": "ok", "data": {}, "metadata": {}, "found": False} async def history_request(self, stream, ident, parent): + """Handle a history request.""" content = parent["content"] reply_content = self.do_history(**content) @@ -838,6 +843,7 @@ def do_history( return {"status": "ok", "history": []} async def connect_request(self, stream, ident, parent): + """Handle a connect request.""" if self._recorded_ports is not None: content = self._recorded_ports.copy() else: @@ -858,12 +864,14 @@ def kernel_info(self): } async def kernel_info_request(self, stream, ident, parent): + """Handle a kernel info request.""" content = {"status": "ok"} content.update(self.kernel_info) msg = self.session.send(stream, "kernel_info_reply", content, parent, ident) self.log.debug("%s", msg) async def comm_info_request(self, stream, ident, parent): + """Handle a comm info request.""" content = parent["content"] target_name = content.get("target_name", None) @@ -900,12 +908,14 @@ def _send_interupt_children(self): pass async def interrupt_request(self, stream, ident, parent): + """Handle an interrupt request.""" self._send_interupt_children() content = parent["content"] self.session.send(stream, "interrupt_reply", content, parent, ident=ident) return async def shutdown_request(self, stream, ident, parent): + """Handle a shutdown request.""" content = self.do_shutdown(parent["content"]["restart"]) if inspect.isawaitable(content): content = await content @@ -930,6 +940,7 @@ def do_shutdown(self, restart): return {"status": "ok", "restart": restart} async def is_complete_request(self, stream, ident, parent): + """Handle an is_complete request.""" content = parent["content"] code = content["code"] @@ -945,6 +956,7 @@ def do_is_complete(self, code): return {"status": "unknown"} async def debug_request(self, stream, ident, parent): + """Handle a debug request.""" content = parent["content"] reply_content = self.do_debug_request(content) if inspect.isawaitable(reply_content): @@ -954,6 +966,7 @@ async def debug_request(self, stream, ident, parent): self.log.debug("%s", reply_msg) def get_process_metric_value(self, process, name, attribute=None): + """Get the process metric value.""" try: metric_value = getattr(process, name)() if attribute is not None: # ... a named tuple @@ -966,6 +979,7 @@ def get_process_metric_value(self, process, name, attribute=None): return None async def usage_request(self, stream, ident, parent): + """Handle a usage request.""" reply_content = {"hostname": socket.gethostname(), "pid": os.getpid()} current_process = psutil.Process() all_processes = [current_process] + current_process.children(recursive=True) @@ -1005,6 +1019,7 @@ async def do_debug_request(self, msg): # --------------------------------------------------------------------------- async def apply_request(self, stream, ident, parent): # pragma: no cover + """Handle an apply request.""" self.log.warning("apply_request is deprecated in kernel_base, moving to ipyparallel.") try: content = parent["content"] diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 64bc0acae..4f2fc33b2 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -168,11 +168,13 @@ class InstallIPythonKernelSpecApp(Application): name = Unicode("ipython-kernel-install") def initialize(self, argv=None): + """Initialize the app.""" if argv is None: argv = sys.argv[1:] self.argv = argv def start(self): + """Start the app.""" import argparse parser = argparse.ArgumentParser( diff --git a/ipykernel/log.py b/ipykernel/log.py index aca21d25b..d55c21d04 100644 --- a/ipykernel/log.py +++ b/ipykernel/log.py @@ -1,3 +1,4 @@ +"""A PUB log handler.""" import warnings from zmq.log.handlers import PUBHandler @@ -15,6 +16,7 @@ class EnginePUBHandler(PUBHandler): engine = None def __init__(self, engine, *args, **kwargs): + """Initialize the handler.""" PUBHandler.__init__(self, *args, **kwargs) self.engine = engine diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 139b833e4..e48c5b4de 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -1,3 +1,4 @@ +"""A parent poller for unix.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. @@ -22,10 +23,12 @@ class ParentPollerUnix(Thread): """ def __init__(self): + """Initialize the poller.""" super().__init__() self.daemon = True def run(self): + """Run the poller.""" # We cannot use os.waitpid because it works only for child processes. from errno import EINTR diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index ed7d4a7b8..8e2dbe5f3 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -118,6 +118,8 @@ def use_cloudpickle(): class CannedObject: + """A canned object.""" + def __init__(self, obj, keys=None, hook=None): """can an object for safe pickling @@ -145,6 +147,7 @@ def __init__(self, obj, keys=None, hook=None): self.buffers = [] def get_object(self, g=None): + """Get an object.""" if g is None: g = {} obj = self.obj @@ -161,15 +164,18 @@ class Reference(CannedObject): """object for wrapping a remote reference by name.""" def __init__(self, name): + """Initialize the reference.""" if not isinstance(name, str): raise TypeError("illegal name: %r" % name) self.name = name self.buffers = [] def __repr__(self): + """Get the string repr of the reference.""" return "" % self.name def get_object(self, g=None): + """Get an object in the reference.""" if g is None: g = {} @@ -180,19 +186,25 @@ class CannedCell(CannedObject): """Can a closure cell""" def __init__(self, cell): + """Initialize the canned cell.""" self.cell_contents = can(cell.cell_contents) def get_object(self, g=None): + """Get an object in the cell.""" cell_contents = uncan(self.cell_contents, g) def inner(): + """Inner function.""" return cell_contents return inner.__closure__[0] # type:ignore[index] class CannedFunction(CannedObject): + """Can a function.""" + def __init__(self, f): + """Initialize the can""" self._check_type(f) self.code = f.__code__ self.defaults: typing.Optional[typing.List[typing.Any]] @@ -216,6 +228,7 @@ def _check_type(self, obj): assert isinstance(obj, FunctionType), "Not a function type" def get_object(self, g=None): + """Get an object out of the can.""" # try to load function back into its module: if not self.module.startswith("__"): __import__(self.module) @@ -236,7 +249,10 @@ def get_object(self, g=None): class CannedClass(CannedObject): + """A canned class object.""" + def __init__(self, cls): + """Initialize the can.""" self._check_type(cls) self.name = cls.__name__ self.old_style = not isinstance(cls, type) @@ -256,12 +272,16 @@ def _check_type(self, obj): assert isinstance(obj, class_type), "Not a class type" def get_object(self, g=None): + """Get an object from the can.""" parents = tuple(uncan(p, g) for p in self.parents) return type(self.name, parents, uncan_dict(self._canned_dict, g=g)) class CannedArray(CannedObject): + """A canned numpy array.""" + def __init__(self, obj): + """Initialize the can.""" from numpy import ascontiguousarray self.shape = obj.shape @@ -283,6 +303,7 @@ def __init__(self, obj): self.buffers = [buffer(obj)] def get_object(self, g=None): + """Get the object.""" from numpy import frombuffer data = self.buffers[0] @@ -294,6 +315,8 @@ def get_object(self, g=None): class CannedBytes(CannedObject): + """A canned bytes object.""" + @staticmethod def wrap(buf: typing.Union[memoryview, bytes, typing.SupportsBytes]) -> bytes: """Cast a buffer or memoryview object to bytes""" @@ -304,18 +327,24 @@ def wrap(buf: typing.Union[memoryview, bytes, typing.SupportsBytes]) -> bytes: return buf def __init__(self, obj): + """Initialize the can.""" self.buffers = [obj] def get_object(self, g=None): + """Get the canned object.""" data = self.buffers[0] return self.wrap(data) class CannedBuffer(CannedBytes): + """A canned buffer.""" + wrap = buffer # type:ignore[assignment] class CannedMemoryView(CannedBytes): + """A canned memory view.""" + wrap = memoryview # type:ignore[assignment] @@ -377,6 +406,7 @@ def can(obj): def can_class(obj): + """Can a class object.""" if isinstance(obj, class_type) and obj.__module__ == "__main__": return CannedClass(obj) else: @@ -427,6 +457,7 @@ def uncan(obj, g=None): def uncan_dict(obj, g=None): + """Uncan a dict object.""" if istype(obj, dict): newobj = {} for k, v in obj.items(): @@ -437,6 +468,7 @@ def uncan_dict(obj, g=None): def uncan_sequence(obj, g=None): + """Uncan a sequence.""" if istype(obj, sequence_types): t = type(obj) return t([uncan(i, g) for i in obj]) diff --git a/ipykernel/tests/test_message_spec.py b/ipykernel/tests/test_message_spec.py index 1ba5f7651..5710cf297 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/ipykernel/tests/test_message_spec.py @@ -252,9 +252,10 @@ class HistoryReply(Reply): "display_data": DisplayData(), "header": RHeader(), } -""" -Specifications of `content` part of the reply messages. -""" + +# ----------------------------------------------------------------------------- +# Specifications of `content` part of the reply messages. +# ----------------------------------------------------------------------------- def validate_message(msg, msg_type=None, parent=None): diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index 280501acf..333eb2461 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -1,3 +1,4 @@ +"""A trio loop runner.""" import builtins import logging import signal @@ -9,11 +10,15 @@ class TrioRunner: + """A trio loop runner.""" + def __init__(self): + """Initialize the runner.""" self._cell_cancel_scope = None self._trio_token = None def initialize(self, kernel, io_loop): + """Initialize the runner.""" kernel.shell.set_trio_runner(self) kernel.shell.run_line_magic("autoawait", "trio") kernel.shell.magics_manager.magics["line"]["autoawait"] = lambda _: warnings.warn( @@ -24,12 +29,14 @@ def initialize(self, kernel, io_loop): bg_thread.start() def interrupt(self, signum, frame): + """Interuppt the runner.""" if self._cell_cancel_scope: self._cell_cancel_scope.cancel() else: raise Exception("Kernel interrupted but no cell is running") def run(self): + """Run the loop.""" old_sig = signal.signal(signal.SIGINT, self.interrupt) def log_nursery_exc(exc): @@ -37,6 +44,7 @@ def log_nursery_exc(exc): logging.error("An exception occurred in a global nursery task.\n%s", exc) async def trio_main(): + """Run the main loop.""" self._trio_token = trio.lowlevel.current_trio_token() async with trio.open_nursery() as nursery: # TODO This hack prevents the nursery from cancelling all child @@ -49,7 +57,10 @@ async def trio_main(): signal.signal(signal.SIGINT, old_sig) def __call__(self, async_fn): + """Handle a function call.""" + async def loc(coro): + """A thread runner context.""" self._cell_cancel_scope = trio.CancelScope() with self._cell_cancel_scope: return await coro diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index ff418b863..2d08c1136 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -194,6 +194,8 @@ def unregister_hook(self, hook): @magics_class class KernelMagics(Magics): + """Kernel magics.""" + # ------------------------------------------------------------------------ # Magic overrides # ------------------------------------------------------------------------ @@ -467,6 +469,7 @@ def _update_exit_now(self, change): # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no # interactive input being read; we provide event loop support in ipkernel def enable_gui(self, gui): + """Enable a given guil.""" from .eventloops import enable_gui as real_enable_gui try: @@ -491,6 +494,7 @@ def init_environment(self): env["GIT_PAGER"] = "cat" def init_hooks(self): + """Initialize hooks.""" super().init_hooks() self.set_hook("show_in_pager", page.as_hook(payloadpage.page), 99) @@ -526,6 +530,7 @@ def ask_exit(self): self.payload_manager.write_payload(payload) def run_cell(self, *args, **kwargs): + """Run a cell.""" self._last_traceback = None return super().run_cell(*args, **kwargs) @@ -586,14 +591,17 @@ def set_parent(self, parent): pass def get_parent(self): + """Get the parent header.""" return self.parent_header def init_magics(self): + """Initialize magics.""" super().init_magics() self.register_magics(KernelMagics) self.magics_manager.register_alias("ed", "edit") def init_virtualenv(self): + """Initialize virtual environment.""" # Overridden not to do virtualenv detection, because it's probably # not appropriate in a kernel. To use a kernel in a virtualenv, install # it inside the virtualenv. diff --git a/pyproject.toml b/pyproject.toml index 9904b54b9..b35209add 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,8 @@ docs = [ "sphinx", "myst_parser", "pydata_sphinx_theme", - "sphinxcontrib_github_alt" + "sphinxcontrib_github_alt", + "sphinxcontrib-spelling" ] test = [ "pytest>=7.0", @@ -99,7 +100,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==22.10.0", "mdformat>0.7", "ruff==0.0.177"] +dependencies = ["black==22.10.0", "mdformat>0.7", "ruff==0.0.189"] detached = true [tool.hatch.envs.lint.scripts] style = [ @@ -250,3 +251,16 @@ unfixable = [ # N802 Function name `assertIn` should be lowercase # F841 Local variable `t` is assigned to but never used "ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841"] + +[tool.interrogate] +ignore-init-module=true +ignore-private=true +ignore-semiprivate=true +ignore-property-decorators=true +ignore-nested-functions=true +ignore-nested-classes=true +fail-under=95 +exclude = ["docs", "*/tests"] + +[tool.check-wheel-contents] +toplevel = ["ipykernel/", "ipykernel_launcher.py"] From fbea757e117c1d3b0da29a40b4abcf3133a310f4 Mon Sep 17 00:00:00 2001 From: Emilio Graff <1@emil.io> Date: Mon, 26 Dec 2022 06:25:31 -0800 Subject: [PATCH 0957/1195] ENH: add `%gui` support for Qt6 (#1054) * Initial notes * ENH: Support for `PyQt6` and `PySide6`. - Distinguish between specific version requests and the generic one. - Use a `QEventLoop` instance to properly keep windows open between event loop calls. This is "instrumented" with print statements to follow the flow * Move `Qt` importing to client side This way import errors show up in the client, not the kernel. * Bring in some changes by @tacaswell See https://github.com/ipython/ipykernel/pull/782/commits/d5d718bc08609568d39aa3fed28b68715b83001d * Remove diagnostic `print` statements * Move last version check up * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Remove unused import * These seem to run fine in Windows. * TST: Qt event loop logic * Fix "Test Minimum Versions" CI test * Use `IPython` constants and version check. Importing a second version of Qt is not allowed. `IPython` silently ignores requests for different versions; we want `enable_gui` to raise an exception so the user can see it. * Add two Qt versions to test matrix * Improved logic * get coverage on windows back * more targeted windows skip * rename symbol in test * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- ipykernel/eventloops.py | 262 ++++++++++++++++++++++-------- ipykernel/tests/test_eventloop.py | 62 ++++++- pyproject.toml | 11 ++ 3 files changed, 262 insertions(+), 73 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 76d1c700f..d73fbe18e 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -21,41 +21,6 @@ def _use_appnope(): return sys.platform == "darwin" and V(platform.mac_ver()[0]) >= V("10.9") -def _notify_stream_qt(kernel): - - from IPython.external.qt_for_kernel import QtCore - - def process_stream_events(): - """fall back to main loop when there's a socket event""" - # call flush to ensure that the stream doesn't lose events - # due to our consuming of the edge-triggered FD - # flush returns the number of events consumed. - # if there were any, wake it up - if kernel.shell_stream.flush(limit=1): - kernel._qt_notifier.setEnabled(False) - kernel.app.quit() - - if not hasattr(kernel, "_qt_notifier"): - fd = kernel.shell_stream.getsockopt(zmq.FD) - kernel._qt_notifier = QtCore.QSocketNotifier(fd, QtCore.QSocketNotifier.Read, kernel.app) - kernel._qt_notifier.activated.connect(process_stream_events) - else: - kernel._qt_notifier.setEnabled(True) - - # there may already be unprocessed events waiting. - # these events will not wake zmq's edge-triggered FD - # since edge-triggered notification only occurs on new i/o activity. - # process all the waiting events immediately - # so we start in a clean state ensuring that any new i/o events will notify. - # schedule first call on the eventloop as soon as it's running, - # so we don't block here processing events - if not hasattr(kernel, "_qt_timer"): - kernel._qt_timer = QtCore.QTimer(kernel.app) - kernel._qt_timer.setSingleShot(True) - kernel._qt_timer.timeout.connect(process_stream_events) - kernel._qt_timer.start(0) - - # mapping of keys to loop functions loop_map = { "inline": None, @@ -103,54 +68,67 @@ def exit_decorator(exit_func): return decorator -def _loop_qt(app): - """Inner-loop for running the Qt eventloop - - Pulled from guisupport.start_event_loop in IPython < 5.2, - since IPython 5.2 only checks `get_ipython().active_eventloop` is defined, - rather than if the eventloop is actually running. - """ - app._in_event_loop = True - app.exec_() - app._in_event_loop = False - +def _notify_stream_qt(kernel): + import operator + from functools import lru_cache -@register_integration("qt4") -def loop_qt4(kernel): - """Start a kernel with PyQt4 event loop integration.""" + from IPython.external.qt_for_kernel import QtCore - from IPython.external.qt_for_kernel import QtGui - from IPython.lib.guisupport import get_app_qt4 + try: + from IPython.external.qt_for_kernel import enum_helper + except ImportError: - kernel.app = get_app_qt4([" "]) - if isinstance(kernel.app, QtGui.QApplication): - kernel.app.setQuitOnLastWindowClosed(False) - _notify_stream_qt(kernel) + @lru_cache(None) + def enum_helper(name): + return operator.attrgetter(name.rpartition(".")[0])(sys.modules[QtCore.__package__]) - _loop_qt(kernel.app) + def process_stream_events(): + """fall back to main loop when there's a socket event""" + # call flush to ensure that the stream doesn't lose events + # due to our consuming of the edge-triggered FD + # flush returns the number of events consumed. + # if there were any, wake it up + if kernel.shell_stream.flush(limit=1): + kernel._qt_notifier.setEnabled(False) + kernel.app.qt_event_loop.quit() + if not hasattr(kernel, "_qt_notifier"): + fd = kernel.shell_stream.getsockopt(zmq.FD) + kernel._qt_notifier = QtCore.QSocketNotifier( + fd, enum_helper('QtCore.QSocketNotifier.Type').Read, kernel.app.qt_event_loop + ) + kernel._qt_notifier.activated.connect(process_stream_events) + else: + kernel._qt_notifier.setEnabled(True) -@register_integration("qt", "qt5") -def loop_qt5(kernel): - """Start a kernel with PyQt5 event loop integration.""" - if os.environ.get("QT_API", None) is None: - try: - import PyQt5 # noqa + # there may already be unprocessed events waiting. + # these events will not wake zmq's edge-triggered FD + # since edge-triggered notification only occurs on new i/o activity. + # process all the waiting events immediately + # so we start in a clean state ensuring that any new i/o events will notify. + # schedule first call on the eventloop as soon as it's running, + # so we don't block here processing events + if not hasattr(kernel, "_qt_timer"): + kernel._qt_timer = QtCore.QTimer(kernel.app) + kernel._qt_timer.setSingleShot(True) + kernel._qt_timer.timeout.connect(process_stream_events) + kernel._qt_timer.start(0) - os.environ["QT_API"] = "pyqt5" - except ImportError: - try: - import PySide2 # noqa - os.environ["QT_API"] = "pyside2" - except ImportError: - os.environ["QT_API"] = "pyqt5" - return loop_qt4(kernel) +@register_integration("qt", "qt4", "qt5", "qt6") +def loop_qt(kernel): + """Event loop for all versions of Qt.""" + _notify_stream_qt(kernel) # install hook to stop event loop. + # Start the event loop. + kernel.app._in_event_loop = True + # `exec` blocks until there's ZMQ activity. + el = kernel.app.qt_event_loop # for brevity + el.exec() if hasattr(el, 'exec') else el.exec_() + kernel.app._in_event_loop = False # exit and watch are the same for qt 4 and 5 -@loop_qt4.exit -@loop_qt5.exit +@loop_qt.exit def loop_qt_exit(kernel): kernel.app.exit() @@ -450,6 +428,135 @@ def close_loop(): loop.close() +# The user can generically request `qt` or a specific Qt version, e.g. `qt6`. For a generic Qt +# request, we let the mechanism in IPython choose the best available version by leaving the `QT_API` +# environment variable blank. +# +# For specific versions, we check to see whether the PyQt or PySide implementations are present and +# set `QT_API` accordingly to indicate to IPython which version we want. If neither implementation +# is present, we leave the environment variable set so IPython will generate a helpful error +# message. +# +# NOTE: if the environment variable is already set, it will be used unchanged, regardless of what +# the user requested. + + +def set_qt_api_env_from_gui(gui): + """ + Sets the QT_API environment variable by trying to import PyQtx or PySidex. + + If QT_API is already set, ignore the request. + """ + qt_api = os.environ.get("QT_API", None) + + from IPython.external.qt_loaders import ( + QT_API_PYQT, + QT_API_PYQT5, + QT_API_PYQT6, + QT_API_PYSIDE, + QT_API_PYSIDE2, + QT_API_PYSIDE6, + QT_API_PYQTv1, + loaded_api, + ) + + loaded = loaded_api() + + qt_env2gui = { + QT_API_PYSIDE: 'qt4', + QT_API_PYQTv1: 'qt4', + QT_API_PYQT: 'qt4', + QT_API_PYSIDE2: 'qt5', + QT_API_PYQT5: 'qt5', + QT_API_PYSIDE6: 'qt6', + QT_API_PYQT6: 'qt6', + } + if loaded is not None and gui != 'qt': + if qt_env2gui[loaded] != gui: + raise ImportError( + f'Cannot switch Qt versions for this session; must use {qt_env2gui[loaded]}.' + ) + + if qt_api is not None and gui != 'qt': + if qt_env2gui[qt_api] != gui: + print( + f'Request for "{gui}" will be ignored because `QT_API` ' + f'environment variable is set to "{qt_api}"' + ) + else: + if gui == 'qt4': + try: + import PyQt # noqa + + os.environ["QT_API"] = "pyqt" + except ImportError: + try: + import PySide # noqa + + os.environ["QT_API"] = "pyside" + except ImportError: + # Neither implementation installed; set it to something so IPython gives an error + os.environ["QT_API"] = "pyqt" + elif gui == 'qt5': + try: + import PyQt5 # noqa + + os.environ["QT_API"] = "pyqt5" + except ImportError: + try: + import PySide2 # noqa + + os.environ["QT_API"] = "pyside2" + except ImportError: + os.environ["QT_API"] = "pyqt5" + elif gui == 'qt6': + try: + import PyQt6 # noqa + + os.environ["QT_API"] = "pyqt6" + except ImportError: + try: + import PySide6 # noqa + + os.environ["QT_API"] = "pyside6" + except ImportError: + os.environ["QT_API"] = "pyqt6" + elif gui == 'qt': + # Don't set QT_API; let IPython logic choose the version. + if 'QT_API' in os.environ.keys(): + del os.environ['QT_API'] + else: + raise ValueError( + f'Unrecognized Qt version: {gui}. Should be "qt4", "qt5", "qt6", or "qt".' + ) + + # Do the actual import now that the environment variable is set to make sure it works. + try: + from IPython.external.qt_for_kernel import QtCore, QtGui # noqa + except ImportError: + # Clear the environment variable for the next attempt. + if 'QT_API' in os.environ.keys(): + del os.environ["QT_API"] + raise + + +def make_qt_app_for_kernel(gui, kernel): + """Sets the `QT_API` environment variable if it isn't already set.""" + if hasattr(kernel, 'app'): + raise RuntimeError('Kernel already running a Qt event loop.') + + set_qt_api_env_from_gui(gui) + # This import is guaranteed to work now: + from IPython.external.qt_for_kernel import QtCore, QtGui + from IPython.lib.guisupport import get_app_qt4 + + kernel.app = get_app_qt4([" "]) + if isinstance(kernel.app, QtGui.QApplication): + kernel.app.setQuitOnLastWindowClosed(False) + + kernel.app.qt_event_loop = QtCore.QEventLoop(kernel.app) + + def enable_gui(gui, kernel=None): """Enable integration with a given GUI""" if gui not in loop_map: @@ -463,7 +570,18 @@ def enable_gui(gui, kernel=None): "You didn't specify a kernel," " and no IPython Application with a kernel appears to be running." ) + if gui is None: + # User wants to turn off integration; clear any evidence if Qt was the last one. + if hasattr(kernel, 'app'): + delattr(kernel, 'app') + else: + if gui.startswith('qt'): + # Prepare the kernel here so any exceptions are displayed in the client. + make_qt_app_for_kernel(gui, kernel) + loop = loop_map[gui] if loop and kernel.eventloop is not None and kernel.eventloop is not loop: raise RuntimeError("Cannot activate multiple GUI eventloops") kernel.eventloop = loop + # We set `eventloop`; the function the user chose is executed in `Kernel.enter_eventloop`, thus + # any exceptions raised during the event loop will not be shown in the client. diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index 7d0063e61..3a684d625 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -9,12 +9,39 @@ import pytest import tornado -from ipykernel.eventloops import enable_gui, loop_asyncio, loop_cocoa, loop_tk +from ipykernel.eventloops import ( + enable_gui, + loop_asyncio, + loop_cocoa, + loop_tk, + set_qt_api_env_from_gui, +) from .utils import execute, flush_channels, start_new_kernel KC = KM = None +qt_guis_avail = [] + + +def _get_qt_vers(): + """If any version of Qt is available, this will populate `guis_avail` with 'qt' and 'qtx'. Due + to the import mechanism, we can't import multiple versions of Qt in one session.""" + for gui in ['qt', 'qt6', 'qt5', 'qt4']: + print(f'Trying {gui}') + try: + set_qt_api_env_from_gui(gui) + qt_guis_avail.append(gui) + if 'QT_API' in os.environ.keys(): + del os.environ['QT_API'] + except ImportError: + pass # that version of Qt isn't available. + except RuntimeError: + pass # the version of IPython doesn't know what to do with this Qt version. + + +_get_qt_vers() + def setup(): """start the global kernel (if it isn't running) and return its client""" @@ -97,3 +124,36 @@ def test_enable_gui(kernel): @pytest.mark.skipif(sys.platform != "darwin", reason="MacOS-only") def test_cocoa_loop(kernel): loop_cocoa(kernel) + + +@pytest.mark.skipif( + len(qt_guis_avail) == 0, reason='No viable version of PyQt or PySide installed.' +) +def test_qt_enable_gui(kernel): + gui = qt_guis_avail[0] + + enable_gui(gui, kernel) + + # We store the `QApplication` instance in the kernel. + assert hasattr(kernel, 'app') + # And the `QEventLoop` is added to `app`:` + assert hasattr(kernel.app, 'qt_event_loop') + + # Can't start another event loop, even if `gui` is the same. + with pytest.raises(RuntimeError): + enable_gui(gui, kernel) + + # Event loop intergration can be turned off. + enable_gui(None, kernel) + assert not hasattr(kernel, 'app') + + # But now we're stuck with this version of Qt for good; can't switch. + for not_gui in ['qt6', 'qt5', 'qt4']: + if not_gui not in qt_guis_avail: + break + + with pytest.raises(ImportError): + enable_gui(not_gui, kernel) + + # A gui of 'qt' means "best available", or in this case, the last one that was used. + enable_gui('qt', kernel) diff --git a/pyproject.toml b/pyproject.toml index b35209add..b8c79fbad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,8 @@ cov = [ "curio", "trio", ] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] [tool.hatch.version] path = "ipykernel/_version.py" @@ -93,6 +95,15 @@ features = ["test", "cov"] test = "python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered {args}" nowarn = "test -W default {args}" +[[tool.hatch.envs.cov.matrix]] +qt = ["qt5", "qt6"] + +[tool.hatch.envs.cov.overrides] +matrix.qt.features = [ + { value = "pyqt5", if = ["qt5"] }, + { value = "pyside6", if = ["qt6"] }, +] + [tool.hatch.envs.typing] features = ["test"] dependencies = ["mypy>=0.990"] From 828e55ca4f75241f20486c93d28032ccc2b76911 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 26 Dec 2022 14:29:57 +0000 Subject: [PATCH 0958/1195] Publish 6.20.0 SHA256 hashes: ipykernel-6.20.0-py3-none-any.whl: 7614640d703f052fc92e719b1c397a5e62dfdee8d946e136edbf016805475c01 ipykernel-6.20.0.tar.gz: 7dfafaec645300710c41479c0b0bdc13a9ea6639bee6e3ad042757674e9a9a29 --- CHANGELOG.md | 22 ++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77eb4705f..a43631d24 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ +## 6.20.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.4...fbea757e117c1d3b0da29a40b4abcf3133a310f4)) + +### Enhancements made + +- ENH: add `%gui` support for Qt6 [#1054](https://github.com/ipython/ipykernel/pull/1054) ([@shaperilio](https://github.com/shaperilio)) + +### Maintenance and upkeep improvements + +- Add more ci checks [#1063](https://github.com/ipython/ipykernel/pull/1063) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-12-20&to=2022-12-26&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-12-20..2022-12-26&type=Issues) | [@shaperilio](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ashaperilio+updated%3A2022-12-20..2022-12-26&type=Issues) + + + ## 6.19.4 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.3...07da48e686b5906525c2a6b8cfc11cd7c3d96a5f)) @@ -16,8 +36,6 @@ [@bollwyvl](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abollwyvl+updated%3A2022-12-19..2022-12-20&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2022-12-19..2022-12-20&type=Issues) - - ## 6.19.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.2...0925d09075280beb23c009ca0d361f73e5402e27)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index f999d5aff..bc7a190a9 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.19.4" +__version__ = "6.20.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From e3c74d0dec4314718cb52ac60ab52aafa0576709 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Jan 2023 08:17:27 +0100 Subject: [PATCH 0959/1195] [pre-commit.ci] pre-commit autoupdate (#1068) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/psf/black: 22.10.0 → 22.12.0](https://github.com/psf/black/compare/22.10.0...22.12.0) - [github.com/charliermarsh/ruff-pre-commit: v0.0.189 → v0.0.207](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.189...v0.0.207) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 419f3dc57..fc3761864 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -31,12 +31,12 @@ repos: - id: mdformat - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 22.12.0 hooks: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.189 + rev: v0.0.207 hooks: - id: ruff args: ["--fix"] From 817258df78ee9d718081574e627edda2622c6af9 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 3 Jan 2023 09:33:06 -0600 Subject: [PATCH 0960/1195] Fix types and sync lint deps (#1070) fix types and sync lint deps --- ipykernel/inprocess/tests/test_kernel.py | 2 +- ipykernel/ipkernel.py | 2 +- ipykernel/kernelapp.py | 22 +++++++++++----------- ipykernel/tests/test_kernel.py | 2 +- ipykernel/tests/test_zmq_shell.py | 2 +- ipykernel/zmqshell.py | 14 +++++++++----- pyproject.toml | 2 +- 7 files changed, 25 insertions(+), 21 deletions(-) diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index 947b5823e..defe039df 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -6,7 +6,7 @@ from io import StringIO import pytest -from IPython.utils.io import capture_output +from IPython.utils.io import capture_output # type:ignore[attr-defined] from jupyter_client.session import Session from ipykernel.inprocess.blocking import BlockingInProcessKernelClient diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index f4b67caa2..a4b975a4b 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -26,7 +26,7 @@ from .zmqshell import ZMQInteractiveShell try: - from IPython.core.interactiveshell import _asyncio_runner + from IPython.core.interactiveshell import _asyncio_runner # type:ignore[attr-defined] except ImportError: _asyncio_runner = None diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 24355b14b..a98439cfc 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -15,7 +15,7 @@ from logging import StreamHandler import zmq -from IPython.core.application import ( +from IPython.core.application import ( # type:ignore[attr-defined] BaseIPythonApplication, base_aliases, base_flags, @@ -88,12 +88,12 @@ ) # inherit flags&aliases for any IPython shell apps -kernel_aliases.update(shell_aliases) +kernel_aliases.update(shell_aliases) # type:ignore[arg-type] kernel_flags.update(shell_flags) # inherit flags&aliases for Sessions -kernel_aliases.update(session_aliases) -kernel_flags.update(session_flags) +kernel_aliases.update(session_aliases) # type:ignore[arg-type] +kernel_flags.update(session_flags) # type:ignore[arg-type] _ctrl_c_message = """\ NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work. @@ -114,8 +114,8 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMix """The IPYKernel application class.""" name = "ipython-kernel" - aliases = Dict(kernel_aliases) - flags = Dict(kernel_flags) + aliases = Dict(kernel_aliases) # type:ignore[assignment] + flags = Dict(kernel_flags) # type:ignore[assignment] classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session] # the kernel class, as an importstring kernel_class = Type( @@ -429,7 +429,7 @@ def log_connection_info(self): self.log.info(line) # also raw print to the terminal if no parent_handle (`ipython kernel`) # unless log-level is CRITICAL (--quiet) - if not self.parent_handle and self.log_level < logging.CRITICAL: + if not self.parent_handle and int(self.log_level) < logging.CRITICAL: print(_ctrl_c_message, file=sys.__stdout__) for line in lines: print(line, file=sys.__stdout__) @@ -658,9 +658,9 @@ def init_pdb(self): if hasattr(debugger, "InterruptiblePdb"): # Only available in newer IPython releases: - debugger.Pdb = debugger.InterruptiblePdb - pdb.Pdb = debugger.Pdb # type:ignore[misc] - pdb.set_trace = debugger.set_trace + debugger.Pdb = debugger.InterruptiblePdb # type:ignore + pdb.Pdb = debugger.Pdb # type:ignore + pdb.set_trace = debugger.set_trace # type:ignore[assignment] @catch_config_error def initialize(self, argv=None): @@ -687,7 +687,7 @@ def initialize(self, argv=None): except Exception: # Catch exception when initializing signal fails, eg when running the # kernel on a separate thread - if self.log_level < logging.CRITICAL: + if int(self.log_level) < logging.CRITICAL: self.log.error("Unable to initialize signal:", exc_info=True) self.init_kernel() # shell init steps diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 0647ef442..b48d51967 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -517,7 +517,7 @@ def _print_and_exit(sig, frame): def _start_children(): - ip = IPython.get_ipython() + ip = IPython.get_ipython() # type:ignore[attr-defined] ns = ip.user_ns cmd = [sys.executable, "-c", f"from {__name__} import _child; _child()"] diff --git a/ipykernel/tests/test_zmq_shell.py b/ipykernel/tests/test_zmq_shell.py index 52e11e43a..665139271 100644 --- a/ipykernel/tests/test_zmq_shell.py +++ b/ipykernel/tests/test_zmq_shell.py @@ -238,7 +238,7 @@ def test_zmq_interactive_shell(kernel): with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) - shell.data_pub_class = MagicMock() + shell.data_pub_class = MagicMock() # type:ignore shell.data_pub shell.kernel = kernel shell.set_next_input("hi") diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 2d08c1136..f36baf5f7 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -25,11 +25,11 @@ from IPython.core.error import UsageError from IPython.core.interactiveshell import InteractiveShell, InteractiveShellABC from IPython.core.magic import Magics, line_magic, magics_class -from IPython.core.magics import CodeMagics, MacroToEdit +from IPython.core.magics import CodeMagics, MacroToEdit # type:ignore[attr-defined] from IPython.core.usage import default_banner -from IPython.display import Javascript, display +from IPython.display import Javascript, display # type:ignore[attr-defined] from IPython.utils import openpy -from IPython.utils.process import arg_split, system +from IPython.utils.process import arg_split, system # type:ignore[attr-defined] from jupyter_client.session import Session, extract_header from jupyter_core.paths import jupyter_runtime_dir from traitlets import Any, CBool, CBytes, Dict, Instance, Type, default, observe @@ -296,6 +296,7 @@ def edit(self, parameter_s="", last_call=None): filename = os.path.abspath(filename) payload = {"source": "edit_magic", "filename": filename, "line_number": lineno} + assert self.shell is not None self.shell.payload_manager.write_payload(payload) # A few magics that are adapted to the specifics of using pexpect and a @@ -304,6 +305,7 @@ def edit(self, parameter_s="", last_call=None): @line_magic def clear(self, arg_s): """Clear the terminal.""" + assert self.shell is not None if os.name == "posix": self.shell.system("clear") else: @@ -324,6 +326,7 @@ def less(self, arg_s): raise UsageError("Missing filename.") if arg_s.endswith(".py"): + assert self.shell is not None cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False)) else: with open(arg_s) as fid: @@ -338,6 +341,7 @@ def less(self, arg_s): @line_magic def man(self, arg_s): """Find the man page for the given command and display in pager.""" + assert self.shell is not None page.page(self.shell.getoutput("man %s | col -b" % arg_s, split=False)) @line_magic @@ -430,7 +434,7 @@ class ZMQInteractiveShell(InteractiveShell): displayhook_class = Type(ZMQShellDisplayHook) display_pub_class = Type(ZMQDisplayPublisher) - data_pub_class = Any() + data_pub_class = Any() # type:ignore[assignment] kernel = Any() parent_header = Any() @@ -511,7 +515,7 @@ def data_pub(self): stacklevel=2, ) - self._data_pub = self.data_pub_class(parent=self) + self._data_pub = self.data_pub_class(parent=self) # type:ignore[has-type] self._data_pub.session = self.display_pub.session self._data_pub.pub_socket = self.display_pub.pub_socket return self._data_pub diff --git a/pyproject.toml b/pyproject.toml index b8c79fbad..77b885ecb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==22.10.0", "mdformat>0.7", "ruff==0.0.189"] +dependencies = ["black==22.12.0", "mdformat>0.7", "ruff==0.0.207"] detached = true [tool.hatch.envs.lint.scripts] style = [ From 4f0e252d745c44aa3a3109c8274e5014a512f462 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 8 Jan 2023 14:41:45 -0600 Subject: [PATCH 0961/1195] Update CI (#1073) update ci --- .pre-commit-config.yaml | 2 +- ipykernel/connect.py | 3 ++- ipykernel/eventloops.py | 19 ++++++++++--------- ipykernel/inprocess/channels.py | 3 ++- ipykernel/inprocess/client.py | 9 ++++++--- ipykernel/inprocess/manager.py | 6 ++++-- ipykernel/iostream.py | 15 ++++++++++----- ipykernel/jsonutil.py | 3 ++- ipykernel/kernelbase.py | 18 ++++++++---------- ipykernel/parentpoller.py | 3 ++- ipykernel/trio_runner.py | 3 ++- ipykernel/zmqshell.py | 6 ++++-- pyproject.toml | 8 +++++--- 13 files changed, 58 insertions(+), 40 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fc3761864..2b903131f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.207 + rev: v0.0.215 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel/connect.py b/ipykernel/connect.py index f76091674..abc1a8211 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -26,7 +26,8 @@ def get_connection_file(app=None): from ipykernel.kernelapp import IPKernelApp if not IPKernelApp.initialized(): - raise RuntimeError("app not specified, and not in a running Kernel") + msg = "app not specified, and not in a running Kernel" + raise RuntimeError(msg) app = IPKernelApp.instance() return filefind(app.connection_file, [".", app.connection_dir]) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index d73fbe18e..551c6efc7 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -473,9 +473,8 @@ def set_qt_api_env_from_gui(gui): } if loaded is not None and gui != 'qt': if qt_env2gui[loaded] != gui: - raise ImportError( - f'Cannot switch Qt versions for this session; must use {qt_env2gui[loaded]}.' - ) + msg = f'Cannot switch Qt versions for this session; must use {qt_env2gui[loaded]}.' + raise ImportError(msg) if qt_api is not None and gui != 'qt': if qt_env2gui[qt_api] != gui: @@ -526,9 +525,8 @@ def set_qt_api_env_from_gui(gui): if 'QT_API' in os.environ.keys(): del os.environ['QT_API'] else: - raise ValueError( - f'Unrecognized Qt version: {gui}. Should be "qt4", "qt5", "qt6", or "qt".' - ) + msg = f'Unrecognized Qt version: {gui}. Should be "qt4", "qt5", "qt6", or "qt".' + raise ValueError(msg) # Do the actual import now that the environment variable is set to make sure it works. try: @@ -543,7 +541,8 @@ def set_qt_api_env_from_gui(gui): def make_qt_app_for_kernel(gui, kernel): """Sets the `QT_API` environment variable if it isn't already set.""" if hasattr(kernel, 'app'): - raise RuntimeError('Kernel already running a Qt event loop.') + msg = 'Kernel already running a Qt event loop.' + raise RuntimeError(msg) set_qt_api_env_from_gui(gui) # This import is guaranteed to work now: @@ -566,10 +565,11 @@ def enable_gui(gui, kernel=None): if Application.initialized(): kernel = getattr(Application.instance(), "kernel", None) if kernel is None: - raise RuntimeError( + msg = ( "You didn't specify a kernel," " and no IPython Application with a kernel appears to be running." ) + raise RuntimeError(msg) if gui is None: # User wants to turn off integration; clear any evidence if Qt was the last one. if hasattr(kernel, 'app'): @@ -581,7 +581,8 @@ def enable_gui(gui, kernel=None): loop = loop_map[gui] if loop and kernel.eventloop is not None and kernel.eventloop is not loop: - raise RuntimeError("Cannot activate multiple GUI eventloops") + msg = "Cannot activate multiple GUI eventloops" + raise RuntimeError(msg) kernel.eventloop = loop # We set `eventloop`; the function the user chose is executed in `Kernel.enter_eventloop`, thus # any exceptions raised during the event loop will not be shown in the client. diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 9b054e043..964015ed3 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -40,7 +40,8 @@ def call_handlers(self, msg): Subclasses should override this method to handle incoming messages. """ - raise NotImplementedError("call_handlers must be defined in a subclass.") + msg = "call_handlers must be defined in a subclass." + raise NotImplementedError(msg) def flush(self, timeout=1.0): """Flush the channel.""" diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 124d5cf3e..2d9397271 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -154,7 +154,8 @@ def history(self, raw=True, output=False, hist_access_type="range", **kwds): def shutdown(self, restart=False): """Handle shutdown.""" # FIXME: What to do here? - raise NotImplementedError("Cannot shutdown in-process kernel") + msg = "Cannot shutdown in-process kernel" + raise NotImplementedError(msg) def kernel_info(self): """Request kernel info.""" @@ -175,7 +176,8 @@ def comm_info(self, target_name=None): def input(self, string): """Handle kernel input.""" if self.kernel is None: - raise RuntimeError("Cannot send input reply. No kernel exists.") + msg = "Cannot send input reply. No kernel exists." + raise RuntimeError(msg) self.kernel.raw_input_str = string def is_complete(self, code): @@ -188,7 +190,8 @@ def _dispatch_to_kernel(self, msg): """Send a message to the kernel and handle a reply.""" kernel = self.kernel if kernel is None: - raise RuntimeError("Cannot send request. No kernel exists.") + msg = "Cannot send request. No kernel exists." + raise RuntimeError(msg) stream = kernel.shell_stream self.session.send(stream, msg) diff --git a/ipykernel/inprocess/manager.py b/ipykernel/inprocess/manager.py index 5649e8cab..3388dbf61 100644 --- a/ipykernel/inprocess/manager.py +++ b/ipykernel/inprocess/manager.py @@ -66,11 +66,13 @@ def _kill_kernel(self): def interrupt_kernel(self): """Interrupt the kernel.""" - raise NotImplementedError("Cannot interrupt in-process kernel.") + msg = "Cannot interrupt in-process kernel." + raise NotImplementedError(msg) def signal_kernel(self, signum): """Send a signal to the kernel.""" - raise NotImplementedError("Cannot signal in-process kernel.") + msg = "Cannot signal in-process kernel." + raise NotImplementedError(msg) def is_alive(self): """Test if the kernel is alive.""" diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 22b50a2a4..92df61aea 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -312,7 +312,8 @@ def fileno(self): if getattr(self, "_original_stdstream_copy", None) is not None: return self._original_stdstream_copy else: - raise io.UnsupportedOperation("fileno") + msg = "fileno" + raise io.UnsupportedOperation(msg) def _watch_pipe_fd(self): """ @@ -415,7 +416,8 @@ def __init__( if hasattr(echo, "read") and hasattr(echo, "write"): self.echo = echo else: - raise ValueError("echo argument must be a file like object") + msg = "echo argument must be a file like object" + raise ValueError(msg) def isatty(self): """Return a bool indicating whether this is an 'interactive' stream. @@ -540,7 +542,8 @@ def write(self, string: str) -> Optional[int]: # type:ignore[override] """ if not isinstance(string, str): - raise TypeError(f"write() argument must be str, not {type(string)}") + msg = f"write() argument must be str, not {type(string)}" + raise TypeError(msg) if self.echo is not None: try: @@ -550,7 +553,8 @@ def write(self, string: str) -> Optional[int]: # type:ignore[override] print(f"Write failed: {e}", file=sys.__stderr__) if self.pub_thread is None: - raise ValueError("I/O operation on closed file") + msg = "I/O operation on closed file" + raise ValueError(msg) else: is_child = not self._is_master_process() @@ -573,7 +577,8 @@ def write(self, string: str) -> Optional[int]: # type:ignore[override] def writelines(self, sequence): """Write lines to the stream.""" if self.pub_thread is None: - raise ValueError("I/O operation on closed file") + msg = "I/O operation on closed file" + raise ValueError(msg) else: for string in sequence: self.write(string) diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 41f63ac5b..9d6da754f 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -146,10 +146,11 @@ def json_clean(obj): # pragma: no cover nkeys = len(obj) nkeys_collapsed = len(set(map(str, obj))) if nkeys != nkeys_collapsed: - raise ValueError( + msg = ( "dict cannot be safely converted to JSON: " "key collision would lead to dropped values" ) + raise ValueError(msg) # If all OK, proceed by making the new dict that will be json-safe out = {} for k, v in obj.items(): diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 768056cfd..110f834c1 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1146,9 +1146,8 @@ def _send_abort_reply(self, stream, msg, idents): def _no_raw_input(self): """Raise StdinNotImplementedError if active frontend doesn't support stdin.""" - raise StdinNotImplementedError( - "raw_input was called, but this frontend does not support stdin." - ) + msg = "raw_input was called, but this frontend does not support stdin." + raise StdinNotImplementedError(msg) def getpass(self, prompt="", stream=None): """Forward getpass to frontends @@ -1158,9 +1157,8 @@ def getpass(self, prompt="", stream=None): StdinNotImplementedError if active frontend doesn't support stdin. """ if not self._allow_stdin: - raise StdinNotImplementedError( - "getpass was called, but this frontend does not support input requests." - ) + msg = "getpass was called, but this frontend does not support input requests." + raise StdinNotImplementedError(msg) if stream is not None: import warnings @@ -1184,9 +1182,8 @@ def raw_input(self, prompt=""): StdinNotImplementedError if active frontend doesn't support stdin. """ if not self._allow_stdin: - raise StdinNotImplementedError( - "raw_input was called, but this frontend does not support input requests." - ) + msg = "raw_input was called, but this frontend does not support input requests." + raise StdinNotImplementedError(msg) return self._input_request( str(prompt), self._parent_ident["shell"], @@ -1229,7 +1226,8 @@ def _input_request(self, prompt, ident, parent, password=False): break except KeyboardInterrupt: # re-raise KeyboardInterrupt, to truncate traceback - raise KeyboardInterrupt("Interrupted by user") from None + msg = "Interrupted by user" + raise KeyboardInterrupt(msg) from None except Exception: self.log.warning("Invalid Message:", exc_info=True) diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index e48c5b4de..e500383fe 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -66,7 +66,8 @@ def __init__(self, interrupt_handle=None, parent_handle=None): assert interrupt_handle or parent_handle super().__init__() if ctypes is None: - raise ImportError("ParentPollerWindows requires ctypes") + msg = "ParentPollerWindows requires ctypes" + raise ImportError(msg) self.daemon = True self.interrupt_handle = interrupt_handle self.parent_handle = parent_handle diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index 333eb2461..a9b327256 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -33,7 +33,8 @@ def interrupt(self, signum, frame): if self._cell_cancel_scope: self._cell_cancel_scope.cancel() else: - raise Exception("Kernel interrupted but no cell is running") + msg = "Kernel interrupted but no cell is running" + raise Exception(msg) def run(self): """Run the loop.""" diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index f36baf5f7..5a5c9e88b 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -323,7 +323,8 @@ def less(self, arg_s): Files ending in .py are syntax-highlighted.""" if not arg_s: - raise UsageError("Missing filename.") + msg = "Missing filename." + raise UsageError(msg) if arg_s.endswith(".py"): assert self.shell is not None @@ -628,7 +629,8 @@ def system_piped(self, cmd): # pexpect or pipes to read from. Users can always just call # os.system() or use ip.system=ip.system_raw # if they really want a background process. - raise OSError("Background processes not supported.") + msg = "Background processes not supported." + raise OSError(msg) # we explicitly do NOT return the subprocess status code, because # a non-None value would trigger :func:`sys.displayhook` calls. diff --git a/pyproject.toml b/pyproject.toml index 77b885ecb..33fc2bcf7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,7 +111,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==22.12.0", "mdformat>0.7", "ruff==0.0.207"] +dependencies = ["black==22.12.0", "mdformat>0.7", "ruff==0.0.215"] detached = true [tool.hatch.envs.lint.scripts] style = [ @@ -208,7 +208,7 @@ target-version = ["py37"] target-version = "py37" line-length = 100 select = [ - "A", "B", "C", "E", "F", "FBT", "I", "N", "Q", "RUF", "S", "T", + "A", "B", "C", "E", "EM", "F", "FBT", "I", "N", "Q", "RUF", "S", "T", "UP", "W", "YTT", ] ignore = [ @@ -261,7 +261,8 @@ unfixable = [ # B007 Loop control variable `i` not used within the loop body. # N802 Function name `assertIn` should be lowercase # F841 Local variable `t` is assigned to but never used -"ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841"] +# EM101 Exception must not use a string literal, assign to variable first +"ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", "EM102"] [tool.interrogate] ignore-init-module=true @@ -275,3 +276,4 @@ exclude = ["docs", "*/tests"] [tool.check-wheel-contents] toplevel = ["ipykernel/", "ipykernel_launcher.py"] +ignore = ["W002"] From a192ced7afdfb0bd843132af8870dcf2fc5b8c5b Mon Sep 17 00:00:00 2001 From: Carlos Cordoba Date: Mon, 9 Jan 2023 07:43:38 -0500 Subject: [PATCH 0962/1195] Don't raise error when trying to create another Qt app for Qt eventloop (#1071) * Don't raise error when trying to set another app for Qt eventloop - This was making the `%matplotlib qt` fail in Spyder. - It was also generating a long traceback when trying to set another interactive backend (e.g. tk). * Remove support to set a Qt4 eventloop because it's no longer supported * Move big comment outside a function to be part of a docstring * Don't raise errors in set_qt_api_env_from_gui Use prints instead because these errors are not displayed to users but contain valuable information for them. * Restore loop_qt5 function because it was public API * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add missing return's in set_qt_api_env_from_gui This avoids making that function run beyond the point where a certain message is printed. * Fix test_qt_enable_gui with the new changes Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/eventloops.py | 79 ++++++++++++++----------------- ipykernel/tests/test_eventloop.py | 30 +++++++----- 2 files changed, 52 insertions(+), 57 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 551c6efc7..c29255073 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -115,18 +115,24 @@ def process_stream_events(): kernel._qt_timer.start(0) -@register_integration("qt", "qt4", "qt5", "qt6") +@register_integration("qt", "qt5", "qt6") def loop_qt(kernel): - """Event loop for all versions of Qt.""" + """Event loop for all supported versions of Qt.""" _notify_stream_qt(kernel) # install hook to stop event loop. + # Start the event loop. kernel.app._in_event_loop = True + # `exec` blocks until there's ZMQ activity. el = kernel.app.qt_event_loop # for brevity el.exec() if hasattr(el, 'exec') else el.exec_() kernel.app._in_event_loop = False +# NOTE: To be removed in version 7 +loop_qt5 = loop_qt + + # exit and watch are the same for qt 4 and 5 @loop_qt.exit def loop_qt_exit(kernel): @@ -428,44 +434,38 @@ def close_loop(): loop.close() -# The user can generically request `qt` or a specific Qt version, e.g. `qt6`. For a generic Qt -# request, we let the mechanism in IPython choose the best available version by leaving the `QT_API` -# environment variable blank. -# -# For specific versions, we check to see whether the PyQt or PySide implementations are present and -# set `QT_API` accordingly to indicate to IPython which version we want. If neither implementation -# is present, we leave the environment variable set so IPython will generate a helpful error -# message. -# -# NOTE: if the environment variable is already set, it will be used unchanged, regardless of what -# the user requested. - - def set_qt_api_env_from_gui(gui): """ Sets the QT_API environment variable by trying to import PyQtx or PySidex. - If QT_API is already set, ignore the request. + The user can generically request `qt` or a specific Qt version, e.g. `qt6`. + For a generic Qt request, we let the mechanism in IPython choose the best + available version by leaving the `QT_API` environment variable blank. + + For specific versions, we check to see whether the PyQt or PySide + implementations are present and set `QT_API` accordingly to indicate to + IPython which version we want. If neither implementation is present, we + leave the environment variable set so IPython will generate a helpful error + message. + + Notes + ----- + - If the environment variable is already set, it will be used unchanged, + regardless of what the user requested. """ qt_api = os.environ.get("QT_API", None) from IPython.external.qt_loaders import ( - QT_API_PYQT, QT_API_PYQT5, QT_API_PYQT6, - QT_API_PYSIDE, QT_API_PYSIDE2, QT_API_PYSIDE6, - QT_API_PYQTv1, loaded_api, ) loaded = loaded_api() qt_env2gui = { - QT_API_PYSIDE: 'qt4', - QT_API_PYQTv1: 'qt4', - QT_API_PYQT: 'qt4', QT_API_PYSIDE2: 'qt5', QT_API_PYQT5: 'qt5', QT_API_PYSIDE6: 'qt6', @@ -473,8 +473,8 @@ def set_qt_api_env_from_gui(gui): } if loaded is not None and gui != 'qt': if qt_env2gui[loaded] != gui: - msg = f'Cannot switch Qt versions for this session; must use {qt_env2gui[loaded]}.' - raise ImportError(msg) + print(f'Cannot switch Qt versions for this session; you must use {qt_env2gui[loaded]}.') + return if qt_api is not None and gui != 'qt': if qt_env2gui[qt_api] != gui: @@ -482,21 +482,9 @@ def set_qt_api_env_from_gui(gui): f'Request for "{gui}" will be ignored because `QT_API` ' f'environment variable is set to "{qt_api}"' ) + return else: - if gui == 'qt4': - try: - import PyQt # noqa - - os.environ["QT_API"] = "pyqt" - except ImportError: - try: - import PySide # noqa - - os.environ["QT_API"] = "pyside" - except ImportError: - # Neither implementation installed; set it to something so IPython gives an error - os.environ["QT_API"] = "pyqt" - elif gui == 'qt5': + if gui == 'qt5': try: import PyQt5 # noqa @@ -525,26 +513,29 @@ def set_qt_api_env_from_gui(gui): if 'QT_API' in os.environ.keys(): del os.environ['QT_API'] else: - msg = f'Unrecognized Qt version: {gui}. Should be "qt4", "qt5", "qt6", or "qt".' - raise ValueError(msg) + print(f'Unrecognized Qt version: {gui}. Should be "qt5", "qt6", or "qt".') + return # Do the actual import now that the environment variable is set to make sure it works. try: from IPython.external.qt_for_kernel import QtCore, QtGui # noqa - except ImportError: + except Exception as e: # Clear the environment variable for the next attempt. if 'QT_API' in os.environ.keys(): del os.environ["QT_API"] - raise + print(f"QT_API couldn't be set due to error {e}") + return def make_qt_app_for_kernel(gui, kernel): """Sets the `QT_API` environment variable if it isn't already set.""" if hasattr(kernel, 'app'): - msg = 'Kernel already running a Qt event loop.' - raise RuntimeError(msg) + # Kernel is already running a Qt event loop, so there's no need to + # create another app for it. + return set_qt_api_env_from_gui(gui) + # This import is guaranteed to work now: from IPython.external.qt_for_kernel import QtCore, QtGui from IPython.lib.guisupport import get_app_qt4 diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index 3a684d625..c8bd95407 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -14,7 +14,6 @@ loop_asyncio, loop_cocoa, loop_tk, - set_qt_api_env_from_gui, ) from .utils import execute, flush_channels, start_new_kernel @@ -23,21 +22,21 @@ qt_guis_avail = [] +gui_to_module = {'qt6': 'PySide6', 'qt5': 'PyQt5'} + def _get_qt_vers(): """If any version of Qt is available, this will populate `guis_avail` with 'qt' and 'qtx'. Due to the import mechanism, we can't import multiple versions of Qt in one session.""" - for gui in ['qt', 'qt6', 'qt5', 'qt4']: + for gui in ['qt6', 'qt5']: print(f'Trying {gui}') try: - set_qt_api_env_from_gui(gui) + __import__(gui_to_module[gui]) qt_guis_avail.append(gui) if 'QT_API' in os.environ.keys(): del os.environ['QT_API'] except ImportError: pass # that version of Qt isn't available. - except RuntimeError: - pass # the version of IPython doesn't know what to do with this Qt version. _get_qt_vers() @@ -129,31 +128,36 @@ def test_cocoa_loop(kernel): @pytest.mark.skipif( len(qt_guis_avail) == 0, reason='No viable version of PyQt or PySide installed.' ) -def test_qt_enable_gui(kernel): +def test_qt_enable_gui(kernel, capsys): gui = qt_guis_avail[0] enable_gui(gui, kernel) # We store the `QApplication` instance in the kernel. assert hasattr(kernel, 'app') + # And the `QEventLoop` is added to `app`:` assert hasattr(kernel.app, 'qt_event_loop') - # Can't start another event loop, even if `gui` is the same. - with pytest.raises(RuntimeError): - enable_gui(gui, kernel) + # Don't create another app even if `gui` is the same. + app = kernel.app + enable_gui(gui, kernel) + assert app == kernel.app # Event loop intergration can be turned off. enable_gui(None, kernel) assert not hasattr(kernel, 'app') # But now we're stuck with this version of Qt for good; can't switch. - for not_gui in ['qt6', 'qt5', 'qt4']: + for not_gui in ['qt6', 'qt5']: if not_gui not in qt_guis_avail: break - with pytest.raises(ImportError): - enable_gui(not_gui, kernel) + enable_gui(not_gui, kernel) + captured = capsys.readouterr() + assert captured.out == f'Cannot switch Qt versions for this session; you must use {gui}.\n' - # A gui of 'qt' means "best available", or in this case, the last one that was used. + # Check 'qt' gui, which means "the best available" + enable_gui(None, kernel) enable_gui('qt', kernel) + assert gui_to_module[gui] in str(kernel.app) From 5f07abc22a1c75672f7bee129505f19c954a7c36 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 9 Jan 2023 06:43:51 -0600 Subject: [PATCH 0963/1195] Add api docs (#1067) add api docs --- .github/workflows/ci.yml | 9 +++ docs/api/ipykernel.comm.rst | 25 ++++++ docs/api/ipykernel.inprocess.rst | 55 +++++++++++++ docs/api/ipykernel.rst | 130 +++++++++++++++++++++++++++++++ docs/api/modules.rst | 7 ++ docs/conf.py | 1 + docs/index.rst | 3 +- ipykernel/comm/comm.py | 2 +- ipykernel/embed.py | 2 +- pyproject.toml | 5 +- 10 files changed, 235 insertions(+), 4 deletions(-) create mode 100644 docs/api/ipykernel.comm.rst create mode 100644 docs/api/ipykernel.inprocess.rst create mode 100644 docs/api/ipykernel.rst create mode 100644 docs/api/modules.rst diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b4e2b015..d76ad3bf3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -93,6 +93,15 @@ jobs: steps: - uses: actions/checkout@v3 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Build API docs + run: | + hatch run docs:api + # If this fails run `hatch run docs:api` locally + # and commit. + git status --porcelain + git status -s | grep "A" && exit 1 + git status -s | grep "M" && exit 1 + echo "API docs done" - run: hatch run docs:build test_without_debugpy: diff --git a/docs/api/ipykernel.comm.rst b/docs/api/ipykernel.comm.rst new file mode 100644 index 000000000..1cf9ee4e7 --- /dev/null +++ b/docs/api/ipykernel.comm.rst @@ -0,0 +1,25 @@ +ipykernel.comm package +====================== + +Submodules +---------- + + +.. automodule:: ipykernel.comm.comm + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.comm.manager + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: ipykernel.comm + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/ipykernel.inprocess.rst b/docs/api/ipykernel.inprocess.rst new file mode 100644 index 000000000..c2d6536bc --- /dev/null +++ b/docs/api/ipykernel.inprocess.rst @@ -0,0 +1,55 @@ +ipykernel.inprocess package +=========================== + +Submodules +---------- + + +.. automodule:: ipykernel.inprocess.blocking + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.inprocess.channels + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.inprocess.client + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.inprocess.constants + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.inprocess.ipkernel + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.inprocess.manager + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.inprocess.socket + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: ipykernel.inprocess + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/ipykernel.rst b/docs/api/ipykernel.rst new file mode 100644 index 000000000..2e1cf20d8 --- /dev/null +++ b/docs/api/ipykernel.rst @@ -0,0 +1,130 @@ +ipykernel package +================= + +Subpackages +----------- + +.. toctree:: + :maxdepth: 4 + + ipykernel.comm + ipykernel.inprocess + +Submodules +---------- + + +.. automodule:: ipykernel.compiler + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.connect + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.control + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.debugger + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.displayhook + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.embed + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.eventloops + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.heartbeat + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.iostream + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.ipkernel + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.jsonutil + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.kernelapp + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.kernelbase + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.kernelspec + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.log + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.parentpoller + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.trio_runner + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.zmqshell + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: ipykernel + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/api/modules.rst b/docs/api/modules.rst new file mode 100644 index 000000000..95927aa38 --- /dev/null +++ b/docs/api/modules.rst @@ -0,0 +1,7 @@ +ipykernel +========= + +.. toctree:: + :maxdepth: 4 + + ipykernel diff --git a/docs/conf.py b/docs/conf.py index b5edf98a9..990da1e21 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -34,6 +34,7 @@ "sphinx.ext.autodoc", "sphinx.ext.intersphinx", "sphinxcontrib_github_alt", + "sphinx_autodoc_typehints", ] try: diff --git a/docs/index.rst b/docs/index.rst index f58fc62fc..aed0ac0e9 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,9 +9,10 @@ Most IPython kernel documentation is in the `IPython documentation Indices and tables diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 8f3c2dff3..e236bee78 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -18,7 +18,7 @@ class BaseComm(comm.base_comm.BaseComm): """The base class for comms.""" - kernel: Optional[Kernel] = None + kernel: Optional["Kernel"] = None def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): """Helper for sending a comm message on IOPub""" diff --git a/ipykernel/embed.py b/ipykernel/embed.py index 53c11119b..de208eb2a 100644 --- a/ipykernel/embed.py +++ b/ipykernel/embed.py @@ -24,7 +24,7 @@ def embed_kernel(module=None, local_ns=None, **kwargs): The module to load into IPython globals (default: caller) local_ns : dict, optional The namespace to load into IPython user namespace (default: caller) - **kwargs : various, optional + kwargs : dict, optional Further keyword args are relayed to the IPKernelApp constructor, allowing configuration of the Kernel. Will only have an effect on the first embed_kernel call for a given process. diff --git a/pyproject.toml b/pyproject.toml index 33fc2bcf7..1730197a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,7 +45,9 @@ docs = [ "myst_parser", "pydata_sphinx_theme", "sphinxcontrib_github_alt", - "sphinxcontrib-spelling" + "sphinxcontrib-spelling", + "sphinx-autodoc-typehints", + "trio" ] test = [ "pytest>=7.0", @@ -82,6 +84,7 @@ path = "ipykernel/_version.py" features = ["docs"] [tool.hatch.envs.docs.scripts] build = "make -C docs html SPHINXOPTS='-W'" +api = "sphinx-apidoc -o docs/api -f -E ipykernel ipykernel/tests ipykernel/inprocess/tests ipykernel/datapub.py ipykernel/pickleutil.py ipykernel/serialize.py ipykernel/gui ipykernel/pylab" [tool.hatch.envs.test] features = ["test"] From b4ea598646b68d326da9e182fddaf67d0061d842 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 9 Jan 2023 12:47:56 +0000 Subject: [PATCH 0964/1195] Publish 6.20.1 SHA256 hashes: ipykernel-6.20.1-py3-none-any.whl: a314e6782a4f9e277783382976b3a93608a3787cd70a235b558b47f875134be1 ipykernel-6.20.1.tar.gz: f6016ecbf581d0ea6e29ba16cee6cc1a9bbde3835900c46c6571a791692f4139 --- CHANGELOG.md | 27 +++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a43631d24..39895401d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ +## 6.20.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.20.0...5f07abc22a1c75672f7bee129505f19c954a7c36)) + +### Bugs fixed + +- Don't raise error when trying to create another Qt app for Qt eventloop [#1071](https://github.com/ipython/ipykernel/pull/1071) ([@ccordoba12](https://github.com/ccordoba12)) + +### Maintenance and upkeep improvements + +- Update CI [#1073](https://github.com/ipython/ipykernel/pull/1073) ([@blink1073](https://github.com/blink1073)) +- Fix types and sync lint deps [#1070](https://github.com/ipython/ipykernel/pull/1070) ([@blink1073](https://github.com/blink1073)) + +### Documentation improvements + +- Add api docs [#1067](https://github.com/ipython/ipykernel/pull/1067) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2022-12-26&to=2023-01-09&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-12-26..2023-01-09&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2022-12-26..2023-01-09&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-12-26..2023-01-09&type=Issues) + + + ## 6.20.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.4...fbea757e117c1d3b0da29a40b4abcf3133a310f4)) @@ -20,8 +45,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-12-20..2022-12-26&type=Issues) | [@shaperilio](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ashaperilio+updated%3A2022-12-20..2022-12-26&type=Issues) - - ## 6.19.4 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.3...07da48e686b5906525c2a6b8cfc11cd7c3d96a5f)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index bc7a190a9..a9fed5e85 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.20.0" +__version__ = "6.20.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 203ee2bce0b506257bd561d082e983330d1ebd14 Mon Sep 17 00:00:00 2001 From: Ilya Sherstyuk <46343317+ilyasher@users.noreply.github.com> Date: Fri, 13 Jan 2023 02:17:59 -0800 Subject: [PATCH 0965/1195] Fix Exception in OutStream.close() (#1076) This bug fixes an Exception which was thrown in OutStream.close() whenever the OutStream was constructed with watchfd=False. A regression test was also added to test_io.py. Co-authored-by: Ilya Sherstyuk --- ipykernel/iostream.py | 3 ++- ipykernel/tests/test_io.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 92df61aea..32829f6bd 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -401,6 +401,7 @@ def __init__( self._buffer = StringIO() self.echo = None self._isatty = bool(isatty) + self._should_watch = False if ( watchfd @@ -449,7 +450,7 @@ def set_parent(self, parent): def close(self): """Close the stream.""" - if sys.platform.startswith("linux") or sys.platform.startswith("darwin"): + if self._should_watch: self._should_watch = False self.watch_fd_thread.join() if self._exc: diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index f7fc8b3f4..221af1f8b 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -98,6 +98,9 @@ def test_outstream(): stream = OutStream(session, pub, "stdout") stream = OutStream(session, thread, "stdout", pipe=object()) + stream = OutStream(session, thread, "stdout", watchfd=False) + stream.close() + stream = OutStream(session, thread, "stdout", isatty=True, echo=io.StringIO()) with pytest.raises(io.UnsupportedOperation): stream.fileno() From 601c0fe3ea612e6cf73982043669383f27256711 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 16 Jan 2023 12:39:28 +0000 Subject: [PATCH 0966/1195] Publish 6.20.2 SHA256 hashes: ipykernel-6.20.2-py3-none-any.whl: 5d0675d5f48bf6a95fd517d7b70bcb3b2c5631b2069949b5c2d6e1d7477fb5a0 ipykernel-6.20.2.tar.gz: 1893c5b847033cd7a58f6843b04a9349ffb1031bc6588401cadc9adb58da428e --- CHANGELOG.md | 18 ++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39895401d..a526e4514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.20.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.20.1...203ee2bce0b506257bd561d082e983330d1ebd14)) + +### Bugs fixed + +- Fix Exception in OutStream.close() [#1076](https://github.com/ipython/ipykernel/pull/1076) ([@ilyasher](https://github.com/ilyasher)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-01-09&to=2023-01-16&type=c)) + +[@ilyasher](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ailyasher+updated%3A2023-01-09..2023-01-16&type=Issues) + + + ## 6.20.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.20.0...5f07abc22a1c75672f7bee129505f19c954a7c36)) @@ -25,8 +41,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2022-12-26..2023-01-09&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2022-12-26..2023-01-09&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2022-12-26..2023-01-09&type=Issues) - - ## 6.20.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.19.4...fbea757e117c1d3b0da29a40b4abcf3133a310f4)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index a9fed5e85..29c4ad6d8 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.20.1" +__version__ = "6.20.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 5bda9c06804283e8155517a1bb85ebb4b5d2f920 Mon Sep 17 00:00:00 2001 From: Nicolas Brichet <32258950+brichet@users.noreply.github.com> Date: Wed, 18 Jan 2023 00:32:06 +0100 Subject: [PATCH 0967/1195] Add copy_to_globals debug request handling (#1055) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/debugger.py | 28 ++++++++- ipykernel/tests/test_debugger.py | 97 ++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 3 files changed, 125 insertions(+), 2 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 43ae68300..b59ad476f 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -316,7 +316,13 @@ class Debugger: ] # Requests that can be handled even if the debugger is not running - static_debug_msg_types = ["debugInfo", "inspectVariables", "richInspectVariables", "modules"] + static_debug_msg_types = [ + "debugInfo", + "inspectVariables", + "richInspectVariables", + "modules", + "copyToGlobals", + ] def __init__( self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code=True @@ -662,6 +668,26 @@ async def richInspectVariables(self, message): reply["success"] = True return reply + async def copyToGlobals(self, message): + dst_var_name = message["arguments"]["dstVariableName"] + src_var_name = message["arguments"]["srcVariableName"] + src_frame_id = message["arguments"]["srcFrameId"] + + expression = f"globals()['{dst_var_name}']" + seq = message["seq"] + return await self._forward_message( + { + "type": "request", + "command": "setExpression", + "seq": seq + 1, + "arguments": { + "expression": expression, + "value": src_var_name, + "frameId": src_frame_id, + }, + } + ) + async def modules(self, message): """Handle a modules message.""" modules = list(sys.modules.values()) diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index 200154cfc..c9071296c 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -282,3 +282,100 @@ def test_convert_to_long_pathname(): from ipykernel.compiler import _convert_to_long_pathname _convert_to_long_pathname(__file__) + + +def test_copy_to_globals(kernel_with_debug): + local_var_name = "var" + global_var_name = "var_copy" + code = f"""from IPython.core.display import HTML +def my_test(): + {local_var_name} = HTML('

test content

') + pass +a = 2 +my_test()""" + + # Init debugger and set breakpoint + r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) + source = r["body"]["sourcePath"] + + wait_for_debug_request( + kernel_with_debug, + "setBreakpoints", + { + "breakpoints": [{"line": 4}], + "source": {"path": source}, + "sourceModified": False, + }, + ) + + wait_for_debug_request(kernel_with_debug, "debugInfo") + + wait_for_debug_request(kernel_with_debug, "configurationDone") + + # Execute code + kernel_with_debug.execute(code) + + # Wait for stop on breakpoint + msg: dict = {"msg_type": "", "content": {}} + while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": + msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) + + stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})["body"][ + "stackFrames" + ] + + # Get local frame id + frame_id = stacks[0]["id"] + + # Copy the variable + wait_for_debug_request( + kernel_with_debug, + "copyToGlobals", + { + "srcVariableName": local_var_name, + "dstVariableName": global_var_name, + "srcFrameId": frame_id, + }, + ) + + # Get the scopes + scopes = wait_for_debug_request(kernel_with_debug, "scopes", {"frameId": frame_id})["body"][ + "scopes" + ] + + # Get the local variable + locals_ = wait_for_debug_request( + kernel_with_debug, + "variables", + { + "variablesReference": next(filter(lambda s: s["name"] == "Locals", scopes))[ + "variablesReference" + ] + }, + )["body"]["variables"] + + local_var = None + for variable in locals_: + if local_var_name in variable["evaluateName"]: + local_var = variable + assert local_var is not None + + # Get the global variable (copy of the local variable) + globals_ = wait_for_debug_request( + kernel_with_debug, + "variables", + { + "variablesReference": next(filter(lambda s: s["name"] == "Globals", scopes))[ + "variablesReference" + ] + }, + )["body"]["variables"] + + global_var = None + for variable in globals_: + if global_var_name in variable["evaluateName"]: + global_var = variable + assert global_var is not None + + # Compare local and global variable + assert global_var["value"] == local_var["value"] and global_var["type"] == local_var["type"] diff --git a/pyproject.toml b/pyproject.toml index 1730197a0..ef9587f89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ classifiers = [ urls = {Homepage = "https://ipython.org"} requires-python = ">=3.8" dependencies = [ - "debugpy>=1.0", + "debugpy>=1.6.5", "ipython>=7.23.1", "comm>=0.1.1", "traitlets>=5.4.0", From 1185d12121a6b0dd93e84f9a5b21a8aa733eb2b2 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Tue, 24 Jan 2023 14:57:18 +0100 Subject: [PATCH 0968/1195] Expose session start file in __file__. (#1078) --- ipykernel/ipkernel.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index a4b975a4b..6acc48725 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -3,6 +3,7 @@ import asyncio import builtins import getpass +import os import signal import sys import threading @@ -126,6 +127,11 @@ def __init__(self, **kwargs): compiler_class=XCachingCompiler, ) self.shell.displayhook.session = self.session + + jupyter_session_name = os.environ.get('JPY_SESSION_NAME') + if jupyter_session_name: + self.shell.user_ns['__file__'] = jupyter_session_name + self.shell.displayhook.pub_socket = self.iopub_socket self.shell.displayhook.topic = self._topic("execute_result") self.shell.display_pub.session = self.session From 4ce612aff28b5fe2c2b0c3a0c4ee6ee78207ad06 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 27 Jan 2023 10:21:38 -0600 Subject: [PATCH 0969/1195] Test spyder kernels (#1080) * test-spyder-kernels * cleanup --- .github/workflows/ci.yml | 2 ++ .github/workflows/downstream.yml | 37 ++++++++++++++++++++++++++++---- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d76ad3bf3..c730f117d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,6 +4,8 @@ on: push: branches: ["main"] pull_request: + schedule: + - cron: "0 0 * * *" concurrency: group: ci-${{ github.ref }} diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 57c856155..a37242e68 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -90,13 +90,11 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - - name: Setup Python uses: actions/setup-python@v4 with: python-version: "3.9" architecture: "x64" - - name: Install System Packages run: | sudo apt-get update @@ -109,16 +107,46 @@ jobs: cd qtconsole ${pythonLocation}/bin/python -m pip install -e ".[test]" ${pythonLocation}/bin/python -m pip install pyqt5 - - name: Install Jupyter-Client changes + - name: Install Ipykernel changes shell: bash -l {0} run: ${pythonLocation}/bin/python -m pip install -e . - - name: Test qtconsole shell: bash -l {0} run: | cd ${GITHUB_WORKSPACE}/../qtconsole xvfb-run --auto-servernum ${pythonLocation}/bin/python -m pytest -x -vv -s --full-trace --color=yes qtconsole + spyder_kernels: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Setup Python + uses: actions/setup-python@v4 + with: + python-version: "3.9" + architecture: "x64" + - name: Install System Packages + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends libegl1-mesa + - name: Install spyder-kernels dependencies + shell: bash -l {0} + run: | + cd ${GITHUB_WORKSPACE}/.. + git clone https://github.com/spyder-ide/spyder-kernels.git + cd spyder-kernels + ${pythonLocation}/bin/python -m pip install -e ".[test]" + - name: Install IPykernel changes + shell: bash -l {0} + run: ${pythonLocation}/bin/python -m pip install -e . + - name: Test spyder-kernels + shell: bash -l {0} + run: | + cd ${GITHUB_WORKSPACE}/../spyder-kernels + xvfb-run --auto-servernum ${pythonLocation}/bin/python -m pytest -x -vv -s --full-trace --color=yes spyder_kernels + downstream_check: # This job does nothing and is only used for the branch protection if: always() needs: @@ -127,6 +155,7 @@ jobs: - jupyter_client - ipyparallel - jupyter_kernel_test + - spyder_kernels - qtconsole runs-on: ubuntu-latest steps: From 1c64fef8c61c5a8137c4dbd7e70b9dbf61c985c3 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 30 Jan 2023 08:14:51 -0600 Subject: [PATCH 0970/1195] Maintenance updates (#1081) --- ipykernel/eventloops.py | 10 ++-------- ipykernel/inprocess/client.py | 6 +----- ipykernel/kernelapp.py | 6 ++++-- pyproject.toml | 2 +- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index c29255073..d7e2a04ee 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -9,6 +9,7 @@ from functools import partial import zmq +from jupyter_core.utils import run_sync from packaging.version import Version as V # noqa from traitlets.config.application import Application @@ -250,12 +251,6 @@ def process_stream_events(stream, *a, **kw): app.mainloop() else: - import asyncio - - import nest_asyncio - - nest_asyncio.apply() - doi = kernel.do_one_iteration # Tk uses milliseconds poll_interval = int(1000 * kernel._poll_interval) @@ -267,9 +262,8 @@ def __init__(self, app, func): self.func = func def on_timer(self): - loop = asyncio.get_event_loop() try: - loop.run_until_complete(self.func()) + run_sync(self.func)() except Exception: kernel.log.exception("Error in message handler") self.app.after(poll_interval, self.on_timer) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 2d9397271..81bf2be5e 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -15,11 +15,7 @@ from jupyter_client.client import KernelClient from jupyter_client.clientabc import KernelClientABC - -try: - from jupyter_client.utils import run_sync # requires 7.0+ -except ImportError: - run_sync = None # type:ignore +from jupyter_core.utils import run_sync # IPython imports from traitlets import Instance, Type, default diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index a98439cfc..af580e496 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -13,6 +13,7 @@ from functools import partial from io import FileIO, TextIOWrapper from logging import StreamHandler +from typing import Optional import zmq from IPython.core.application import ( # type:ignore[attr-defined] @@ -131,7 +132,7 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMix poller = Any() # don't restrict this even though current pollers are all Threads heartbeat = Instance(Heartbeat, allow_none=True) - context = Any() + context: Optional[zmq.Context] = Any() # type:ignore[assignment] shell_socket = Any() control_socket = Any() debugpy_socket = Any() @@ -403,7 +404,8 @@ def close(self): if socket and not socket.closed: socket.close() self.log.debug("Terminating zmq context") - self.context.term() + if self.context: + self.context.term() self.log.debug("Terminated zmq context") def log_connection_info(self): diff --git a/pyproject.toml b/pyproject.toml index ef9587f89..64b858ff0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,12 +30,12 @@ dependencies = [ "comm>=0.1.1", "traitlets>=5.4.0", "jupyter_client>=6.1.12", + "jupyter_core>=4.12,!=5.0.*", "tornado>=6.1", "matplotlib-inline>=0.1", 'appnope;platform_system=="Darwin"', "pyzmq>=17", "psutil", - "nest_asyncio", "packaging", ] From dde698850d865dec89bba2305d1f3dc3134f8413 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 30 Jan 2023 08:32:01 -0600 Subject: [PATCH 0971/1195] Adopt more lint rules (#1082) --- .pre-commit-config.yaml | 2 +- ipykernel/compiler.py | 2 +- ipykernel/connect.py | 2 +- ipykernel/eventloops.py | 11 ++++----- ipykernel/inprocess/client.py | 5 +--- ipykernel/iostream.py | 2 +- ipykernel/ipkernel.py | 5 +--- ipykernel/jsonutil.py | 6 ++--- ipykernel/kernelapp.py | 6 +++-- ipykernel/kernelbase.py | 17 +++++--------- ipykernel/pickleutil.py | 20 ++++------------ ipykernel/serialize.py | 2 +- ipykernel/tests/test_eventloop.py | 2 +- ipykernel/tests/test_jsonutil.py | 2 +- ipykernel/tests/test_parentpoller.py | 2 +- pyproject.toml | 34 ++++++++++++++++++++++++---- 16 files changed, 62 insertions(+), 58 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2b903131f..e010c8b8a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.215 + rev: v0.0.236 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index fe5561594..7ba79472f 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -29,7 +29,7 @@ def murmur2_x86(data, seed): val = length & 0x03 k = 0 - if val == 3: + if val == 3: # noqa k = (ord(data[rounded_end + 2]) & 0xFF) << 16 if val in [2, 3]: k |= (ord(data[rounded_end + 1]) & 0xFF) << 8 diff --git a/ipykernel/connect.py b/ipykernel/connect.py index abc1a8211..b933179c5 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -117,7 +117,7 @@ def connect_qtconsole(connection_file=None, argv=None): kwargs["start_new_session"] = True return Popen( - [sys.executable, "-c", cmd, "--existing", cf] + argv, + [sys.executable, "-c", cmd, "--existing", cf, *argv], stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != "win32"), diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index d7e2a04ee..b5c65dbc1 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -465,10 +465,9 @@ def set_qt_api_env_from_gui(gui): QT_API_PYSIDE6: 'qt6', QT_API_PYQT6: 'qt6', } - if loaded is not None and gui != 'qt': - if qt_env2gui[loaded] != gui: - print(f'Cannot switch Qt versions for this session; you must use {qt_env2gui[loaded]}.') - return + if loaded is not None and gui != 'qt' and qt_env2gui[loaded] != gui: + print(f'Cannot switch Qt versions for this session; you must use {qt_env2gui[loaded]}.') + return if qt_api is not None and gui != 'qt': if qt_env2gui[qt_api] != gui: @@ -504,7 +503,7 @@ def set_qt_api_env_from_gui(gui): os.environ["QT_API"] = "pyqt6" elif gui == 'qt': # Don't set QT_API; let IPython logic choose the version. - if 'QT_API' in os.environ.keys(): + if 'QT_API' in os.environ: del os.environ['QT_API'] else: print(f'Unrecognized Qt version: {gui}. Should be "qt5", "qt6", or "qt".') @@ -515,7 +514,7 @@ def set_qt_api_env_from_gui(gui): from IPython.external.qt_for_kernel import QtCore, QtGui # noqa except Exception as e: # Clear the environment variable for the next attempt. - if 'QT_API' in os.environ.keys(): + if 'QT_API' in os.environ: del os.environ["QT_API"] print(f"QT_API couldn't be set due to error {e}") return diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 81bf2be5e..aaf2db8f5 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -161,10 +161,7 @@ def kernel_info(self): def comm_info(self, target_name=None): """Request a dictionary of valid comms and their targets.""" - if target_name is None: - content = {} - else: - content = dict(target_name=target_name) + content = {} if target_name is None else dict(target_name=target_name) msg = self.session.msg("comm_info_request", content) self._dispatch_to_kernel(msg) return msg["header"]["msg_id"] diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 32829f6bd..ba5c3f390 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -234,7 +234,7 @@ def _really_send(self, msg, *args, **kwargs): # new context/socket for every pipe-out # since forks don't teardown politely, use ctx.term to ensure send has completed ctx, pipe_out = self._setup_pipe_out() - pipe_out.send_multipart([self._pipe_uuid] + msg, *args, **kwargs) + pipe_out.send_multipart([self._pipe_uuid, *msg], *args, **kwargs) pipe_out.close() ctx.term() diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 6acc48725..bacb0ba78 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -431,10 +431,7 @@ async def run_cell(*args, **kwargs): finally: self._restore_input() - if res.error_before_exec is not None: - err = res.error_before_exec - else: - err = res.error_in_exec + err = res.error_before_exec if res.error_before_exec is not None else res.error_in_exec if res.success: reply_content["status"] = "ok" diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 9d6da754f..73c95ca02 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -26,7 +26,7 @@ # holy crap, strptime is not threadsafe. # Calling it once at import seems to help. -datetime.strptime("1", "%d") +datetime.strptime("1", "%d") # noqa # ----------------------------------------------------------------------------- # Classes and functions @@ -98,7 +98,7 @@ def json_clean(obj): # pragma: no cover it simply sanitizes it so that there will be no encoding errors later. """ - if int(JUPYTER_CLIENT_MAJOR_VERSION) >= 7: + if int(JUPYTER_CLIENT_MAJOR_VERSION) >= 7: # noqa return obj # types that are 'atomic' and ok in json as-is. @@ -156,7 +156,7 @@ def json_clean(obj): # pragma: no cover for k, v in obj.items(): out[str(k)] = json_clean(v) return out - if isinstance(obj, datetime) or isinstance(obj, date): + if isinstance(obj, (datetime, date)): return obj.strftime(ISO8601) # we don't understand it, it's probably an unserializable object diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index af580e496..8d7eea8c8 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -447,7 +447,7 @@ def log_connection_info(self): def init_blackhole(self): """redirects stdout/stderr to devnull if necessary""" if self.no_stdout or self.no_stderr: - blackhole = open(os.devnull, "w") + blackhole = open(os.devnull, "w") # noqa if self.no_stdout: sys.stdout = sys.__stdout__ = blackhole if self.no_stderr: @@ -473,7 +473,9 @@ def init_io(self): if hasattr(sys.stderr, "_original_stdstream_copy"): for handler in self.log.handlers: - if isinstance(handler, StreamHandler) and (handler.stream.buffer.fileno() == 2): + if isinstance(handler, StreamHandler) and ( + handler.stream.buffer.fileno() == 2 # noqa + ): self.log.debug("Seeing logger to stderr, rerouting to raw filedescriptor.") handler.stream = TextIOWrapper( diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 110f834c1..a4ece0994 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -252,7 +252,8 @@ def _parent_header(self): "apply_request", ] # add deprecated ipyparallel control messages - control_msg_types = msg_types + [ + control_msg_types = [ + *msg_types, "clear_request", "abort_request", "debug_request", @@ -544,10 +545,7 @@ def start(self): self.control_stream.on_recv(self.dispatch_control, copy=False) - if self.control_thread: - control_loop = self.control_thread.io_loop - else: - control_loop = self.io_loop + control_loop = self.control_thread.io_loop if self.control_thread else self.io_loop asyncio.run_coroutine_threadsafe(self.poll_control_queue(), control_loop.asyncio_loop) @@ -844,10 +842,7 @@ def do_history( async def connect_request(self, stream, ident, parent): """Handle a connect request.""" - if self._recorded_ports is not None: - content = self._recorded_ports.copy() - else: - content = {} + content = self._recorded_ports.copy() if self._recorded_ports is not None else {} content["status"] = "ok" msg = self.session.send(stream, "connect_reply", content, parent, ident) self.log.debug("%s", msg) @@ -982,7 +977,7 @@ async def usage_request(self, stream, ident, parent): """Handle a usage request.""" reply_content = {"hostname": socket.gethostname(), "pid": os.getpid()} current_process = psutil.Process() - all_processes = [current_process] + current_process.children(recursive=True) + all_processes = [current_process, *current_process.children(recursive=True)] # Ensure 1) self.processes is updated to only current subprocesses # and 2) we reuse processes when possible (needed for accurate CPU) self.processes = { @@ -1004,7 +999,7 @@ async def usage_request(self, stream, ident, parent): cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. - if cpu_percent is not None and cpu_percent != 0.0: + if cpu_percent is not None and cpu_percent != 0.0: # noqa reply_content["host_cpu_percent"] = cpu_percent reply_content["cpu_count"] = psutil.cpu_count(logical=True) reply_content["host_virtual_memory"] = dict(psutil.virtual_memory()._asdict()) diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 8e2dbe5f3..7a7e07ef6 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -236,14 +236,8 @@ def get_object(self, g=None): if g is None: g = {} - if self.defaults: - defaults = tuple(uncan(cfd, g) for cfd in self.defaults) - else: - defaults = None - if self.closure: - closure = tuple(uncan(cell, g) for cell in self.closure) - else: - closure = None + defaults = tuple(uncan(cfd, g) for cfd in self.defaults) if self.defaults else None + closure = tuple(uncan(cell, g) for cell in self.closure) if self.closure else None newFunc = FunctionType(self.code, g, self.__name__, defaults, closure) return newFunc @@ -260,10 +254,7 @@ def __init__(self, cls): for k, v in cls.__dict__.items(): if k not in ("__weakref__", "__dict__"): self._canned_dict[k] = can(v) - if self.old_style: - mro = [] - else: - mro = cls.mro() + mro = [] if self.old_style else cls.mro() self.parents = [can(c) for c in mro[1:]] self.buffers = [] @@ -376,10 +367,7 @@ def istype(obj, check): This won't catch subclasses. """ if isinstance(check, tuple): - for cls in check: - if type(obj) is cls: - return True - return False + return any(type(obj) is cls for cls in check) else: return type(obj) is check diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 616410c81..50ffe8e85 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -181,7 +181,7 @@ def unpack_apply_message(bufs, g=None, copy=True): """unpack f,args,kwargs from buffers packed by pack_apply_message() Returns: original f,args,kwargs""" bufs = list(bufs) # allow us to pop - assert len(bufs) >= 2, "not enough buffers!" + assert len(bufs) >= 2, "not enough buffers!" # noqa pf = bufs.pop(0) f = uncan(pickle.loads(pf), g) pinfo = bufs.pop(0) diff --git a/ipykernel/tests/test_eventloop.py b/ipykernel/tests/test_eventloop.py index c8bd95407..a4d18d114 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/ipykernel/tests/test_eventloop.py @@ -33,7 +33,7 @@ def _get_qt_vers(): try: __import__(gui_to_module[gui]) qt_guis_avail.append(gui) - if 'QT_API' in os.environ.keys(): + if 'QT_API' in os.environ: del os.environ['QT_API'] except ImportError: pass # that version of Qt isn't available. diff --git a/ipykernel/tests/test_jsonutil.py b/ipykernel/tests/test_jsonutil.py index 0189bc83b..faace16a7 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/ipykernel/tests/test_jsonutil.py @@ -53,7 +53,7 @@ def test(): # More exotic objects ((x for x in range(3)), [0, 1, 2]), (iter([1, 2]), [1, 2]), - (datetime(1991, 7, 3, 12, 00), "1991-07-03T12:00:00.000000"), + (datetime(1991, 7, 3, 12, 00), "1991-07-03T12:00:00.000000"), # noqa (date(1991, 7, 3), "1991-07-03T00:00:00.000000"), (MyFloat(), 3.14), (MyInt(), 389), diff --git a/ipykernel/tests/test_parentpoller.py b/ipykernel/tests/test_parentpoller.py index a83cc392a..22df132e7 100644 --- a/ipykernel/tests/test_parentpoller.py +++ b/ipykernel/tests/test_parentpoller.py @@ -33,7 +33,7 @@ def test_parent_poller_windows(): def mock_wait(*args, **kwargs): return -1 - with mock.patch("ctypes.windll.kernel32.WaitForMultipleObjects", mock_wait): + with mock.patch("ctypes.windll.kernel32.WaitForMultipleObjects", mock_wait): # noqa with warnings.catch_warnings(): warnings.simplefilter("ignore") poller.run() diff --git a/pyproject.toml b/pyproject.toml index 64b858ff0..4e0ae62c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,7 +114,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==22.12.0", "mdformat>0.7", "ruff==0.0.215"] +dependencies = ["black==22.12.0", "mdformat>0.7", "ruff==0.0.236"] detached = true [tool.hatch.envs.lint.scripts] style = [ @@ -211,8 +211,31 @@ target-version = ["py37"] target-version = "py37" line-length = 100 select = [ - "A", "B", "C", "E", "EM", "F", "FBT", "I", "N", "Q", "RUF", "S", "T", - "UP", "W", "YTT", + "A", + "B", + "C", + "DTZ", + "E", + "EM", + "F", + "FBT", + "I", + "ICN", + "ISC", + "N", + "PLC", + "PLE", + "PLR", + "PLW", + "Q", + "RUF", + "S", + "SIM", + "T", + "TID", + "UP", + "W", + "YTT", ] ignore = [ # Allow non-abstract empty methods in abstract base classes @@ -247,6 +270,8 @@ ignore = [ "C408", # N801 Class name `directional_link` should use CapWords convention "N801", + # SIM105 Use `contextlib.suppress(ValueError)` instead of try-except-pass + "SIM105", ] unfixable = [ # Don't touch print statements @@ -265,7 +290,8 @@ unfixable = [ # N802 Function name `assertIn` should be lowercase # F841 Local variable `t` is assigned to but never used # EM101 Exception must not use a string literal, assign to variable first -"ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", "EM102"] +# PLR2004 Magic value used in comparison +"ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", "EM102", "EM103", "PLR2004"] [tool.interrogate] ignore-init-module=true From 2f9eb16e3c5898c70b5647eba2061886273bee02 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 30 Jan 2023 14:36:48 +0000 Subject: [PATCH 0972/1195] Publish 6.21.0 SHA256 hashes: ipykernel-6.21.0-py3-none-any.whl: a9aaefb0cd6f44e3dcaeaafb9222862ae73831f71afd77f465fc83d6199d1888 ipykernel-6.21.0.tar.gz: 719eac0f1d86706589ee0910d6f785fd4c1ef123ab8e3466b8961c37991d0ef2 --- CHANGELOG.md | 25 +++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a526e4514..9bd93a2df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ +## 6.21.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.20.2...dde698850d865dec89bba2305d1f3dc3134f8413)) + +### Enhancements made + +- Expose session start file in __file__. [#1078](https://github.com/ipython/ipykernel/pull/1078) ([@Carreau](https://github.com/Carreau)) +- Add copy_to_globals debug request handling [#1055](https://github.com/ipython/ipykernel/pull/1055) ([@brichet](https://github.com/brichet)) + +### Maintenance and upkeep improvements + +- Adopt more lint rules [#1082](https://github.com/ipython/ipykernel/pull/1082) ([@blink1073](https://github.com/blink1073)) +- Maintenance updates [#1081](https://github.com/ipython/ipykernel/pull/1081) ([@blink1073](https://github.com/blink1073)) +- Test spyder kernels [#1080](https://github.com/ipython/ipykernel/pull/1080) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-01-16&to=2023-01-30&type=c)) + +[@agronholm](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aagronholm+updated%3A2023-01-16..2023-01-30&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-01-16..2023-01-30&type=Issues) | [@brichet](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abrichet+updated%3A2023-01-16..2023-01-30&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2023-01-16..2023-01-30&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2023-01-16..2023-01-30&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2023-01-16..2023-01-30&type=Issues) + + + ## 6.20.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.20.1...203ee2bce0b506257bd561d082e983330d1ebd14)) @@ -16,8 +39,6 @@ [@ilyasher](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ailyasher+updated%3A2023-01-09..2023-01-16&type=Issues) - - ## 6.20.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.20.0...5f07abc22a1c75672f7bee129505f19c954a7c36)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 29c4ad6d8..7c2b7ccad 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.20.2" +__version__ = "6.21.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From ac7776dfd68861ae005e1f142ec87cd6703847ea Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 2 Feb 2023 10:29:52 -0600 Subject: [PATCH 0973/1195] Restore nest-asyncio for tk loop (#1086) restore nest-asyncio for tk loop --- ipykernel/eventloops.py | 10 ++++++++-- pyproject.toml | 2 ++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index b5c65dbc1..412a246fe 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -9,7 +9,6 @@ from functools import partial import zmq -from jupyter_core.utils import run_sync from packaging.version import Version as V # noqa from traitlets.config.application import Application @@ -251,6 +250,12 @@ def process_stream_events(stream, *a, **kw): app.mainloop() else: + import asyncio + + import nest_asyncio + + nest_asyncio.apply() + doi = kernel.do_one_iteration # Tk uses milliseconds poll_interval = int(1000 * kernel._poll_interval) @@ -262,8 +267,9 @@ def __init__(self, app, func): self.func = func def on_timer(self): + loop = asyncio.get_event_loop() try: - run_sync(self.func)() + loop.run_until_complete(self.func()) except Exception: kernel.log.exception("Error in message handler") self.app.after(poll_interval, self.on_timer) diff --git a/pyproject.toml b/pyproject.toml index 4e0ae62c2..e72440360 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,8 @@ dependencies = [ "traitlets>=5.4.0", "jupyter_client>=6.1.12", "jupyter_core>=4.12,!=5.0.*", + # For tk event loop support only. + "nest_asyncio", "tornado>=6.1", "matplotlib-inline>=0.1", 'appnope;platform_system=="Darwin"', From 4d1a2d8f93ae48942c1fa6a0d8c11a00a628dec5 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Thu, 2 Feb 2023 16:51:23 +0000 Subject: [PATCH 0974/1195] Publish 6.21.1 SHA256 hashes: ipykernel-6.21.1-py3-none-any.whl: 1a04bb359212e23e46adc0116ec82ea128c1e5bd532fde4fbe679787ff36f0cf ipykernel-6.21.1.tar.gz: a0f8eece39cab1ee352c9b59ec67bbe44d8299f8238e4c16ff7f4cf0052d3378 --- CHANGELOG.md | 18 ++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bd93a2df..dabd0ce44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.21.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.0...ac7776dfd68861ae005e1f142ec87cd6703847ea)) + +### Maintenance and upkeep improvements + +- Restore nest-asyncio for tk loop [#1086](https://github.com/ipython/ipykernel/pull/1086) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-01-30&to=2023-02-02&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-01-30..2023-02-02&type=Issues) + + + ## 6.21.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.20.2...dde698850d865dec89bba2305d1f3dc3134f8413)) @@ -23,8 +39,6 @@ [@agronholm](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aagronholm+updated%3A2023-01-16..2023-01-30&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-01-16..2023-01-30&type=Issues) | [@brichet](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abrichet+updated%3A2023-01-16..2023-01-30&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2023-01-16..2023-01-30&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2023-01-16..2023-01-30&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2023-01-16..2023-01-30&type=Issues) - - ## 6.20.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.20.1...203ee2bce0b506257bd561d082e983330d1ebd14)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7c2b7ccad..ae633f60d 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.21.0" +__version__ = "6.21.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 1d674505d571fbe608b1d89fbf6fd33bde21685d Mon Sep 17 00:00:00 2001 From: David Brochart Date: Fri, 3 Feb 2023 10:57:13 +0100 Subject: [PATCH 0975/1195] Remove test_enter_eventloop (#1084) * Test enter event-loop * Remove test_enter_eventloop --- ipykernel/tests/test_kernel_direct.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/ipykernel/tests/test_kernel_direct.py b/ipykernel/tests/test_kernel_direct.py index b0a09a860..f75d8b6d7 100644 --- a/ipykernel/tests/test_kernel_direct.py +++ b/ipykernel/tests/test_kernel_direct.py @@ -132,27 +132,6 @@ def __init__(self, bytes): await kernel.dispatch_shell(msg) -async def test_enter_eventloop(kernel): - kernel.eventloop = None - kernel.enter_eventloop() - kernel.eventloop = asyncio.get_running_loop() - kernel.enter_eventloop() - called = 0 - - def check_status(): - nonlocal called - if called == 0: - msg = kernel.session.msg("debug_request", {}) - kernel.msg_queue.put(msg) - called += 1 - kernel.io_loop.call_later(0.001, check_status) - - kernel.io_loop.call_later(0.001, check_status) - kernel.start() - while called < 2: - await asyncio.sleep(0.1) - - async def test_do_one_iteration(kernel): kernel.msg_queue = asyncio.Queue() await kernel.do_one_iteration() From 0f09aa690d1881ab6a2b92119215d8561d344e67 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 8 Feb 2023 11:27:40 -0600 Subject: [PATCH 0976/1195] [pre-commit.ci] pre-commit autoupdate (#1089)Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/python-jsonschema/check-jsonschema: 0.19.2 → 0.21.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.19.2...0.21.0) - [github.com/psf/black: 22.12.0 → 23.1.0](https://github.com/psf/black/compare/22.12.0...23.1.0) - [github.com/charliermarsh/ruff-pre-commit: v0.0.236 → v0.0.243](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.236...v0.0.243) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * sync deps and lint * lint * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * lint * bump pyzmq dep * lint --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 6 +++--- ipykernel/compiler.py | 2 +- ipykernel/inprocess/tests/test_kernel.py | 1 - ipykernel/iostream.py | 1 - ipykernel/ipkernel.py | 5 ++--- ipykernel/jsonutil.py | 2 +- ipykernel/kernelapp.py | 5 +---- ipykernel/kernelbase.py | 2 +- ipykernel/serialize.py | 2 +- ipykernel/tests/test_kernel.py | 4 ---- ipykernel/tests/test_kernelspec.py | 1 - pyproject.toml | 7 ++++--- 12 files changed, 14 insertions(+), 24 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e010c8b8a..efaf832f7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.19.2 + rev: 0.21.0 hooks: - id: check-github-workflows @@ -31,12 +31,12 @@ repos: - id: mdformat - repo: https://github.com/psf/black - rev: 22.12.0 + rev: 23.1.0 hooks: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.236 + rev: v0.0.243 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 7ba79472f..fe5561594 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -29,7 +29,7 @@ def murmur2_x86(data, seed): val = length & 0x03 k = 0 - if val == 3: # noqa + if val == 3: k = (ord(data[rounded_end + 2]) & 0xFF) << 16 if val in [2, 3]: k |= (ord(data[rounded_end + 1]) & 0xFF) << 8 diff --git a/ipykernel/inprocess/tests/test_kernel.py b/ipykernel/inprocess/tests/test_kernel.py index defe039df..b85c4bdfe 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/ipykernel/inprocess/tests/test_kernel.py @@ -48,7 +48,6 @@ def kc(): def test_with_cell_id(kc): - with patch_cell_id(): kc.execute("1+1") diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index ba5c3f390..a37aaa6e6 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -557,7 +557,6 @@ def write(self, string: str) -> Optional[int]: # type:ignore[override] msg = "I/O operation on closed file" raise ValueError(msg) else: - is_child = not self._is_master_process() # only touch the buffer in the IO thread to avoid races with self._buffer_lock: diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index bacb0ba78..569e5a881 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -29,7 +29,7 @@ try: from IPython.core.interactiveshell import _asyncio_runner # type:ignore[attr-defined] except ImportError: - _asyncio_runner = None + _asyncio_runner = None # type:ignore try: from IPython.core.completer import provisionalcompleter as _provisionalcompleter @@ -366,7 +366,6 @@ async def run_cell(*args, **kwargs): with_cell_id = _accepts_cell_id(shell.run_cell) try: - # default case: runner is asyncio and asyncio is already running # TODO: this should check every case for "are we inside the runner", # not just asyncio @@ -626,7 +625,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): try: from ipyparallel.serialize import serialize_object, unpack_apply_message except ImportError: - from .serialize import serialize_object, unpack_apply_message # type:ignore + from .serialize import serialize_object, unpack_apply_message shell = self.shell try: diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 73c95ca02..629dfc045 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -98,7 +98,7 @@ def json_clean(obj): # pragma: no cover it simply sanitizes it so that there will be no encoding errors later. """ - if int(JUPYTER_CLIENT_MAJOR_VERSION) >= 7: # noqa + if int(JUPYTER_CLIENT_MAJOR_VERSION) >= 7: return obj # types that are 'atomic' and ok in json as-is. diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 8d7eea8c8..f0ef22361 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -471,11 +471,8 @@ def init_io(self): sys.stderr.flush() sys.stderr = outstream_factory(self.session, self.iopub_thread, "stderr", echo=e_stderr) if hasattr(sys.stderr, "_original_stdstream_copy"): - for handler in self.log.handlers: - if isinstance(handler, StreamHandler) and ( - handler.stream.buffer.fileno() == 2 # noqa - ): + if isinstance(handler, StreamHandler) and (handler.stream.buffer.fileno() == 2): self.log.debug("Seeing logger to stderr, rerouting to raw filedescriptor.") handler.stream = TextIOWrapper( diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index a4ece0994..4727e8bd7 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -999,7 +999,7 @@ async def usage_request(self, stream, ident, parent): cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. - if cpu_percent is not None and cpu_percent != 0.0: # noqa + if cpu_percent is not None and cpu_percent != 0.0: reply_content["host_cpu_percent"] = cpu_percent reply_content["cpu_count"] = psutil.cpu_count(logical=True) reply_content["host_virtual_memory"] = dict(psutil.virtual_memory()._asdict()) diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 50ffe8e85..616410c81 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -181,7 +181,7 @@ def unpack_apply_message(bufs, g=None, copy=True): """unpack f,args,kwargs from buffers packed by pack_apply_message() Returns: original f,args,kwargs""" bufs = list(bufs) # allow us to pop - assert len(bufs) >= 2, "not enough buffers!" # noqa + assert len(bufs) >= 2, "not enough buffers!" pf = bufs.pop(0) f = uncan(pickle.loads(pf), g) pinfo = bufs.pop(0) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index b48d51967..033ee481d 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -117,7 +117,6 @@ def test_sys_path_profile_dir(): def test_subprocess_print(): """printing from forked mp.Process""" with new_kernel() as kc: - _check_master(kc, expected=True) flush_channels(kc) np = 5 @@ -146,7 +145,6 @@ def test_subprocess_print(): def test_subprocess_noprint(): """mp.Process without print doesn't trigger iostream mp_mode""" with kernel() as kc: - np = 5 code = "\n".join( [ @@ -174,7 +172,6 @@ def test_subprocess_noprint(): def test_subprocess_error(): """error in mp.Process doesn't crash""" with new_kernel() as kc: - code = "\n".join( [ "import multiprocessing as mp", @@ -459,7 +456,6 @@ def test_interrupt_during_pdb_set_trace(): def test_control_thread_priority(): - N = 5 with new_kernel() as kc: msg_id = kc.execute("pass") diff --git a/ipykernel/tests/test_kernelspec.py b/ipykernel/tests/test_kernelspec.py index 24771bc1d..e92109648 100644 --- a/ipykernel/tests/test_kernelspec.py +++ b/ipykernel/tests/test_kernelspec.py @@ -76,7 +76,6 @@ def test_write_kernel_spec_path(): def test_install_kernelspec(): - path = tempfile.mkdtemp() try: InstallIPythonKernelSpecApp.launch_instance(argv=["--prefix", path]) diff --git a/pyproject.toml b/pyproject.toml index e72440360..0b3807ac9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "tornado>=6.1", "matplotlib-inline>=0.1", 'appnope;platform_system=="Darwin"', - "pyzmq>=17", + "pyzmq>=20", "psutil", "packaging", ] @@ -116,7 +116,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==22.12.0", "mdformat>0.7", "ruff==0.0.236"] +dependencies = ["black==23.1.0", "mdformat>0.7", "ruff==0.0.243"] detached = true [tool.hatch.envs.lint.scripts] style = [ @@ -227,7 +227,6 @@ select = [ "N", "PLC", "PLE", - "PLR", "PLW", "Q", "RUF", @@ -274,6 +273,8 @@ ignore = [ "N801", # SIM105 Use `contextlib.suppress(ValueError)` instead of try-except-pass "SIM105", + # S110 `try`-`except`-`pass` detected + "S110", ] unfixable = [ # Don't touch print statements From 1a486e06155a4d8e58e716fd40468cb5738ed6bb Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Mon, 13 Feb 2023 16:30:52 +0100 Subject: [PATCH 0977/1195] Un-expose __file__ and expose __session__ instead. (#1095) --- ipykernel/ipkernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 569e5a881..5e6c92391 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -130,7 +130,7 @@ def __init__(self, **kwargs): jupyter_session_name = os.environ.get('JPY_SESSION_NAME') if jupyter_session_name: - self.shell.user_ns['__file__'] = jupyter_session_name + self.shell.user_ns['__session__'] = jupyter_session_name self.shell.displayhook.pub_socket = self.iopub_socket self.shell.displayhook.topic = self._topic("execute_result") From d146b526d425bcb1a95a0ec7c1ebe243b7251a25 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 13 Feb 2023 15:39:39 +0000 Subject: [PATCH 0978/1195] Publish 6.21.2 SHA256 hashes: ipykernel-6.21.2-py3-none-any.whl: 430d00549b6aaf49bd0f5393150691edb1815afa62d457ee6b1a66b25cb17874 ipykernel-6.21.2.tar.gz: 6e9213484e4ce1fb14267ee435e18f23cc3a0634e635b9fb4ed4677b84e0fdf8 --- CHANGELOG.md | 22 ++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dabd0ce44..7a4c23b6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ +## 6.21.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.1...1a486e06155a4d8e58e716fd40468cb5738ed6bb)) + +### Bugs fixed + +- Un-expose __file__ and expose __session__ instead. [#1095](https://github.com/ipython/ipykernel/pull/1095) ([@Carreau](https://github.com/Carreau)) + +### Maintenance and upkeep improvements + +- Remove test_enter_eventloop [#1084](https://github.com/ipython/ipykernel/pull/1084) ([@davidbrochart](https://github.com/davidbrochart)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-02-02&to=2023-02-13&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-02-02..2023-02-13&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2023-02-02..2023-02-13&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2023-02-02..2023-02-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-02-02..2023-02-13&type=Issues) + + + ## 6.21.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.0...ac7776dfd68861ae005e1f142ec87cd6703847ea)) @@ -16,8 +36,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-01-30..2023-02-02&type=Issues) - - ## 6.21.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.20.2...dde698850d865dec89bba2305d1f3dc3134f8413)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index ae633f60d..7534eba71 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.21.1" +__version__ = "6.21.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From ea33a31ca48fa13d73b44837b6791aded1fe4858 Mon Sep 17 00:00:00 2001 From: dcsaba89 <54766615+dcsaba89@users.noreply.github.com> Date: Thu, 16 Feb 2023 23:54:28 +0100 Subject: [PATCH 0979/1195] Add license (#1098) --- COPYING.md | 61 --------------------------------- LICENSE | 30 ++++++++++++++++ README.md | 28 +++++++++++++++ ipykernel/gui/__init__.py | 2 +- ipykernel/gui/gtk3embed.py | 2 +- ipykernel/gui/gtkembed.py | 2 +- ipykernel/heartbeat.py | 2 +- ipykernel/inprocess/blocking.py | 2 +- ipykernel/inprocess/client.py | 2 +- pyproject.toml | 2 +- 10 files changed, 65 insertions(+), 68 deletions(-) delete mode 100644 COPYING.md create mode 100644 LICENSE diff --git a/COPYING.md b/COPYING.md deleted file mode 100644 index d7447c93b..000000000 --- a/COPYING.md +++ /dev/null @@ -1,61 +0,0 @@ -# Licensing terms - -This project is licensed under the terms of the Modified BSD License -(also known as New or Revised or 3-Clause BSD), as follows: - -- Copyright (c) 2015, IPython Development Team - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the IPython Development Team nor the names of its -contributors may be used to endorse or promote products derived from this -software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -## About the IPython Development Team - -The IPython Development Team is the set of all contributors to the IPython project. -This includes all of the IPython subprojects. - -The core team that coordinates development on GitHub can be found here: -https://github.com/ipython/. - -## Our Copyright Policy - -IPython uses a shared copyright model. Each contributor maintains copyright -over their contributions to IPython. But, it is important to note that these -contributions are typically only changes to the repositories. Thus, the IPython -source code, in its entirety is not the copyright of any single person or -institution. Instead, it is the collective copyright of the entire IPython -Development Team. If individual contributors want to maintain a record of what -changes/contributions they have specific copyright on, they should indicate -their copyright in the commit message of the change, when they commit the -change to one of the IPython repositories. - -With this in mind, the following banner should be used in any source code file -to indicate the copyright and license terms: - -``` -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. -``` diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..b5be8fe91 --- /dev/null +++ b/LICENSE @@ -0,0 +1,30 @@ +BSD 3-Clause License + +Copyright (c) 2015, IPython Development Team + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 0dbf03b3b..f758714a8 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,31 @@ and then from the root directory ```bash pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 ``` + +## About the IPython Development Team + +The IPython Development Team is the set of all contributors to the IPython project. +This includes all of the IPython subprojects. + +The core team that coordinates development on GitHub can be found here: +https://github.com/ipython/. + +## Our Copyright Policy + +IPython uses a shared copyright model. Each contributor maintains copyright +over their contributions to IPython. But, it is important to note that these +contributions are typically only changes to the repositories. Thus, the IPython +source code, in its entirety is not the copyright of any single person or +institution. Instead, it is the collective copyright of the entire IPython +Development Team. If individual contributors want to maintain a record of what +changes/contributions they have specific copyright on, they should indicate +their copyright in the commit message of the change, when they commit the +change to one of the IPython repositories. + +With this in mind, the following banner should be used in any source code file +to indicate the copyright and license terms: + +``` +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +``` diff --git a/ipykernel/gui/__init__.py b/ipykernel/gui/__init__.py index bbf3b9ffc..10d31f939 100644 --- a/ipykernel/gui/__init__.py +++ b/ipykernel/gui/__init__.py @@ -10,6 +10,6 @@ # # Distributed under the terms of the BSD License. # -# The full license is in the file COPYING.txt, distributed as part of this +# The full license is in the file LICENSE, distributed as part of this # software. # ----------------------------------------------------------------------------- diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index 3847683cd..4a85fefa5 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -4,7 +4,7 @@ # Copyright (C) 2010-2011 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in -# the file COPYING.txt, distributed as part of this software. +# the file LICENSE, distributed as part of this software. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index 5d9171bb8..2adcbd811 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -4,7 +4,7 @@ # Copyright (C) 2010-2011 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in -# the file COPYING.txt, distributed as part of this software. +# the file LICENSE, distributed as part of this software. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index cf2d6d5ae..33beaad66 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -5,7 +5,7 @@ # Copyright (C) 2008-2011 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. +# the file LICENSE, distributed as part of this software. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index 2274bed39..f09bb2316 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -8,7 +8,7 @@ # Copyright (C) 2012 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in -# the file COPYING.txt, distributed as part of this software. +# the file LICENSE, distributed as part of this software. # ----------------------------------------------------------------------------- from queue import Empty, Queue diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index aaf2db8f5..ea964ecde 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -4,7 +4,7 @@ # Copyright (C) 2012 The IPython Development Team # # Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. +# the file LICENSE, distributed as part of this software. # ----------------------------------------------------------------------------- # ----------------------------------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 0b3807ac9..7476444fc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "hatchling.build" name = "ipykernel" dynamic = ["version"] authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] -license = {file = "COPYING.md"} +license = {file = "LICENSE"} readme = "README.md" description = "IPython Kernel for Jupyter" keywords = ["Interactive", "Interpreter", "Shell", "Web"] From d579a388d43120b8448b50daa8ff1bc249187ab8 Mon Sep 17 00:00:00 2001 From: Marc Udoff Date: Sun, 26 Feb 2023 18:06:00 -0500 Subject: [PATCH 0980/1195] Update changelog for markdown typo (#1096) --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a4c23b6e..40489fcc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ ### Bugs fixed -- Un-expose __file__ and expose __session__ instead. [#1095](https://github.com/ipython/ipykernel/pull/1095) ([@Carreau](https://github.com/Carreau)) +- Un-expose `__file__` and expose `__session__` instead. [#1095](https://github.com/ipython/ipykernel/pull/1095) ([@Carreau](https://github.com/Carreau)) ### Maintenance and upkeep improvements @@ -42,7 +42,7 @@ ### Enhancements made -- Expose session start file in __file__. [#1078](https://github.com/ipython/ipykernel/pull/1078) ([@Carreau](https://github.com/Carreau)) +- Expose session start file in `__file__`. [#1078](https://github.com/ipython/ipykernel/pull/1078) ([@Carreau](https://github.com/Carreau)) - Add copy_to_globals debug request handling [#1055](https://github.com/ipython/ipykernel/pull/1055) ([@brichet](https://github.com/brichet)) ### Maintenance and upkeep improvements From 287184f8597d705a872d8cee10af75d7910bccd5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 2 Mar 2023 11:01:57 -0600 Subject: [PATCH 0981/1195] Update docs link (#1103) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f758714a8..1a2e537e0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://github.com/ipython/ipykernel/actions/workflows/ci.yml/badge.svg?query=branch%3Amain++)](https://github.com/ipython/ipykernel/actions/workflows/ci.yml/badge.svg?query=branch%3Amain++) [![codecov](https://codecov.io/gh/ipython/ipykernel/branch/main/graph/badge.svg?token=SyksDOcIJa)](https://codecov.io/gh/ipython/ipykernel) -[![Documentation Status](https://readthedocs.org/projects/ipython/badge/?version=latest)](http://ipython.readthedocs.io/en/latest/?badge=latest) +[![Documentation Status](https://readthedocs.org/projects/ipykernel/badge/?version=latest)](http://ipykernel.readthedocs.io/en/latest/?badge=latest) This package provides the IPython kernel for Jupyter. From e46f75b93c388886f4b6ba32182e29c3cc486984 Mon Sep 17 00:00:00 2001 From: Garland Zhang Date: Sat, 4 Mar 2023 10:03:02 -0800 Subject: [PATCH 0982/1195] Fix interrupt reply (#1101) * Fix interrupt * . * . * . * . --- ipykernel/kernelbase.py | 25 +++++++++++++++++-------- ipykernel/tests/conftest.py | 2 +- ipykernel/tests/test_ipkernel_direct.py | 15 ++++++++++++++- ipykernel/tests/test_kernel_direct.py | 19 ++++++++++++++++--- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 4727e8bd7..fd3a138df 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -883,7 +883,7 @@ async def comm_info_request(self, stream, ident, parent): msg = self.session.send(stream, "comm_info_reply", reply_content, parent, ident) self.log.debug("%s", msg) - def _send_interupt_children(self): + def _send_interrupt_children(self): if os.name == "nt": self.log.error("Interrupt message not supported on Windows") else: @@ -894,18 +894,27 @@ def _send_interupt_children(self): if pgid and pgid == pid and hasattr(os, "killpg"): try: os.killpg(pgid, SIGINT) - return except OSError: - pass - try: + os.kill(pid, SIGINT) + raise + else: os.kill(pid, SIGINT) - except OSError: - pass async def interrupt_request(self, stream, ident, parent): """Handle an interrupt request.""" - self._send_interupt_children() - content = parent["content"] + content: t.Dict[str, t.Any] = {"status": "ok"} + try: + self._send_interrupt_children() + except OSError as err: + import traceback + + content = { + "status": "error", + "traceback": traceback.format_stack(), + "ename": str(type(err).__name__), + "evalue": str(err), + } + self.session.send(stream, "interrupt_reply", content, parent, ident=ident) return diff --git a/ipykernel/tests/conftest.py b/ipykernel/tests/conftest.py index 45341cef6..00f8394f6 100644 --- a/ipykernel/tests/conftest.py +++ b/ipykernel/tests/conftest.py @@ -99,7 +99,7 @@ async def _wait_for_msg(self): _, msg = self.session.feed_identities(self._reply) return self.session.deserialize(msg) - def _send_interupt_children(self): + def _send_interrupt_children(self): # override to prevent deadlock pass diff --git a/ipykernel/tests/test_ipkernel_direct.py b/ipykernel/tests/test_ipkernel_direct.py index 0e65c9a8a..20e92a402 100644 --- a/ipykernel/tests/test_ipkernel_direct.py +++ b/ipykernel/tests/test_ipkernel_direct.py @@ -90,8 +90,21 @@ async def test_comm_info_request(ipkernel): async def test_direct_interrupt_request(ipkernel): - reply = await ipkernel.test_shell_message("interrupt_request", {}) + reply = await ipkernel.test_control_message("interrupt_request", {}) assert reply["header"]["msg_type"] == "interrupt_reply" + assert reply["content"] == {"status": "ok"} + + # test failure on interrupt request + def raiseOSError(): + raise OSError("evalue") + + ipkernel._send_interrupt_children = raiseOSError + reply = await ipkernel.test_control_message("interrupt_request", {}) + assert reply["header"]["msg_type"] == "interrupt_reply" + assert reply["content"]["status"] == "error" + assert reply["content"]["ename"] == "OSError" + assert reply["content"]["evalue"] == "evalue" + assert len(reply["content"]["traceback"]) > 0 # TODO: this causes deadlock diff --git a/ipykernel/tests/test_kernel_direct.py b/ipykernel/tests/test_kernel_direct.py index f75d8b6d7..7f47aaedd 100644 --- a/ipykernel/tests/test_kernel_direct.py +++ b/ipykernel/tests/test_kernel_direct.py @@ -69,8 +69,21 @@ async def test_comm_info_request(kernel): async def test_direct_interrupt_request(kernel): - reply = await kernel.test_shell_message("interrupt_request", {}) + reply = await kernel.test_control_message("interrupt_request", {}) assert reply["header"]["msg_type"] == "interrupt_reply" + assert reply["content"] == {"status": "ok"} + + # test failure on interrupt request + def raiseOSError(): + raise OSError("evalue") + + kernel._send_interrupt_children = raiseOSError + reply = await kernel.test_control_message("interrupt_request", {}) + assert reply["header"]["msg_type"] == "interrupt_reply" + assert reply["content"]["status"] == "error" + assert reply["content"]["ename"] == "OSError" + assert reply["content"]["evalue"] == "evalue" + assert len(reply["content"]["traceback"]) > 0 async def test_direct_shutdown_request(kernel): @@ -145,8 +158,8 @@ async def test_connect_request(kernel): await kernel.connect_request(kernel.shell_stream, "foo", {}) -async def test_send_interupt_children(kernel): - kernel._send_interupt_children() +async def test_send_interrupt_children(kernel): + kernel._send_interrupt_children() # TODO: this causes deadlock From ba3c1134db84c193a3650bb0532819bfc21623ac Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 6 Mar 2023 14:13:11 +0000 Subject: [PATCH 0983/1195] Publish 6.21.3 SHA256 hashes: ipykernel-6.21.3-py3-none-any.whl: 24ebd9715e317c185e37156ab3a87382410185230dde7aeffce389d6c7d4428a ipykernel-6.21.3.tar.gz: c8ff581905d70e7299bc1473a2f7c113bec1744fb3746d58e5b4b93bd8ee7001 --- CHANGELOG.md | 27 +++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40489fcc1..721fd35d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ +## 6.21.3 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.2...e46f75b93c388886f4b6ba32182e29c3cc486984)) + +### Bugs fixed + +- Fix interrupt reply [#1101](https://github.com/ipython/ipykernel/pull/1101) ([@garlandz-db](https://github.com/garlandz-db)) + +### Maintenance and upkeep improvements + +- Update docs link [#1103](https://github.com/ipython/ipykernel/pull/1103) ([@blink1073](https://github.com/blink1073)) +- Add license [#1098](https://github.com/ipython/ipykernel/pull/1098) ([@dcsaba89](https://github.com/dcsaba89)) + +### Documentation improvements + +- Update changelog for markdown typo [#1096](https://github.com/ipython/ipykernel/pull/1096) ([@mlucool](https://github.com/mlucool)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-02-13&to=2023-03-06&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-02-13..2023-03-06&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2023-02-13..2023-03-06&type=Issues) | [@dcsaba89](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adcsaba89+updated%3A2023-02-13..2023-03-06&type=Issues) | [@garlandz-db](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Agarlandz-db+updated%3A2023-02-13..2023-03-06&type=Issues) | [@mlucool](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amlucool+updated%3A2023-02-13..2023-03-06&type=Issues) + + + ## 6.21.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.1...1a486e06155a4d8e58e716fd40468cb5738ed6bb)) @@ -20,8 +45,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-02-02..2023-02-13&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2023-02-02..2023-02-13&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2023-02-02..2023-02-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-02-02..2023-02-13&type=Issues) - - ## 6.21.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.0...ac7776dfd68861ae005e1f142ec87cd6703847ea)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7534eba71..a5363eaf2 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.21.2" +__version__ = "6.21.3" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From a895ce72e4c0ecff95280e68ae96faa283e561cf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Mar 2023 11:10:00 -0600 Subject: [PATCH 0984/1195] [pre-commit.ci] pre-commit autoupdate (#1104)Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/charliermarsh/ruff-pre-commit: v0.0.243 → v0.0.254](https://github.com/charliermarsh/ruff-pre-commit/compare/v0.0.243...v0.0.254) * sync deps and lint * lint * lint * lint --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 2 +- ipykernel/ipkernel.py | 2 +- ipykernel/kernelapp.py | 4 ++-- ipykernel/pickleutil.py | 8 ++++---- pyproject.toml | 6 ++++-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index efaf832f7..dcdd43716 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.243 + rev: v0.0.254 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 5e6c92391..4cda01368 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -55,7 +55,7 @@ def _create_comm(*args, **kwargs): def _get_comm_manager(*args, **kwargs): """Create a new CommManager.""" - global _comm_manager + global _comm_manager # noqa if _comm_manager is None: with _comm_lock: if _comm_manager is None: diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index f0ef22361..9628c7d53 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -449,9 +449,9 @@ def init_blackhole(self): if self.no_stdout or self.no_stderr: blackhole = open(os.devnull, "w") # noqa if self.no_stdout: - sys.stdout = sys.__stdout__ = blackhole + sys.stdout = sys.__stdout__ = blackhole # type:ignore if self.no_stderr: - sys.stderr = sys.__stderr__ = blackhole + sys.stderr = sys.__stderr__ = blackhole # type:ignore def init_io(self): """Redirect input streams and set a display hook.""" diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 7a7e07ef6..b93e43d5f 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -77,7 +77,7 @@ def use_dill(): # dill doesn't work with cPickle, # tell the two relevant modules to use plain pickle - global pickle + global pickle # noqa pickle = dill try: @@ -98,7 +98,7 @@ def use_cloudpickle(): """ import cloudpickle - global pickle + global pickle # noqa pickle = cloudpickle try: @@ -278,9 +278,9 @@ def __init__(self, obj): self.shape = obj.shape self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str self.pickled = False - if sum(obj.shape) == 0: + if sum(obj.shape) == 0: # noqa self.pickled = True - elif obj.dtype == "O": + elif obj.dtype == "O": # noqa # can't handle object dtype with buffer approach self.pickled = True elif obj.dtype.fields and any(dt == "O" for dt, sz in obj.dtype.fields.values()): diff --git a/pyproject.toml b/pyproject.toml index 7476444fc..f1d0cecd2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==23.1.0", "mdformat>0.7", "ruff==0.0.243"] +dependencies = ["black==23.1.0", "mdformat>0.7", "ruff==0.0.254"] detached = true [tool.hatch.envs.lint.scripts] style = [ @@ -294,7 +294,9 @@ unfixable = [ # F841 Local variable `t` is assigned to but never used # EM101 Exception must not use a string literal, assign to variable first # PLR2004 Magic value used in comparison -"ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", "EM102", "EM103", "PLR2004"] +# PLW0603 Using the global statement to update ... +# PLW2901 `for` loop variable ... +"ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", "EM102", "EM103", "PLR2004", "PLW0603", "PLW2901"] [tool.interrogate] ignore-init-module=true From e2972d763b5357d4e1cb9b5355593583ca6d5657 Mon Sep 17 00:00:00 2001 From: martinRenou Date: Fri, 17 Mar 2023 22:10:47 +0100 Subject: [PATCH 0985/1195] Deprecate Comm class + Fix incompatibility with ipywidgets (#1097)Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> * Deprecate Comm class * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix comm creation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Don't fail on warnings mismatch for ipywidgets downstream tests * Escape * Disable more deprecation tests in ipywidgets tests --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/downstream.yml | 1 + ipykernel/comm/comm.py | 12 +++++++++- ipykernel/comm/manager.py | 39 ++++++++++++++++++++++++++++++++ ipykernel/tests/test_comm.py | 12 +++++++--- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index a37242e68..f4ee5cf91 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -38,6 +38,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: ipywidgets + test_command: pytest -vv -raXxs -k \"not deprecation_fa_icons and not tooltip_deprecation and not on_submit_deprecation\" -W default --durations 10 --color=yes jupyter_client: runs-on: ubuntu-latest diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index e236bee78..01cc4d7b2 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -5,6 +5,7 @@ import uuid from typing import Optional +from warnings import warn import comm.base_comm import traitlets.config @@ -70,8 +71,17 @@ def _default_kernel(self): def _default_comm_id(self): return uuid.uuid4().hex - def __init__(self, target_name='', data=None, metadata=None, buffers=None, **kwargs): + def __init__( + self, target_name='', data=None, metadata=None, buffers=None, show_warning=True, **kwargs + ): """Initialize a comm.""" + if show_warning: + warn( + "The `ipykernel.comm.Comm` class has been deprecated. Please use the `comm` module instead." + "For creating comms, use the function `from comm import create_comm`.", + DeprecationWarning, + ) + # Handle differing arguments between base classes. had_kernel = 'kernel' in kwargs kernel = kwargs.pop('kernel', None) diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index f18e272d2..7b0de6933 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -3,11 +3,16 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import logging import comm.base_comm import traitlets import traitlets.config +from .comm import Comm + +logger = logging.getLogger("ipykernel.comm") + class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): """A comm manager.""" @@ -21,3 +26,37 @@ def __init__(self, **kwargs): # CommManager doesn't take arguments, so we explicitly forward arguments comm.base_comm.CommManager.__init__(self) traitlets.config.LoggingConfigurable.__init__(self, **kwargs) + + def comm_open(self, stream, ident, msg): + """Handler for comm_open messages""" + # This is for backward compatibility, the comm_open creates a a new ipykernel.comm.Comm + # but we should let the base class create the comm with comm.create_comm in a major release + content = msg["content"] + comm_id = content["comm_id"] + target_name = content["target_name"] + f = self.targets.get(target_name, None) + comm = Comm( + comm_id=comm_id, + primary=False, + target_name=target_name, + show_warning=False, + ) + self.register_comm(comm) + if f is None: + logger.error("No such comm target registered: %s", target_name) + else: + try: + f(comm, msg) + return + except Exception: + logger.error("Exception opening comm with target: %s", target_name, exc_info=True) + + # Failure. + try: + comm.close() + except Exception: + logger.error( + """Could not close comm during `comm_open` failure + clean-up. The comm may not have been opened yet.""", + exc_info=True, + ) diff --git a/ipykernel/tests/test_comm.py b/ipykernel/tests/test_comm.py index 5ec0d455a..728731ce0 100644 --- a/ipykernel/tests/test_comm.py +++ b/ipykernel/tests/test_comm.py @@ -1,5 +1,7 @@ import unittest.mock +import pytest + from ipykernel.comm import Comm, CommManager from ipykernel.ipkernel import IPythonKernel from ipykernel.kernelbase import Kernel @@ -9,7 +11,8 @@ def test_comm(kernel: Kernel) -> None: manager = CommManager(kernel=kernel) kernel.comm_manager = manager # type:ignore - c = Comm(kernel=kernel, target_name="bar") + with pytest.deprecated_call(): + c = Comm(kernel=kernel, target_name="bar") msgs = [] assert kernel is c.kernel # type:ignore @@ -53,7 +56,8 @@ def on_msg(msg): kernel.comm_manager = manager # type:ignore with unittest.mock.patch.object(Comm, "publish_msg") as publish_msg: - comm = Comm() + with pytest.deprecated_call(): + comm = Comm() comm.on_msg(on_msg) comm.on_close(on_close) manager.register_comm(comm) @@ -96,5 +100,7 @@ def on_msg(msg): def test_comm_in_manager(ipkernel: IPythonKernel) -> None: - comm = Comm() + with pytest.deprecated_call(): + comm = Comm() + assert comm.comm_id in ipkernel.comm_manager.comms From aa53dcef1a282cba7dbd06d0715252ad10a9987c Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 20 Mar 2023 14:41:33 +0000 Subject: [PATCH 0986/1195] Publish 6.22.0 SHA256 hashes: ipykernel-6.22.0-py3-none-any.whl: 1ae6047c1277508933078163721bbb479c3e7292778a04b4bacf0874550977d6 ipykernel-6.22.0.tar.gz: 302558b81f1bc22dc259fb2a0c5c7cf2f4c0bdb21b50484348f7bafe7fb71421 --- CHANGELOG.md | 20 ++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 721fd35d1..95fe7ba98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ +## 6.22.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.3...e2972d763b5357d4e1cb9b5355593583ca6d5657)) + +### Bugs fixed + +- Deprecate Comm class + Fix incompatibility with ipywidgets [#1097](https://github.com/ipython/ipykernel/pull/1097) ([@martinRenou](https://github.com/martinRenou)) + +### Maintenance and upkeep improvements + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-03-06&to=2023-03-20&type=c)) + +[@martinRenou](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AmartinRenou+updated%3A2023-03-06..2023-03-20&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-03-06..2023-03-20&type=Issues) + + + ## 6.21.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.2...e46f75b93c388886f4b6ba32182e29c3cc486984)) @@ -25,8 +43,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-02-13..2023-03-06&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2023-02-13..2023-03-06&type=Issues) | [@dcsaba89](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adcsaba89+updated%3A2023-02-13..2023-03-06&type=Issues) | [@garlandz-db](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Agarlandz-db+updated%3A2023-02-13..2023-03-06&type=Issues) | [@mlucool](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amlucool+updated%3A2023-02-13..2023-03-06&type=Issues) - - ## 6.21.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.1...1a486e06155a4d8e58e716fd40468cb5738ed6bb)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index a5363eaf2..8b74b00ce 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.21.3" +__version__ = "6.22.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From dca55bf4b7f41c3cd437a57c45ac38f354f9e653 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Apr 2023 10:54:44 -0500 Subject: [PATCH 0987/1195] [pre-commit.ci] pre-commit autoupdate (#1108) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 6 +++--- ipykernel/_version.py | 2 +- ipykernel/comm/comm.py | 1 + ipykernel/gui/gtk3embed.py | 4 +++- ipykernel/gui/gtkembed.py | 4 +++- ipykernel/iostream.py | 3 ++- ipykernel/ipkernel.py | 1 + ipykernel/kernelspec.py | 2 +- ipykernel/parentpoller.py | 3 ++- ipykernel/pickleutil.py | 2 +- ipykernel/pylab/backend_inline.py | 1 + ipykernel/pylab/config.py | 1 + ipykernel/serialize.py | 6 +++--- ipykernel/tests/test_pickleutil.py | 2 +- ipykernel/trio_runner.py | 2 +- ipykernel/zmqshell.py | 4 ++-- ipykernel_launcher.py | 2 +- pyproject.toml | 7 +++++-- 18 files changed, 33 insertions(+), 20 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dcdd43716..85297d7c3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.21.0 + rev: 0.22.0 hooks: - id: check-github-workflows @@ -31,12 +31,12 @@ repos: - id: mdformat - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.254 + rev: v0.0.260 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 8b74b00ce..7db902549 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -17,4 +17,4 @@ version_info = tuple(parts) kernel_protocol_version_info = (5, 3) -kernel_protocol_version = "%s.%s" % kernel_protocol_version_info +kernel_protocol_version = "{}.{}".format(*kernel_protocol_version_info) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 01cc4d7b2..cbb3ecfa7 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -80,6 +80,7 @@ def __init__( "The `ipykernel.comm.Comm` class has been deprecated. Please use the `comm` module instead." "For creating comms, use the function `from comm import create_comm`.", DeprecationWarning, + stacklevel=2, ) # Handle differing arguments between base classes. diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index 4a85fefa5..baf8c5ee9 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -14,7 +14,9 @@ import sys import warnings -warnings.warn("The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning) +warnings.warn( + "The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning, stacklevel=2 +) # Third-party import gi diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index 2adcbd811..ac23afc71 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -14,7 +14,9 @@ import sys import warnings -warnings.warn("The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning) +warnings.warn( + "The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning, stacklevel=2 +) # Third-party import gobject diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index a37aaa6e6..b928976cc 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -134,7 +134,8 @@ def _setup_pipe_in(self): except zmq.ZMQError as e: warnings.warn( "Couldn't bind IOPub Pipe to 127.0.0.1: %s" % e - + "\nsubprocess output will be unavailable." + + "\nsubprocess output will be unavailable.", + stacklevel=2, ) self._pipe_flag = False pipe_in.close() diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 4cda01368..2952f788a 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -704,5 +704,6 @@ def __init__(self, *args, **kwargs): # pragma: no cover warnings.warn( "Kernel is a deprecated alias of ipykernel.ipkernel.IPythonKernel", DeprecationWarning, + stacklevel=2, ) super().__init__(*args, **kwargs) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 4f2fc33b2..320afff98 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -227,7 +227,7 @@ def start(self): ) opts = parser.parse_args(self.argv) if opts.env: - opts.env = {k: v for (k, v) in opts.env} + opts.env = dict(opts.env) try: dest = install( user=opts.user, diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index e500383fe..b6b34f7e5 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -115,6 +115,7 @@ def run(self): """Parent poll failed. If the frontend dies, the kernel may be left running. Please let us know about your system (bitness, Python, etc.) at - ipython-dev@scipy.org""" + ipython-dev@scipy.org""", + stacklevel=2, ) return diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index b93e43d5f..80f0c9a0b 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -300,7 +300,7 @@ def get_object(self, g=None): data = self.buffers[0] if self.pickled: # we just pickled it - return pickle.loads(data) + return pickle.loads(data) # noqa else: return frombuffer(data, dtype=self.dtype).reshape(self.shape) diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index b1627cac5..18e0222a0 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -11,4 +11,5 @@ "`ipykernel.pylab.backend_inline` is deprecated, directly " "use `matplotlib_inline.backend_inline`", DeprecationWarning, + stacklevel=2, ) diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index 7622f9e11..56557b1e1 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -10,4 +10,5 @@ warnings.warn( "`ipykernel.pylab.config` is deprecated, directly use `matplotlib_inline.config`", DeprecationWarning, + stacklevel=2, ) diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 616410c81..12e5b9a99 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -122,7 +122,7 @@ def deserialize_object(buffers, g=None): """ bufs = list(buffers) pobj = bufs.pop(0) - canned = pickle.loads(pobj) + canned = pickle.loads(pobj) # noqa if istype(canned, sequence_types) and len(canned) < MAX_ITEMS: for c in canned: _restore_buffers(c, bufs) @@ -183,9 +183,9 @@ def unpack_apply_message(bufs, g=None, copy=True): bufs = list(bufs) # allow us to pop assert len(bufs) >= 2, "not enough buffers!" pf = bufs.pop(0) - f = uncan(pickle.loads(pf), g) + f = uncan(pickle.loads(pf), g) # noqa pinfo = bufs.pop(0) - info = pickle.loads(pinfo) + info = pickle.loads(pinfo) # noqa arg_bufs, kwarg_bufs = bufs[: info["narg_bufs"]], bufs[info["narg_bufs"] :] args_list = [] diff --git a/ipykernel/tests/test_pickleutil.py b/ipykernel/tests/test_pickleutil.py index c48eadf77..3351d7882 100644 --- a/ipykernel/tests/test_pickleutil.py +++ b/ipykernel/tests/test_pickleutil.py @@ -16,7 +16,7 @@ def dumps(obj): def loads(obj): - return uncan(pickle.loads(obj)) + return uncan(pickle.loads(obj)) # noqa def test_no_closure(): diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index a9b327256..62fc9ea35 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -22,7 +22,7 @@ def initialize(self, kernel, io_loop): kernel.shell.set_trio_runner(self) kernel.shell.run_line_magic("autoawait", "trio") kernel.shell.magics_manager.magics["line"]["autoawait"] = lambda _: warnings.warn( - "Autoawait isn't allowed in Trio background loop mode." + "Autoawait isn't allowed in Trio background loop mode.", stacklevel=2 ) self._interrupted = False bg_thread = threading.Thread(target=io_loop.start, daemon=True, name="TornadoBackground") diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 5a5c9e88b..9455b8f74 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -363,7 +363,7 @@ def connect_info(self, arg_s): connection_file = get_connection_file() info = get_connection_info(unpack=False) except Exception as e: - warnings.warn("Could not get connection info: %r" % e) + warnings.warn("Could not get connection info: %r" % e, stacklevel=2) return # if it's in the default dir, truncate to basename @@ -399,7 +399,7 @@ def qtconsole(self, arg_s): try: connect_qtconsole(argv=arg_split(arg_s, os.name == "posix")) except Exception as e: - warnings.warn("Could not start qtconsole: %r" % e) + warnings.warn("Could not start qtconsole: %r" % e, stacklevel=2) return @line_magic diff --git a/ipykernel_launcher.py b/ipykernel_launcher.py index 49aa2651a..6290c7e52 100644 --- a/ipykernel_launcher.py +++ b/ipykernel_launcher.py @@ -9,7 +9,7 @@ if __name__ == "__main__": # Remove the CWD from sys.path while we load stuff. # This is added back by InteractiveShellApp.init_path() - if sys.path[0] == "": + if sys.path[0] == "": # noqa del sys.path[0] from ipykernel import kernelapp as app diff --git a/pyproject.toml b/pyproject.toml index f1d0cecd2..c56abdf1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==23.1.0", "mdformat>0.7", "ruff==0.0.254"] +dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.260"] detached = true [tool.hatch.envs.lint.scripts] style = [ @@ -296,7 +296,10 @@ unfixable = [ # PLR2004 Magic value used in comparison # PLW0603 Using the global statement to update ... # PLW2901 `for` loop variable ... -"ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", "EM102", "EM103", "PLR2004", "PLW0603", "PLW2901"] +# PLC1901 `stderr == ""` can be simplified to `not stderr` as an empty string is falsey +# B018 Found useless expression. Either assign it to a variable or remove it. +"ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", + "EM102", "EM103", "PLR2004", "PLW0603", "PLW2901", "PLC1901", "B018"] [tool.interrogate] ignore-init-module=true From 4f72a6c464a07b355553d5331cf597a2406a17af Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 13 Apr 2023 15:57:50 -0500 Subject: [PATCH 0988/1195] Use local coverage (#1109) --- .github/workflows/ci.yml | 19 +++++++++++++------ README.md | 1 - codecov.yml | 9 --------- pyproject.toml | 2 ++ 4 files changed, 15 insertions(+), 16 deletions(-) delete mode 100644 codecov.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c730f117d..ad5d4fece 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,17 +57,24 @@ jobs: run: | hatch run cov:nowarn || hatch run test:nowarn --lf - - name: Coverage - run: | - pip install codecov coverage[toml] - codecov - - name: Check Launcher run: | pip install . cd $HOME python -m ipykernel_launcher --help + - uses: jupyterlab/maintainer-tools/.github/actions/upload-coverage@v1 + + coverage: + runs-on: ubuntu-latest + needs: + - build + steps: + - uses: actions/checkout@v3 + - uses: jupyterlab/maintainer-tools/.github/actions/report-coverage@v1 + with: + fail_under: 80 + test_lint: name: Test Lint runs-on: ubuntu-latest @@ -190,7 +197,7 @@ jobs: tests_check: # This job does nothing and is only used for the branch protection if: always() needs: - - build + - coverage - test_docs - test_without_debugpy - test_miniumum_versions diff --git a/README.md b/README.md index 1a2e537e0..ec39e4785 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # IPython Kernel for Jupyter [![Build Status](https://github.com/ipython/ipykernel/actions/workflows/ci.yml/badge.svg?query=branch%3Amain++)](https://github.com/ipython/ipykernel/actions/workflows/ci.yml/badge.svg?query=branch%3Amain++) -[![codecov](https://codecov.io/gh/ipython/ipykernel/branch/main/graph/badge.svg?token=SyksDOcIJa)](https://codecov.io/gh/ipython/ipykernel) [![Documentation Status](https://readthedocs.org/projects/ipykernel/badge/?version=latest)](http://ipykernel.readthedocs.io/en/latest/?badge=latest) This package provides the IPython kernel for Jupyter. diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index b75c3e2db..000000000 --- a/codecov.yml +++ /dev/null @@ -1,9 +0,0 @@ -coverage: - status: - project: - default: - target: auto - threshold: 1 - patch: - default: - target: 0% diff --git a/pyproject.toml b/pyproject.toml index c56abdf1b..50ac5096d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -191,6 +191,8 @@ exclude_lines = [ ] [tool.coverage.run] +relative_files = true +source = ["ipykernel"] omit = [ "ipykernel/tests/*", "ipykernel/datapub.py", From 756c588df92cab0adddecdfae63e019540f62331 Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Fri, 14 Apr 2023 22:04:39 +0200 Subject: [PATCH 0989/1195] Add outstream hook similar to display publisher (#1110) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/iostream.py | 62 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index b928976cc..9de6156b3 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -13,6 +13,7 @@ from binascii import b2a_hex from collections import deque from io import StringIO, TextIOBase +from threading import local from typing import Any, Callable, Deque, Optional from weakref import WeakSet @@ -403,6 +404,7 @@ def __init__( self.echo = None self._isatty = bool(isatty) self._should_watch = False + self._local = local() if ( watchfd @@ -525,11 +527,19 @@ def _flush(self): # There should be a better way to do this. self.session.pid = os.getpid() content = {"name": self.name, "text": data} + msg = self.session.msg("stream", content, parent=self.parent_header) + + # Each transform either returns a new + # message or None. If None is returned, + # the message has been 'used' and we return. + for hook in self._hooks: + msg = hook(msg) + if msg is None: + return + self.session.send( self.pub_thread, - "stream", - content=content, - parent=self.parent_header, + msg, ident=self.topic, ) @@ -601,3 +611,49 @@ def _rotate_buffer(self): old_buffer = self._buffer self._buffer = StringIO() return old_buffer + + @property + def _hooks(self): + if not hasattr(self._local, "hooks"): + # create new list for a new thread + self._local.hooks = [] + return self._local.hooks + + def register_hook(self, hook): + """ + Registers a hook with the thread-local storage. + + Parameters + ---------- + hook : Any callable object + + Returns + ------- + Either a publishable message, or `None`. + The hook callable must return a message from + the __call__ method if they still require the + `session.send` method to be called after transformation. + Returning `None` will halt that execution path, and + session.send will not be called. + """ + self._hooks.append(hook) + + def unregister_hook(self, hook): + """ + Un-registers a hook with the thread-local storage. + + Parameters + ---------- + hook : Any callable object which has previously been + registered as a hook. + + Returns + ------- + bool - `True` if the hook was removed, `False` if it wasn't + found. + """ + try: + self._hooks.remove(hook) + return True + except ValueError: + return False From c698d27d3de46bc1d7447f6991e86e43d0d9dcbc Mon Sep 17 00:00:00 2001 From: tkrabel-db <91616041+tkrabel-db@users.noreply.github.com> Date: Fri, 5 May 2023 02:55:56 +0200 Subject: [PATCH 0990/1195] Support control<>iopub messages to e.g. unblock comm_msg from command execution (#1114) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/comm/comm.py | 9 ++++++++- ipykernel/control.py | 6 ++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index cbb3ecfa7..6825c054d 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -3,6 +3,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import threading import uuid from typing import Optional from warnings import warn @@ -11,6 +12,7 @@ import traitlets.config from traitlets import Bool, Bytes, Instance, Unicode, default +from ipykernel.control import CONTROL_THREAD_NAME from ipykernel.jsonutil import json_clean from ipykernel.kernelbase import Kernel @@ -30,6 +32,11 @@ def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): metadata = {} if metadata is None else metadata content = json_clean(dict(data=data, comm_id=self.comm_id, **keys)) + if threading.current_thread().name == CONTROL_THREAD_NAME: + channel_from_which_to_get_parent_header = "control" + else: + channel_from_which_to_get_parent_header = "shell" + if self.kernel is None: self.kernel = Kernel.instance() @@ -38,7 +45,7 @@ def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): msg_type, content, metadata=json_clean(metadata), - parent=self.kernel.get_parent("shell"), + parent=self.kernel.get_parent(channel_from_which_to_get_parent_header), ident=self.topic, buffers=buffers, ) diff --git a/ipykernel/control.py b/ipykernel/control.py index d78a4ebe1..0ee0fad05 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -3,20 +3,22 @@ from tornado.ioloop import IOLoop +CONTROL_THREAD_NAME = "Control" + class ControlThread(Thread): """A thread for a control channel.""" def __init__(self, **kwargs): """Initialize the thread.""" - Thread.__init__(self, name="Control", **kwargs) + Thread.__init__(self, name=CONTROL_THREAD_NAME, **kwargs) self.io_loop = IOLoop(make_current=False) self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True def run(self): """Run the thread.""" - self.name = "Control" + self.name = CONTROL_THREAD_NAME try: self.io_loop.start() finally: From 3dd6dc9712ff6eb0a53cf79969dcefa0ba1b086e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 4 May 2023 20:13:49 -0500 Subject: [PATCH 0991/1195] [pre-commit.ci] pre-commit autoupdate (#1112) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 2 +- ipykernel/connect.py | 2 +- pyproject.toml | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 85297d7c3..58fac7cf8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.260 + rev: v0.0.263 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel/connect.py b/ipykernel/connect.py index b933179c5..255716497 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -117,7 +117,7 @@ def connect_qtconsole(connection_file=None, argv=None): kwargs["start_new_session"] = True return Popen( - [sys.executable, "-c", cmd, "--existing", cf, *argv], + [sys.executable, "-c", cmd, "--existing", cf, *argv], # noqa stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != "win32"), diff --git a/pyproject.toml b/pyproject.toml index 50ac5096d..b042fac27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,7 +116,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.260"] +dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.263"] detached = true [tool.hatch.envs.lint.scripts] style = [ @@ -300,8 +300,9 @@ unfixable = [ # PLW2901 `for` loop variable ... # PLC1901 `stderr == ""` can be simplified to `not stderr` as an empty string is falsey # B018 Found useless expression. Either assign it to a variable or remove it. +# S603 `subprocess` call: check for execution of untrusted input "ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", - "EM102", "EM103", "PLR2004", "PLW0603", "PLW2901", "PLC1901", "B018"] + "EM102", "EM103", "PLR2004", "PLW0603", "PLW2901", "PLC1901", "B018", "S603"] [tool.interrogate] ignore-init-module=true From a35c49f35fe712fd12fa77fc5534f9bede79f993 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 8 May 2023 11:11:47 +0000 Subject: [PATCH 0992/1195] Publish 6.23.0 SHA256 hashes: ipykernel-6.23.0-py3-none-any.whl: fc886f1dcdc0ec17f277e4d21fd071c857d381adcb04f3f3735d25325ca323c6 ipykernel-6.23.0.tar.gz: bd6f487d9e2744c84f6e667d46462d7647a4c862e70e08282f05a52b9d4b705f --- CHANGELOG.md | 23 +++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95fe7ba98..badb52e40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ +## 6.23.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.22.0...3dd6dc9712ff6eb0a53cf79969dcefa0ba1b086e)) + +### Enhancements made + +- Support control\<>iopub messages to e.g. unblock comm_msg from command execution [#1114](https://github.com/ipython/ipykernel/pull/1114) ([@tkrabel-db](https://github.com/tkrabel-db)) +- Add outstream hook similar to display publisher [#1110](https://github.com/ipython/ipykernel/pull/1110) ([@maartenbreddels](https://github.com/maartenbreddels)) + +### Maintenance and upkeep improvements + +- Use local coverage [#1109](https://github.com/ipython/ipykernel/pull/1109) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-03-20&to=2023-05-08&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-03-20..2023-05-08&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2023-03-20..2023-05-08&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-03-20..2023-05-08&type=Issues) | [@tkrabel-db](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Atkrabel-db+updated%3A2023-03-20..2023-05-08&type=Issues) + + + ## 6.22.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.3...e2972d763b5357d4e1cb9b5355593583ca6d5657)) @@ -18,8 +39,6 @@ [@martinRenou](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AmartinRenou+updated%3A2023-03-06..2023-03-20&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-03-06..2023-03-20&type=Issues) - - ## 6.21.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.2...e46f75b93c388886f4b6ba32182e29c3cc486984)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7db902549..7050c93ca 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.22.0" +__version__ = "6.23.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From a0e887abb9f027d88572ced3c547383c09b8494b Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 8 May 2023 16:59:30 +0200 Subject: [PATCH 0993/1195] update readthedocs env to 3.11 (#1117) --- .readthedocs.yaml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index fed614d54..7ab2c8bf0 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,8 +1,14 @@ version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.11" + sphinx: configuration: docs/conf.py + python: - version: 3.8 install: # install itself with pip install . - method: pip From d63c33afb9872f2781997b2428d7e9e0c1d23d41 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 10 May 2023 18:09:04 +0200 Subject: [PATCH 0994/1195] Avoid echoing onto a captured FD (#1111) --- .github/workflows/ci.yml | 5 + ipykernel/iostream.py | 40 ++++++-- ipykernel/tests/test_io.py | 197 ++++++++++++++++++++++++++++--------- pyproject.toml | 1 + 4 files changed, 190 insertions(+), 53 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ad5d4fece..8781622d5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,6 +150,11 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: dependency_type: minimum + + - name: List installed packages + run: | + hatch run test:list + - name: Run the unit tests run: | hatch run test:nowarn || hatch run test:nowarn --lf diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 9de6156b3..8b5e47b30 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -364,7 +364,7 @@ def __init__( echo : bool whether to echo output watchfd : bool (default, True) - Watch the file descripttor corresponding to the replaced stream. + Watch the file descriptor corresponding to the replaced stream. This is useful if you know some underlying code will write directly the file descriptor by its number. It will spawn a watching thread, that will swap the give file descriptor for a pipe, read from the @@ -408,19 +408,39 @@ def __init__( if ( watchfd - and (sys.platform.startswith("linux") or sys.platform.startswith("darwin")) - and ("PYTEST_CURRENT_TEST" not in os.environ) + and ( + (sys.platform.startswith("linux") or sys.platform.startswith("darwin")) + # Pytest set its own capture. Don't redirect from within pytest. + and ("PYTEST_CURRENT_TEST" not in os.environ) + ) + # allow forcing watchfd (mainly for tests) + or watchfd == "force" ): - # Pytest set its own capture. Dont redirect from within pytest. - self._should_watch = True self._setup_stream_redirects(name) if echo: if hasattr(echo, "read") and hasattr(echo, "write"): + # make sure we aren't trying to echo on the FD we're watching! + # that would cause an infinite loop, always echoing on itself + if self._should_watch: + try: + echo_fd = echo.fileno() + except Exception: + echo_fd = None + + if echo_fd is not None and echo_fd == self._original_stdstream_fd: + # echo on the _copy_ we made during + # this is the actual terminal FD now + echo = io.TextIOWrapper( + io.FileIO( + self._original_stdstream_copy, + "w", + ) + ) self.echo = echo else: - msg = "echo argument must be a file like object" + msg = "echo argument must be a file-like object" raise ValueError(msg) def isatty(self): @@ -433,7 +453,7 @@ def isatty(self): def _setup_stream_redirects(self, name): pr, pw = os.pipe() - fno = getattr(sys, name).fileno() + fno = self._original_stdstream_fd = getattr(sys, name).fileno() self._original_stdstream_copy = os.dup(fno) os.dup2(pw, fno) @@ -455,7 +475,13 @@ def close(self): """Close the stream.""" if self._should_watch: self._should_watch = False + # thread won't wake unless there's something to read + # writing something after _should_watch will not be echoed + os.write(self._original_stdstream_fd, b'\0') self.watch_fd_thread.join() + # restore original FDs + os.dup2(self._original_stdstream_copy, self._original_stdstream_fd) + os.close(self._original_stdstream_copy) if self._exc: etype, value, tb = self._exc traceback.print_exception(etype, value, tb) diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index 221af1f8b..6a9f65170 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -1,7 +1,12 @@ """Test IO capturing functionality""" import io +import os +import subprocess +import sys +import time import warnings +from unittest import mock import pytest import zmq @@ -10,20 +15,28 @@ from ipykernel.iostream import MASTER, BackgroundSocket, IOPubThread, OutStream -def test_io_api(): - """Test that wrapped stdout has the same API as a normal TextIO object""" - session = Session() +@pytest.fixture +def ctx(): ctx = zmq.Context() - pub = ctx.socket(zmq.PUB) - thread = IOPubThread(pub) - thread.start() + yield ctx + ctx.destroy() - stream = OutStream(session, thread, "stdout") - # cleanup unused zmq objects before we start testing - thread.stop() - thread.close() - ctx.term() +@pytest.fixture +def iopub_thread(ctx): + with ctx.socket(zmq.PUB) as pub: + thread = IOPubThread(pub) + thread.start() + + yield thread + thread.stop() + thread.close() + + +def test_io_api(iopub_thread): + """Test that wrapped stdout has the same API as a normal TextIO object""" + session = Session() + stream = OutStream(session, iopub_thread, "stdout") assert stream.errors is None assert not stream.isatty() @@ -43,28 +56,21 @@ def test_io_api(): stream.write(b"") # type:ignore -def test_io_isatty(): +def test_io_isatty(iopub_thread): session = Session() - ctx = zmq.Context() - pub = ctx.socket(zmq.PUB) - thread = IOPubThread(pub) - thread.start() - - stream = OutStream(session, thread, "stdout", isatty=True) + stream = OutStream(session, iopub_thread, "stdout", isatty=True) assert stream.isatty() -def test_io_thread(): - ctx = zmq.Context() - pub = ctx.socket(zmq.PUB) - thread = IOPubThread(pub) +def test_io_thread(iopub_thread): + thread = iopub_thread thread._setup_pipe_in() msg = [thread._pipe_uuid, b"a"] thread._handle_pipe_msg(msg) ctx1, pipe = thread._setup_pipe_out() pipe.close() thread._pipe_in.close() - thread._check_mp_mode = lambda: MASTER # type:ignore + thread._check_mp_mode = lambda: MASTER thread._really_send([b"hi"]) ctx1.destroy() thread.close() @@ -72,40 +78,139 @@ def test_io_thread(): thread._really_send(None) -def test_background_socket(): - ctx = zmq.Context() - pub = ctx.socket(zmq.PUB) - thread = IOPubThread(pub) - sock = BackgroundSocket(thread) +def test_background_socket(iopub_thread): + sock = BackgroundSocket(iopub_thread) assert sock.__class__ == BackgroundSocket with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) sock.linger = 101 - assert thread.socket.linger == 101 - assert sock.io_thread == thread + assert iopub_thread.socket.linger == 101 + assert sock.io_thread == iopub_thread sock.send(b"hi") -def test_outstream(): +def test_outstream(iopub_thread): session = Session() - ctx = zmq.Context() - pub = ctx.socket(zmq.PUB) - thread = IOPubThread(pub) - thread.start() - + pub = iopub_thread.socket with warnings.catch_warnings(): warnings.simplefilter("ignore", DeprecationWarning) stream = OutStream(session, pub, "stdout") - stream = OutStream(session, thread, "stdout", pipe=object()) + stream.close() + stream = OutStream(session, iopub_thread, "stdout", pipe=object()) + stream.close() - stream = OutStream(session, thread, "stdout", watchfd=False) + stream = OutStream(session, iopub_thread, "stdout", watchfd=False) stream.close() - stream = OutStream(session, thread, "stdout", isatty=True, echo=io.StringIO()) - with pytest.raises(io.UnsupportedOperation): - stream.fileno() - stream._watch_pipe_fd() - stream.flush() - stream.write("hi") - stream.writelines(["ab", "cd"]) - assert stream.writable() + stream = OutStream(session, iopub_thread, "stdout", isatty=True, echo=io.StringIO()) + + with stream: + with pytest.raises(io.UnsupportedOperation): + stream.fileno() + stream._watch_pipe_fd() + stream.flush() + stream.write("hi") + stream.writelines(["ab", "cd"]) + assert stream.writable() + + +def subprocess_test_echo_watch(): + # handshake Pub subscription + session = Session(key=b'abc') + + # use PUSH socket to avoid subscription issues + with zmq.Context() as ctx, ctx.socket(zmq.PUSH) as pub: + pub.connect(os.environ["IOPUB_URL"]) + iopub_thread = IOPubThread(pub) + iopub_thread.start() + stdout_fd = sys.stdout.fileno() + sys.stdout.flush() + stream = OutStream( + session, + iopub_thread, + "stdout", + isatty=True, + echo=sys.stdout, + watchfd="force", + ) + save_stdout = sys.stdout + with stream, mock.patch.object(sys, "stdout", stream): + # write to low-level FD + os.write(stdout_fd, b"fd\n") + # print (writes to stream) + print("print\n", end="") + sys.stdout.flush() + # write to unwrapped __stdout__ (should also go to original FD) + sys.__stdout__.write("__stdout__\n") + sys.__stdout__.flush() + # write to original sys.stdout (should be the same as __stdout__) + save_stdout.write("stdout\n") + save_stdout.flush() + # is there another way to flush on the FD? + fd_file = os.fdopen(stdout_fd, "w") + fd_file.flush() + # we don't have a sync flush on _reading_ from the watched pipe + time.sleep(1) + stream.flush() + iopub_thread.stop() + iopub_thread.close() + + +@pytest.mark.skipif(sys.platform.startswith("win"), reason="Windows") +def test_echo_watch(ctx): + """Test echo on underlying FD while capturing the same FD + + Test runs in a subprocess to avoid messing with pytest output capturing. + """ + s = ctx.socket(zmq.PULL) + port = s.bind_to_random_port("tcp://127.0.0.1") + url = f"tcp://127.0.0.1:{port}" + session = Session(key=b'abc') + messages = [] + stdout_chunks = [] + with s: + env = dict(os.environ) + env["IOPUB_URL"] = url + env["PYTHONUNBUFFERED"] = "1" + env.pop("PYTEST_CURRENT_TEST", None) + p = subprocess.run( + [ + sys.executable, + "-c", + f"import {__name__}; {__name__}.subprocess_test_echo_watch()", + ], + env=env, + capture_output=True, + text=True, + timeout=10, + ) + print(f"{p.stdout=}") + print(f"{p.stderr}=", file=sys.stderr) + assert p.returncode == 0 + while s.poll(timeout=100): + ident, msg = session.recv(s) + assert msg is not None # for type narrowing + if msg["header"]["msg_type"] == "stream" and msg["content"]["name"] == "stdout": + stdout_chunks.append(msg["content"]["text"]) + + # check outputs + # use sets of lines to ignore ordering issues with + # async flush and watchfd thread + + # Check the stream output forwarded over zmq + zmq_stdout = "".join(stdout_chunks) + assert set(zmq_stdout.strip().splitlines()) == { + "fd", + "print", + "stdout", + "__stdout__", + } + + # Check what was written to the process stdout (kernel terminal) + # just check that each output source went to the terminal + assert set(p.stdout.strip().splitlines()) == { + "fd", + "print", + "stdout", + "__stdout__", + } diff --git a/pyproject.toml b/pyproject.toml index b042fac27..3494fefae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,6 +91,7 @@ api = "sphinx-apidoc -o docs/api -f -E ipykernel ipykernel/tests ipykernel/inpro [tool.hatch.envs.test] features = ["test"] [tool.hatch.envs.test.scripts] +list = "python -m pip freeze" test = "python -m pytest -vv {args}" nowarn = "test -W default {args}" From f7e5a8fb5c37d81c55b9ea785168c8a19d110eb4 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 15 May 2023 13:41:25 +0000 Subject: [PATCH 0995/1195] Publish 6.23.1 SHA256 hashes: ipykernel-6.23.1-py3-none-any.whl: 77aeffab056c21d16f1edccdc9e5ccbf7d96eb401bd6703610a21be8b068aadc ipykernel-6.23.1.tar.gz: 1aba0ae8453e15e9bc6b24e497ef6840114afcdb832ae597f32137fa19d42a6f --- CHANGELOG.md | 22 ++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index badb52e40..9d599e576 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ +## 6.23.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.0...d63c33afb9872f2781997b2428d7e9e0c1d23d41)) + +### Bugs fixed + +- Avoid echoing onto a captured FD [#1111](https://github.com/ipython/ipykernel/pull/1111) ([@minrk](https://github.com/minrk)) + +### Maintenance and upkeep improvements + +- update readthedocs env to 3.11 [#1117](https://github.com/ipython/ipykernel/pull/1117) ([@minrk](https://github.com/minrk)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-05-08&to=2023-05-15&type=c)) + +[@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2023-05-08..2023-05-15&type=Issues) + + + ## 6.23.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.22.0...3dd6dc9712ff6eb0a53cf79969dcefa0ba1b086e)) @@ -21,8 +41,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-03-20..2023-05-08&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2023-03-20..2023-05-08&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-03-20..2023-05-08&type=Issues) | [@tkrabel-db](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Atkrabel-db+updated%3A2023-03-20..2023-05-08&type=Issues) - - ## 6.22.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.3...e2972d763b5357d4e1cb9b5355593583ca6d5657)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7050c93ca..61c2db121 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.23.0" +__version__ = "6.23.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 3455c29d8787425f58f30ff500cdca33fd11d5fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 6 Jun 2023 11:41:52 -0500 Subject: [PATCH 0996/1195] [pre-commit.ci] pre-commit autoupdate (#1120) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 4 ++-- ipykernel/eventloops.py | 5 ++--- pyproject.toml | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 58fac7cf8..82b55d101 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.22.0 + rev: 0.23.1 hooks: - id: check-github-workflows @@ -36,7 +36,7 @@ repos: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.263 + rev: v0.0.270 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 412a246fe..8bf0996d7 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -419,10 +419,9 @@ def loop_asyncio_exit(kernel): loop = asyncio.get_event_loop() - @asyncio.coroutine - def close_loop(): + async def close_loop(): if hasattr(loop, "shutdown_asyncgens"): - yield from loop.shutdown_asyncgens() + yield loop.shutdown_asyncgens() loop._should_close = True # type:ignore[attr-defined] loop.stop() diff --git a/pyproject.toml b/pyproject.toml index 3494fefae..8ba499ca2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,7 +117,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.263"] +dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.270"] detached = true [tool.hatch.envs.lint.scripts] style = [ From 1c7f626f801168293115c7343b61a21b5fa8f9e7 Mon Sep 17 00:00:00 2001 From: Charles Cooper Date: Thu, 8 Jun 2023 08:42:45 -0700 Subject: [PATCH 0997/1195] fix: protect stdout/stderr restoration in `InProcessKernel._redirected_io` (#1122) --- ipykernel/inprocess/ipkernel.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index df34303b4..3f7fcce9e 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -121,9 +121,11 @@ def _input_request(self, prompt, ident, parent, password=False): def _redirected_io(self): """Temporarily redirect IO to the kernel.""" sys_stdout, sys_stderr = sys.stdout, sys.stderr - sys.stdout, sys.stderr = self.stdout, self.stderr - yield - sys.stdout, sys.stderr = sys_stdout, sys_stderr + try: + sys.stdout, sys.stderr = self.stdout, self.stderr + yield + finally: + sys.stdout, sys.stderr = sys_stdout, sys_stderr # ------ Trait change handlers -------------------------------------------- From 112ca66da0ee8156b983094b2c8e2926ed63cfcb Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 12 Jun 2023 22:13:12 +0200 Subject: [PATCH 0998/1195] Avoid ResourceWarning on implicitly closed event pipe sockets (#1125) Co-authored-by: Steven Silvester Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/iostream.py | 50 ++++++++++++++++++++++++++++++++------ ipykernel/tests/test_io.py | 35 ++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 7 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 8b5e47b30..a1e138452 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -3,6 +3,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import asyncio import atexit import io import os @@ -14,8 +15,7 @@ from collections import deque from io import StringIO, TextIOBase from threading import local -from typing import Any, Callable, Deque, Optional -from weakref import WeakSet +from typing import Any, Callable, Deque, Dict, Optional import zmq from jupyter_client.session import extract_header @@ -63,7 +63,10 @@ def __init__(self, socket, pipe=False): self._setup_pipe_in() self._local = threading.local() self._events: Deque[Callable[..., Any]] = deque() - self._event_pipes: WeakSet[Any] = WeakSet() + self._event_pipes: Dict[threading.Thread, Any] = {} + self._event_pipe_gc_lock: threading.Lock = threading.Lock() + self._event_pipe_gc_seconds: float = 10 + self._event_pipe_gc_task: Optional[asyncio.Task] = None self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main, name="IOPub") self.thread.daemon = True @@ -73,7 +76,18 @@ def __init__(self, socket, pipe=False): def _thread_main(self): """The inner loop that's actually run in a thread""" + + def _start_event_gc(): + self._event_pipe_gc_task = asyncio.ensure_future(self._run_event_pipe_gc()) + + self.io_loop.run_sync(_start_event_gc) self.io_loop.start() + if self._event_pipe_gc_task is not None: + # cancel gc task to avoid pending task warnings + async def _cancel(): + self._event_pipe_gc_task.cancel() # type:ignore + + self.io_loop.run_sync(_cancel) self.io_loop.close(all_fds=True) def _setup_event_pipe(self): @@ -88,6 +102,26 @@ def _setup_event_pipe(self): self._event_puller = ZMQStream(pipe_in, self.io_loop) self._event_puller.on_recv(self._handle_event) + async def _run_event_pipe_gc(self): + """Task to run event pipe gc continuously""" + while True: + await asyncio.sleep(self._event_pipe_gc_seconds) + try: + await self._event_pipe_gc() + except Exception as e: + print(f"Exception in IOPubThread._event_pipe_gc: {e}", file=sys.__stderr__) + + async def _event_pipe_gc(self): + """run a single garbage collection on event pipes""" + if not self._event_pipes: + # don't acquire the lock if there's nothing to do + return + with self._event_pipe_gc_lock: + for thread, socket in list(self._event_pipes.items()): + if not thread.is_alive(): + socket.close() + del self._event_pipes[thread] + @property def _event_pipe(self): """thread-local event pipe for signaling events that should be processed in the thread""" @@ -100,9 +134,11 @@ def _event_pipe(self): event_pipe.linger = 0 event_pipe.connect(self._event_interface) self._local.event_pipe = event_pipe - # WeakSet so that event pipes will be closed by garbage collection - # when their threads are terminated - self._event_pipes.add(event_pipe) + # associate event pipes to their threads + # so they can be closed explicitly + # implicit close on __del__ throws a ResourceWarning + with self._event_pipe_gc_lock: + self._event_pipes[threading.current_thread()] = event_pipe return event_pipe def _handle_event(self, msg): @@ -188,7 +224,7 @@ def stop(self): # close *all* event pipes, created in any thread # event pipes can only be used from other threads while self.thread.is_alive() # so after thread.join, this should be safe - for event_pipe in self._event_pipes: + for _thread, event_pipe in self._event_pipes.items(): event_pipe.close() def close(self): diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index 6a9f65170..404657cbb 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -4,8 +4,10 @@ import os import subprocess import sys +import threading import time import warnings +from concurrent.futures import Future, ThreadPoolExecutor from unittest import mock import pytest @@ -114,6 +116,39 @@ def test_outstream(iopub_thread): assert stream.writable() +async def test_event_pipe_gc(iopub_thread): + session = Session(key=b'abc') + stream = OutStream( + session, + iopub_thread, + "stdout", + isatty=True, + watchfd=False, + ) + save_stdout = sys.stdout + assert iopub_thread._event_pipes == {} + with stream, mock.patch.object(sys, "stdout", stream), ThreadPoolExecutor(1) as pool: + pool.submit(print, "x").result() + pool_thread = pool.submit(threading.current_thread).result() + assert list(iopub_thread._event_pipes) == [pool_thread] + + # run gc once in the iopub thread + f: Future = Future() + + async def run_gc(): + try: + await iopub_thread._event_pipe_gc() + except Exception as e: + f.set_exception(e) + else: + f.set_result(None) + + iopub_thread.io_loop.add_callback(run_gc) + # wait for call to finish in iopub thread + f.result() + assert iopub_thread._event_pipes == {} + + def subprocess_test_echo_watch(): # handshake Pub subscription session = Session(key=b'abc') From 04eb1b99b4f0387ab352cc1ad092eeaf7c49e001 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 12 Jun 2023 20:23:25 +0000 Subject: [PATCH 0999/1195] Publish 6.23.2 SHA256 hashes: ipykernel-6.23.2-py3-none-any.whl: 7ccb6e2d32fd958c21453db494c914f3474908a2fdefd99ab548a5375b548d1f ipykernel-6.23.2.tar.gz: fcfb67c5b504aa1bfcda1c5b3716636239e0f7b9290958f1c558c79b4c0e7ed5 --- CHANGELOG.md | 21 +++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d599e576..d8850297c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ +## 6.23.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.1...112ca66da0ee8156b983094b2c8e2926ed63cfcb)) + +### Bugs fixed + +- Avoid ResourceWarning on implicitly closed event pipe sockets [#1125](https://github.com/ipython/ipykernel/pull/1125) ([@minrk](https://github.com/minrk)) +- fix: protect stdout/stderr restoration in `InProcessKernel._redirected_io` [#1122](https://github.com/ipython/ipykernel/pull/1122) ([@charles-cooper](https://github.com/charles-cooper)) + +### Maintenance and upkeep improvements + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-05-15&to=2023-06-12&type=c)) + +[@charles-cooper](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Acharles-cooper+updated%3A2023-05-15..2023-06-12&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2023-05-15..2023-06-12&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-05-15..2023-06-12&type=Issues) + + + ## 6.23.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.0...d63c33afb9872f2781997b2428d7e9e0c1d23d41)) @@ -20,8 +39,6 @@ [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2023-05-08..2023-05-15&type=Issues) - - ## 6.23.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.22.0...3dd6dc9712ff6eb0a53cf79969dcefa0ba1b086e)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 61c2db121..3784c55eb 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.23.1" +__version__ = "6.23.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From ea3e6479aca70f87282ec0b60412f2cfba59eb35 Mon Sep 17 00:00:00 2001 From: Xie Zejian Date: Fri, 23 Jun 2023 18:31:14 +0800 Subject: [PATCH 1000/1195] fix: check existence of connection_file before writing (#1127) Co-authored-by: Steven Silvester --- ipykernel/eventloops.py | 2 +- ipykernel/inprocess/tests/test_kernelmanager.py | 3 +++ ipykernel/kernelapp.py | 3 +++ ipykernel/tests/test_debugger.py | 4 ++-- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 8bf0996d7..3230d6a65 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -429,7 +429,7 @@ async def close_loop(): close_loop() elif not loop.is_closed(): - loop.run_until_complete(close_loop) # type:ignore[call-overload] + loop.run_until_complete(close_loop) # type:ignore loop.close() diff --git a/ipykernel/inprocess/tests/test_kernelmanager.py b/ipykernel/inprocess/tests/test_kernelmanager.py index 850f543ce..2b6d5faa0 100644 --- a/ipykernel/inprocess/tests/test_kernelmanager.py +++ b/ipykernel/inprocess/tests/test_kernelmanager.py @@ -3,6 +3,8 @@ import unittest +from flaky import flaky + from ipykernel.inprocess.manager import InProcessKernelManager # ----------------------------------------------------------------------------- @@ -18,6 +20,7 @@ def tearDown(self): if self.km.has_kernel: self.km.shutdown_kernel() + @flaky def test_interface(self): """Does the in-process kernel manager implement the basic KM interface?""" km = self.km diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 9628c7d53..922db6347 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -260,6 +260,9 @@ def _bind_socket(self, s, port): def write_connection_file(self): """write connection info to JSON file""" cf = self.abs_connection_file + if os.path.exists(cf): + self.log.debug("Connection file %s already exists", cf) + return self.log.debug("Writing connection file: %s", cf) write_connection_file( cf, diff --git a/ipykernel/tests/test_debugger.py b/ipykernel/tests/test_debugger.py index c9071296c..c46064425 100644 --- a/ipykernel/tests/test_debugger.py +++ b/ipykernel/tests/test_debugger.py @@ -121,7 +121,7 @@ def test_set_breakpoints(kernel_with_debug): assert reply["body"]["breakpoints"][0]["source"]["path"] == source r = wait_for_debug_request(kernel_with_debug, "debugInfo") - assert source in map(lambda b: b["source"], r["body"]["breakpoints"]) # type:ignore # noqa + assert source in map(lambda b: b["source"], r["body"]["breakpoints"]) # noqa r = wait_for_debug_request(kernel_with_debug, "configurationDone") assert r["success"] @@ -208,7 +208,7 @@ def test_rich_inspect_not_at_breakpoint(kernel_with_debug): get_reply(kernel_with_debug, msg_id) r = wait_for_debug_request(kernel_with_debug, "inspectVariables") - assert var_name in list(map(lambda v: v["name"], r["body"]["variables"])) # type:ignore # noqa + assert var_name in list(map(lambda v: v["name"], r["body"]["variables"])) # noqa reply = wait_for_debug_request( kernel_with_debug, From 99b2ec4f4e29e214128b860cb9678e9bd275d959 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Fri, 23 Jun 2023 11:00:27 +0000 Subject: [PATCH 1001/1195] Publish 6.23.3 SHA256 hashes: ipykernel-6.23.3-py3-none-any.whl: bc00662dc44d4975b668cdb5fefb725e38e9d8d6e28441a519d043f38994922d ipykernel-6.23.3.tar.gz: dd4e18116357f36a1e459b3768412371bee764c51844cbf25c4ed1eb9cae4a54 --- CHANGELOG.md | 18 ++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8850297c..0802a11f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.23.3 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.2...ea3e6479aca70f87282ec0b60412f2cfba59eb35)) + +### Bugs fixed + +- Check existence of connection_file before writing [#1127](https://github.com/ipython/ipykernel/pull/1127) ([@fecet](https://github.com/fecet)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-06-12&to=2023-06-23&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-06-12..2023-06-23&type=Issues) | [@fecet](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afecet+updated%3A2023-06-12..2023-06-23&type=Issues) + + + ## 6.23.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.1...112ca66da0ee8156b983094b2c8e2926ed63cfcb)) @@ -19,8 +35,6 @@ [@charles-cooper](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Acharles-cooper+updated%3A2023-05-15..2023-06-12&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2023-05-15..2023-06-12&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-05-15..2023-06-12&type=Issues) - - ## 6.23.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.0...d63c33afb9872f2781997b2428d7e9e0c1d23d41)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 3784c55eb..05666cd2f 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.23.2" +__version__ = "6.23.3" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From a18f0074dfe3ee2044fbe94be96450a14ce46803 Mon Sep 17 00:00:00 2001 From: Boyuan Deng <40481626+dby-tmwctw@users.noreply.github.com> Date: Tue, 27 Jun 2023 02:16:17 -0700 Subject: [PATCH 1002/1195] Let get_parent decide the channel to get parent header (#1128) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/comm/comm.py | 9 +-------- ipykernel/ipkernel.py | 1 - ipykernel/kernelbase.py | 19 +++++++++++++++---- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 6825c054d..b8f4586f5 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -3,7 +3,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import threading import uuid from typing import Optional from warnings import warn @@ -12,7 +11,6 @@ import traitlets.config from traitlets import Bool, Bytes, Instance, Unicode, default -from ipykernel.control import CONTROL_THREAD_NAME from ipykernel.jsonutil import json_clean from ipykernel.kernelbase import Kernel @@ -32,11 +30,6 @@ def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): metadata = {} if metadata is None else metadata content = json_clean(dict(data=data, comm_id=self.comm_id, **keys)) - if threading.current_thread().name == CONTROL_THREAD_NAME: - channel_from_which_to_get_parent_header = "control" - else: - channel_from_which_to_get_parent_header = "shell" - if self.kernel is None: self.kernel = Kernel.instance() @@ -45,7 +38,7 @@ def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): msg_type, content, metadata=json_clean(metadata), - parent=self.kernel.get_parent(channel_from_which_to_get_parent_header), + parent=self.kernel.get_parent(), ident=self.topic, buffers=buffers, ) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 2952f788a..c1c79e942 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -675,7 +675,6 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): "error", reply_content, ident=self._topic("error"), - channel="shell", ) self.log.info("Exception in apply request:\n%s", "\n".join(reply_content["traceback"])) result_buf = [] diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index fd3a138df..8b45156a7 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -11,6 +11,7 @@ import os import socket import sys +import threading import time import typing as t import uuid @@ -19,6 +20,8 @@ from functools import partial from signal import SIGINT, SIGTERM, Signals, default_int_handler, signal +from .control import CONTROL_THREAD_NAME + if sys.platform != "win32": from signal import SIGKILL else: @@ -187,7 +190,7 @@ def _parent_header(self): DeprecationWarning, stacklevel=2, ) - return self.get_parent(channel="shell") + return self.get_parent() # Time to sleep after flushing the stdout/err buffers in each execute # cycle. While this introduces a hard limit on the minimal latency of the @@ -598,7 +601,7 @@ def _publish_debug_event(self, event): self.iopub_socket, "debug_event", event, - parent=self.get_parent("control"), + parent=self.get_parent(), ident=self._topic("debug_event"), ) @@ -614,7 +617,7 @@ def set_parent(self, ident, parent, channel="shell"): self._parent_ident[channel] = ident self._parents[channel] = parent - def get_parent(self, channel="shell"): + def get_parent(self, channel=None): """Get the parent request associated with a channel. .. versionadded:: 6 @@ -629,6 +632,14 @@ def get_parent(self, channel="shell"): message : dict the parent message for the most recent request on the channel. """ + + if channel is None: + # If a channel is not specified, get information from current thread + if threading.current_thread().name == CONTROL_THREAD_NAME: + channel = "control" + else: + channel = "shell" + return self._parents.get(channel, {}) def send_response( @@ -641,7 +652,7 @@ def send_response( track=False, header=None, metadata=None, - channel="shell", + channel=None, ): """Send a response to the message we're currently processing. From 0c1db099a32c4cb28bfb4b3508bb808d8b4092e7 Mon Sep 17 00:00:00 2001 From: Ariel Eizenberg Date: Thu, 29 Jun 2023 13:19:28 +0300 Subject: [PATCH 1003/1195] Bugfix: binary stdout/stderr handling (#1129) Co-authored-by: Ariel Eizenberg Co-authored-by: Steven Silvester --- ipykernel/iostream.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index a1e138452..5c2818dcc 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -29,6 +29,8 @@ MASTER = 0 CHILD = 1 +PIPE_BUFFER_SIZE = 1000 + # ----------------------------------------------------------------------------- # IO classes # ----------------------------------------------------------------------------- @@ -367,11 +369,11 @@ def _watch_pipe_fd(self): """ try: - bts = os.read(self._fid, 1000) + bts = os.read(self._fid, PIPE_BUFFER_SIZE) while bts and self._should_watch: - self.write(bts.decode()) + self.write(bts.decode(errors='replace')) os.write(self._original_stdstream_copy, bts) - bts = os.read(self._fid, 1000) + bts = os.read(self._fid, PIPE_BUFFER_SIZE) except Exception: self._exc = sys.exc_info() From 1270d5a6e17845e6b22361e43325dd2de59d6392 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 3 Jul 2023 13:54:19 +0000 Subject: [PATCH 1004/1195] Publish 6.24.0 SHA256 hashes: ipykernel-6.24.0-py3-none-any.whl: 2f5fffc7ad8f1fd5aadb4e171ba9129d9668dbafa374732cf9511ada52d6547f ipykernel-6.24.0.tar.gz: 29cea0a716b1176d002a61d0b0c851f34536495bc4ef7dd0222c88b41b816123 --- CHANGELOG.md | 22 ++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0802a11f3..2c8c66897 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ +## 6.24.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.3...0c1db099a32c4cb28bfb4b3508bb808d8b4092e7)) + +### New features added + +- Let get_parent decide the channel to get parent header [#1128](https://github.com/ipython/ipykernel/pull/1128) ([@dby-tmwctw](https://github.com/dby-tmwctw)) + +### Bugs fixed + +- Bugfix: binary stdout/stderr handling [#1129](https://github.com/ipython/ipykernel/pull/1129) ([@arieleiz](https://github.com/arieleiz)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-06-23&to=2023-07-03&type=c)) + +[@arieleiz](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aarieleiz+updated%3A2023-06-23..2023-07-03&type=Issues) | [@dby-tmwctw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adby-tmwctw+updated%3A2023-06-23..2023-07-03&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2023-06-23..2023-07-03&type=Issues) + + + ## 6.23.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.2...ea3e6479aca70f87282ec0b60412f2cfba59eb35)) @@ -16,8 +36,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-06-12..2023-06-23&type=Issues) | [@fecet](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afecet+updated%3A2023-06-12..2023-06-23&type=Issues) - - ## 6.23.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.1...112ca66da0ee8156b983094b2c8e2926ed63cfcb)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 05666cd2f..2d8ba0432 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.23.3" +__version__ = "6.24.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 9433579ac5a3352610e5fd4eae42a9f97afd13fb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 8 Jul 2023 13:14:08 -0500 Subject: [PATCH 1005/1195] [pre-commit.ci] pre-commit autoupdate (#1131) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 6 +++--- ipykernel_launcher.py | 2 +- pyproject.toml | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 82b55d101..4d8bc369e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.23.1 + rev: 0.23.2 hooks: - id: check-github-workflows @@ -35,8 +35,8 @@ repos: hooks: - id: black - - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.270 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.0.276 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel_launcher.py b/ipykernel_launcher.py index 6290c7e52..49aa2651a 100644 --- a/ipykernel_launcher.py +++ b/ipykernel_launcher.py @@ -9,7 +9,7 @@ if __name__ == "__main__": # Remove the CWD from sys.path while we load stuff. # This is added back by InteractiveShellApp.init_path() - if sys.path[0] == "": # noqa + if sys.path[0] == "": del sys.path[0] from ipykernel import kernelapp as app diff --git a/pyproject.toml b/pyproject.toml index 8ba499ca2..354f8438b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -278,6 +278,8 @@ ignore = [ "SIM105", # S110 `try`-`except`-`pass` detected "S110", + # RUF012 Mutable class attributes should be annotated with `typing.ClassVar` + "RUF012", ] unfixable = [ # Don't touch print statements From 1abb01943b6cd056e619e190e3c9267c805c32e0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 16 Jul 2023 17:00:57 -0500 Subject: [PATCH 1006/1195] Clean up lint (#1134) --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 2 +- ipykernel/debugger.py | 2 +- ipykernel/pickleutil.py | 2 +- pyproject.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8781622d5..79951fc7d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -137,7 +137,7 @@ jobs: pip freeze - name: Run the tests - timeout-minutes: 10 + timeout-minutes: 15 run: pytest -W default -vv || pytest --vv -W default --lf test_miniumum_versions: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d8bc369e..7d76f02c5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,7 +36,7 @@ repos: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.276 + rev: v0.0.278 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index b59ad476f..0fe68afb3 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -657,7 +657,7 @@ async def richInspectVariables(self, message): } ) if reply["success"]: - repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) + repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) # noqa[S307] body = { "data": repr_data, diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 80f0c9a0b..ec58c6795 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -179,7 +179,7 @@ def get_object(self, g=None): if g is None: g = {} - return eval(self.name, g) + return eval(self.name, g) # noqa[S307] class CannedCell(CannedObject): diff --git a/pyproject.toml b/pyproject.toml index 354f8438b..69c93d853 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,7 +117,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.270"] +dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.278"] detached = true [tool.hatch.envs.lint.scripts] style = [ From 58e9d150acb661c9e9440c1d011edb840eb510d1 Mon Sep 17 00:00:00 2001 From: Maarten Breddels Date: Thu, 20 Jul 2023 14:34:11 +0200 Subject: [PATCH 1007/1195] feat: let display hook handle clear_output (#1135) --- ipykernel/zmqshell.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 9455b8f74..61c5ee69b 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -144,11 +144,17 @@ def clear_output(self, wait=False): """ content = dict(wait=wait) self._flush_streams() + msg = self.session.msg("clear_output", json_clean(content), parent=self.parent_header) + + # see publish() for details on how this works + for hook in self._hooks: + msg = hook(msg) + if msg is None: + return + self.session.send( self.pub_socket, - "clear_output", - content, - parent=self.parent_header, + msg, ident=self.topic, ) From 09c3c359addf60e26078207990ad2ca932cf2613 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Tue, 25 Jul 2023 10:11:22 -0600 Subject: [PATCH 1008/1195] Merge connection info into existing connection file if it already exists (#1133) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/kernelapp.py | 26 +++++---- ipykernel/tests/test_ipkernel_direct.py | 2 +- ipykernel/tests/test_kernelapp.py | 71 +++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 10 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 922db6347..da6d19656 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -24,7 +24,6 @@ ) from IPython.core.profiledir import ProfileDir from IPython.core.shellapp import InteractiveShellApp, shell_aliases, shell_flags -from jupyter_client import write_connection_file from jupyter_client.connect import ConnectionFileMixin from jupyter_client.session import Session, session_aliases, session_flags from jupyter_core.paths import jupyter_runtime_dir @@ -44,10 +43,11 @@ from traitlets.utils.importstring import import_item from zmq.eventloop.zmqstream import ZMQStream -from .control import ControlThread -from .heartbeat import Heartbeat +from .connect import get_connection_info, write_connection_file # local imports +from .control import ControlThread +from .heartbeat import Heartbeat from .iostream import IOPubThread from .ipkernel import IPythonKernel from .parentpoller import ParentPollerUnix, ParentPollerWindows @@ -260,12 +260,7 @@ def _bind_socket(self, s, port): def write_connection_file(self): """write connection info to JSON file""" cf = self.abs_connection_file - if os.path.exists(cf): - self.log.debug("Connection file %s already exists", cf) - return - self.log.debug("Writing connection file: %s", cf) - write_connection_file( - cf, + connection_info = dict( ip=self.ip, key=self.session.key, transport=self.transport, @@ -275,6 +270,19 @@ def write_connection_file(self): iopub_port=self.iopub_port, control_port=self.control_port, ) + if os.path.exists(cf): + # If the file exists, merge our info into it. For example, if the + # original file had port number 0, we update with the actual port + # used. + existing_connection_info = get_connection_info(cf, unpack=True) + connection_info = dict(existing_connection_info, **connection_info) + if connection_info == existing_connection_info: + self.log.debug("Connection file %s with current information already exists", cf) + return + + self.log.debug("Writing connection file: %s", cf) + + write_connection_file(cf, **connection_info) def cleanup_connection_file(self): """Clean up our connection file.""" diff --git a/ipykernel/tests/test_ipkernel_direct.py b/ipykernel/tests/test_ipkernel_direct.py index 20e92a402..b0dbb01f5 100644 --- a/ipykernel/tests/test_ipkernel_direct.py +++ b/ipykernel/tests/test_ipkernel_direct.py @@ -20,7 +20,7 @@ class user_mod: __dict__ = {} -async def test_properities(ipkernel: IPythonKernel) -> None: +async def test_properties(ipkernel: IPythonKernel) -> None: ipkernel.user_module = user_mod() ipkernel.user_ns = {} diff --git a/ipykernel/tests/test_kernelapp.py b/ipykernel/tests/test_kernelapp.py index 9a7b1f92e..da38777d0 100644 --- a/ipykernel/tests/test_kernelapp.py +++ b/ipykernel/tests/test_kernelapp.py @@ -1,13 +1,17 @@ +import json import os import threading import time from unittest.mock import patch import pytest +from jupyter_core.paths import secure_write +from traitlets.config.loader import Config from ipykernel.kernelapp import IPKernelApp from .conftest import MockKernel +from .utils import TemporaryWorkingDirectory try: import trio @@ -47,6 +51,73 @@ def trigger_stop(): app.close() +@pytest.mark.skipif(os.name == "nt", reason="permission errors on windows") +def test_merge_connection_file(): + cfg = Config() + with TemporaryWorkingDirectory() as d: + cfg.ProfileDir.location = d + cf = os.path.join(d, "kernel.json") + initial_connection_info = { + "ip": "*", + "transport": "tcp", + "shell_port": 0, + "hb_port": 0, + "iopub_port": 0, + "stdin_port": 0, + "control_port": 53555, + "key": "abc123", + "signature_scheme": "hmac-sha256", + "kernel_name": "My Kernel", + } + # We cannot use connect.write_connection_file since + # it replaces port number 0 with a random port + # and we want IPKernelApp to do that replacement. + with secure_write(cf) as f: + json.dump(initial_connection_info, f) + assert os.path.exists(cf) + + app = IPKernelApp(config=cfg, connection_file=cf) + + # Calling app.initialize() does not work in the test, so we call the relevant functions that initialize() calls + # We must pass in an empty argv, otherwise the default is to try to parse the test runner's argv + super(IPKernelApp, app).initialize(argv=[""]) + app.init_connection_file() + app.init_sockets() + app.init_heartbeat() + app.write_connection_file() + + # Initialize should have merged the actual connection info + # with the connection info in the file + assert cf == app.abs_connection_file + assert os.path.exists(cf) + + with open(cf) as f: + new_connection_info = json.load(f) + + # ports originally set as 0 have been replaced + for port in ("shell", "hb", "iopub", "stdin"): + key = f"{port}_port" + # We initially had the port as 0 + assert initial_connection_info[key] == 0 + # the port is not 0 now + assert new_connection_info[key] > 0 + # the port matches the port the kernel actually used + assert new_connection_info[key] == getattr(app, key), f"{key}" + del new_connection_info[key] + del initial_connection_info[key] + + # The wildcard ip address was also replaced + assert new_connection_info["ip"] != "*" + del new_connection_info["ip"] + del initial_connection_info["ip"] + + # everything else in the connection file is the same + assert initial_connection_info == new_connection_info + + app.close() + os.remove(cf) + + @pytest.mark.skipif(trio is None, reason="requires trio") def test_trio_loop(): app = IPKernelApp(trio_loop=True) From bed7ee4721c8295d61940aae60f2b78d172d2f33 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 25 Jul 2023 16:19:33 +0000 Subject: [PATCH 1009/1195] Publish 6.25.0 SHA256 hashes: ipykernel-6.25.0-py3-none-any.whl: f0042e867ac3f6bca1679e6a88cbd6a58ed93a44f9d0866aecde6efe8de76659 ipykernel-6.25.0.tar.gz: e342ce84712861be4b248c4a73472be4702c1b0dd77448bfd6bcfb3af9d5ddf9 --- CHANGELOG.md | 26 ++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c8c66897..b320ad7f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ +## 6.25.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.24.0...09c3c359addf60e26078207990ad2ca932cf2613)) + +### Enhancements made + +- feat: let display hook handle clear_output [#1135](https://github.com/ipython/ipykernel/pull/1135) ([@maartenbreddels](https://github.com/maartenbreddels)) + +### Bugs fixed + +- Merge connection info into existing connection file if it already exists [#1133](https://github.com/ipython/ipykernel/pull/1133) ([@jasongrout](https://github.com/jasongrout)) + +### Maintenance and upkeep improvements + +- Clean up lint [#1134](https://github.com/ipython/ipykernel/pull/1134) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-07-03&to=2023-07-25&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-07-03..2023-07-25&type=Issues) | [@fecet](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afecet+updated%3A2023-07-03..2023-07-25&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2023-07-03..2023-07-25&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2023-07-03..2023-07-25&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-07-03..2023-07-25&type=Issues) + + + ## 6.24.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.3...0c1db099a32c4cb28bfb4b3508bb808d8b4092e7)) @@ -20,8 +44,6 @@ [@arieleiz](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aarieleiz+updated%3A2023-06-23..2023-07-03&type=Issues) | [@dby-tmwctw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adby-tmwctw+updated%3A2023-06-23..2023-07-03&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2023-06-23..2023-07-03&type=Issues) - - ## 6.23.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.2...ea3e6479aca70f87282ec0b60412f2cfba59eb35)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 2d8ba0432..afdab3d35 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.24.0" +__version__ = "6.25.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From c8517461f434a72fb4e30fc03b5949aca4c5af52 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 1 Aug 2023 04:48:32 -0500 Subject: [PATCH 1010/1195] [pre-commit.ci] pre-commit autoupdate (#1138) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 6 +++--- ipykernel/debugger.py | 2 +- ipykernel/pickleutil.py | 6 +++--- pyproject.toml | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7d76f02c5..b2d05b23e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,7 +21,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.23.2 + rev: 0.23.3 hooks: - id: check-github-workflows @@ -31,12 +31,12 @@ repos: - id: mdformat - repo: https://github.com/psf/black - rev: 23.3.0 + rev: 23.7.0 hooks: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.278 + rev: v0.0.281 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 0fe68afb3..443177642 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -657,7 +657,7 @@ async def richInspectVariables(self, message): } ) if reply["success"]: - repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) # noqa[S307] + repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) # noqa: S307 body = { "data": repr_data, diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index ec58c6795..c6b71096e 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -179,7 +179,7 @@ def get_object(self, g=None): if g is None: g = {} - return eval(self.name, g) # noqa[S307] + return eval(self.name, g) # noqa: S307 class CannedCell(CannedObject): @@ -278,9 +278,9 @@ def __init__(self, obj): self.shape = obj.shape self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str self.pickled = False - if sum(obj.shape) == 0: # noqa + if sum(obj.shape) == 0: self.pickled = True - elif obj.dtype == "O": # noqa + elif obj.dtype == "O": # can't handle object dtype with buffer approach self.pickled = True elif obj.dtype.fields and any(dt == "O" for dt, sz in obj.dtype.fields.values()): diff --git a/pyproject.toml b/pyproject.toml index 69c93d853..2832ea330 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,7 +117,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.278"] +dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.281"] detached = true [tool.hatch.envs.lint.scripts] style = [ From 18e54f31725d6645dd71a8749c9e1eb28281f804 Mon Sep 17 00:00:00 2001 From: Vaishnavi Gupta Date: Fri, 4 Aug 2023 07:06:30 -0700 Subject: [PATCH 1011/1195] Modifying debugger to return the same breakpoints in 'debugInfo' response as 'setBreakpoints' (#1140) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/debugger.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 443177642..1c9b94395 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -473,7 +473,15 @@ async def setBreakpoints(self, message): """Handle a set breakpoints message.""" source = message["arguments"]["source"]["path"] self.breakpoint_list[source] = message["arguments"]["breakpoints"] - return await self._forward_message(message) + message_response = await self._forward_message(message) + # debugpy can set breakpoints on different lines than the ones requested, + # so we want to record the breakpoints that were actually added + if "success" in message_response and message_response["success"]: + self.breakpoint_list[source] = [ + {"line": breakpoint["line"]} + for breakpoint in message_response["body"]["breakpoints"] + ] + return message_response async def source(self, message): """Handle a source message.""" From 6bf3fe9e44f1caf4bc371f700ac0c2e9c9c3bd84 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 7 Aug 2023 13:45:24 +0000 Subject: [PATCH 1012/1195] Publish 6.25.1 SHA256 hashes: ipykernel-6.25.1-py3-none-any.whl: c8a2430b357073b37c76c21c52184db42f6b4b0e438e1eb7df3c4440d120497c ipykernel-6.25.1.tar.gz: 050391364c0977e768e354bdb60cbbfbee7cbb943b1af1618382021136ffd42f --- CHANGELOG.md | 20 ++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b320ad7f1..4535e3e5c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ +## 6.25.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.25.0...18e54f31725d6645dd71a8749c9e1eb28281f804)) + +### Bugs fixed + +- Modifying debugger to return the same breakpoints in 'debugInfo' response as 'setBreakpoints' [#1140](https://github.com/ipython/ipykernel/pull/1140) ([@vaishnavi17](https://github.com/vaishnavi17)) + +### Maintenance and upkeep improvements + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-07-25&to=2023-08-07&type=c)) + +[@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-07-25..2023-08-07&type=Issues) | [@vaishnavi17](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Avaishnavi17+updated%3A2023-07-25..2023-08-07&type=Issues) + + + ## 6.25.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.24.0...09c3c359addf60e26078207990ad2ca932cf2613)) @@ -24,8 +42,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-07-03..2023-07-25&type=Issues) | [@fecet](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afecet+updated%3A2023-07-03..2023-07-25&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2023-07-03..2023-07-25&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2023-07-03..2023-07-25&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-07-03..2023-07-25&type=Issues) - - ## 6.24.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.23.3...0c1db099a32c4cb28bfb4b3508bb808d8b4092e7)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index afdab3d35..6eaa31247 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.25.0" +__version__ = "6.25.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 44c175931be7c394a2a673fe1fafd1fbc9c0d910 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sat, 2 Sep 2023 12:05:17 +0200 Subject: [PATCH 1013/1195] Don't call QApplication.setQuitOnLastWindowClosed(False). (#1142) --- ipykernel/eventloops.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 3230d6a65..4082f4cf7 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -535,13 +535,10 @@ def make_qt_app_for_kernel(gui, kernel): set_qt_api_env_from_gui(gui) # This import is guaranteed to work now: - from IPython.external.qt_for_kernel import QtCore, QtGui + from IPython.external.qt_for_kernel import QtCore from IPython.lib.guisupport import get_app_qt4 kernel.app = get_app_qt4([" "]) - if isinstance(kernel.app, QtGui.QApplication): - kernel.app.setQuitOnLastWindowClosed(False) - kernel.app.qt_event_loop = QtCore.QEventLoop(kernel.app) From ca79e2e9057aed0946ebed98d96db8014bbe8ffa Mon Sep 17 00:00:00 2001 From: Min RK Date: Sat, 2 Sep 2023 22:11:24 +0200 Subject: [PATCH 1014/1195] avoid starting IOPub background thread after it's been stopped (#1137) --- ipykernel/iostream.py | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 5c2818dcc..74e65e4d9 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -57,6 +57,7 @@ def __init__(self, socket, pipe=False): piped from subprocesses. """ self.socket = socket + self._stopped = False self.background_socket = BackgroundSocket(self) self._master_pid = os.getpid() self._pipe_flag = pipe @@ -83,13 +84,21 @@ def _start_event_gc(): self._event_pipe_gc_task = asyncio.ensure_future(self._run_event_pipe_gc()) self.io_loop.run_sync(_start_event_gc) - self.io_loop.start() + + if not self._stopped: + # avoid race if stop called before start thread gets here + # probably only comes up in tests + self.io_loop.start() + if self._event_pipe_gc_task is not None: # cancel gc task to avoid pending task warnings async def _cancel(): self._event_pipe_gc_task.cancel() # type:ignore - self.io_loop.run_sync(_cancel) + try: + self.io_loop.run_sync(_cancel) + except TimeoutError: + pass self.io_loop.close(all_fds=True) def _setup_event_pipe(self): @@ -219,10 +228,16 @@ def start(self): def stop(self): """Stop the IOPub thread""" + self._stopped = True if not self.thread.is_alive(): return self.io_loop.add_callback(self.io_loop.stop) - self.thread.join() + + self.thread.join(timeout=30) + if self.thread.is_alive(): + # avoid infinite hang if stop fails + msg = "IOPub thread did not terminate in 30 seconds" + raise TimeoutError(msg) # close *all* event pipes, created in any thread # event pipes can only be used from other threads while self.thread.is_alive() # so after thread.join, this should be safe From 9d3f7aecc4fe68f14ebcc4dad4b65b19676e820e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 4 Sep 2023 10:23:28 -0500 Subject: [PATCH 1015/1195] Make iostream shutdown more robust (#1143) --- ipykernel/iostream.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 74e65e4d9..87834e26b 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -95,10 +95,11 @@ def _start_event_gc(): async def _cancel(): self._event_pipe_gc_task.cancel() # type:ignore - try: + if not self._stopped: self.io_loop.run_sync(_cancel) - except TimeoutError: - pass + else: + self._event_pipe_gc_task.cancel() + self.io_loop.close(all_fds=True) def _setup_event_pipe(self): From 217da9f76a093cc709e543262a2f98d6dbc9ac11 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 4 Sep 2023 16:37:15 +0000 Subject: [PATCH 1016/1195] Publish 6.25.2 SHA256 hashes: ipykernel-6.25.2-py3-none-any.whl: 2e2ee359baba19f10251b99415bb39de1e97d04e1fab385646f24f0596510b77 ipykernel-6.25.2.tar.gz: f468ddd1f17acb48c8ce67fcfa49ba6d46d4f9ac0438c1f441be7c3d1372230b --- CHANGELOG.md | 20 ++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4535e3e5c..f0a0d1444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,24 @@ +## 6.25.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.25.1...9d3f7aecc4fe68f14ebcc4dad4b65b19676e820e)) + +### Bugs fixed + +- Make iostream shutdown more robust [#1143](https://github.com/ipython/ipykernel/pull/1143) ([@blink1073](https://github.com/blink1073)) +- Don't call QApplication.setQuitOnLastWindowClosed(False). [#1142](https://github.com/ipython/ipykernel/pull/1142) ([@anntzer](https://github.com/anntzer)) +- Avoid starting IOPub background thread after it's been stopped [#1137](https://github.com/ipython/ipykernel/pull/1137) ([@minrk](https://github.com/minrk)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-08-07&to=2023-09-04&type=c)) + +[@anntzer](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aanntzer+updated%3A2023-08-07..2023-09-04&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-08-07..2023-09-04&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2023-08-07..2023-09-04&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2023-08-07..2023-09-04&type=Issues) + + + ## 6.25.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.25.0...18e54f31725d6645dd71a8749c9e1eb28281f804)) @@ -18,8 +36,6 @@ [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-07-25..2023-08-07&type=Issues) | [@vaishnavi17](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Avaishnavi17+updated%3A2023-07-25..2023-08-07&type=Issues) - - ## 6.25.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.24.0...09c3c359addf60e26078207990ad2ca932cf2613)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 6eaa31247..c09e05b4a 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.25.1" +__version__ = "6.25.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 3e69024ef05bf5c1409ffc2f3b2d6ac0b460c598 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 21:16:10 -0500 Subject: [PATCH 1017/1195] Bump actions/checkout from 3 to 4 (#1144) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 20 ++++++++++---------- .github/workflows/downstream.yml | 14 +++++++------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 79951fc7d..9701cdf81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: python-version: "3.8" steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -70,7 +70,7 @@ jobs: needs: - build steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/report-coverage@v1 with: fail_under: 80 @@ -79,7 +79,7 @@ jobs: name: Test Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Run Linters run: | @@ -91,7 +91,7 @@ jobs: check_release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 with: @@ -100,7 +100,7 @@ jobs: test_docs: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Build API docs run: | @@ -122,7 +122,7 @@ jobs: python-version: ["3.9"] steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -145,7 +145,7 @@ jobs: timeout-minutes: 20 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: @@ -165,7 +165,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: @@ -179,7 +179,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1 @@ -195,7 +195,7 @@ jobs: link_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index f4ee5cf91..b63f64d9f 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -58,7 +58,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -73,7 +73,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -90,7 +90,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: @@ -122,7 +122,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v4 with: From c24b252dc4fc81e6cf6354f8d64ea06ed1ce3496 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 5 Sep 2023 05:06:17 -0500 Subject: [PATCH 1018/1195] [pre-commit.ci] pre-commit autoupdate (#1145) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 6 +++--- ipykernel/tests/test_io.py | 1 + ipykernel/tests/test_kernel.py | 2 +- pyproject.toml | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b2d05b23e..0a955ea56 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -21,12 +21,12 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.23.3 + rev: 0.26.3 hooks: - id: check-github-workflows - repo: https://github.com/executablebooks/mdformat - rev: 0.7.16 + rev: 0.7.17 hooks: - id: mdformat @@ -36,7 +36,7 @@ repos: - id: black - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.281 + rev: v0.0.287 hooks: - id: ruff args: ["--fix"] diff --git a/ipykernel/tests/test_io.py b/ipykernel/tests/test_io.py index 404657cbb..fee1e6020 100644 --- a/ipykernel/tests/test_io.py +++ b/ipykernel/tests/test_io.py @@ -216,6 +216,7 @@ def test_echo_watch(ctx): ], env=env, capture_output=True, + check=True, text=True, timeout=10, ) diff --git a/ipykernel/tests/test_kernel.py b/ipykernel/tests/test_kernel.py index 033ee481d..49fc1c08d 100644 --- a/ipykernel/tests/test_kernel.py +++ b/ipykernel/tests/test_kernel.py @@ -252,7 +252,7 @@ def test_smoke_faulthandler(): def test_help_output(): """ipython kernel --help-all works""" cmd = [sys.executable, "-m", "IPython", "kernel", "--help-all"] - proc = subprocess.run(cmd, timeout=30, capture_output=True) + proc = subprocess.run(cmd, timeout=30, capture_output=True, check=True) assert proc.returncode == 0, proc.stderr assert b"Traceback" not in proc.stderr assert b"Options" in proc.stdout diff --git a/pyproject.toml b/pyproject.toml index 2832ea330..8cb453302 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,7 +117,7 @@ dependencies = ["mypy>=0.990"] test = "mypy --install-types --non-interactive {args:.}" [tool.hatch.envs.lint] -dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.281"] +dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.287"] detached = true [tool.hatch.envs.lint.scripts] style = [ From 83ab7d02958f8134ddabf9d2722c222eaab041d4 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 13 Sep 2023 20:52:52 -0500 Subject: [PATCH 1019/1195] Use sp-repo-review (#1146) --- .github/workflows/ci.yml | 4 +- .pre-commit-config.yaml | 38 ++++++++++++++++- examples/embedding/inprocess_qtconsole.py | 2 +- examples/embedding/inprocess_terminal.py | 2 +- ipykernel/debugger.py | 4 +- ipykernel/eventloops.py | 10 +++-- ipykernel/inprocess/channels.py | 2 +- ipykernel/inprocess/client.py | 2 +- ipykernel/inprocess/ipkernel.py | 2 +- ipykernel/iostream.py | 8 ++-- ipykernel/ipkernel.py | 6 +-- ipykernel/kernelapp.py | 10 ++--- ipykernel/parentpoller.py | 2 +- ipykernel/trio_runner.py | 2 +- pyproject.toml | 42 +++++++++++++++---- {ipykernel/tests => tests}/__init__.py | 0 {ipykernel/tests => tests}/_asyncio_utils.py | 0 {ipykernel/tests => tests}/conftest.py | 0 .../tests => tests/inprocess}/__init__.py | 0 .../tests => tests/inprocess}/test_kernel.py | 3 +- .../inprocess}/test_kernelmanager.py | 0 {ipykernel/tests => tests}/test_async.py | 0 {ipykernel/tests => tests}/test_comm.py | 0 {ipykernel/tests => tests}/test_connect.py | 0 {ipykernel/tests => tests}/test_debugger.py | 12 +++++- .../tests => tests}/test_embed_kernel.py | 0 {ipykernel/tests => tests}/test_eventloop.py | 4 +- {ipykernel/tests => tests}/test_heartbeat.py | 0 {ipykernel/tests => tests}/test_io.py | 2 - .../tests => tests}/test_ipkernel_direct.py | 5 ++- {ipykernel/tests => tests}/test_jsonutil.py | 4 +- {ipykernel/tests => tests}/test_kernel.py | 8 ++-- .../tests => tests}/test_kernel_direct.py | 3 +- {ipykernel/tests => tests}/test_kernelapp.py | 0 {ipykernel/tests => tests}/test_kernelspec.py | 0 .../tests => tests}/test_message_spec.py | 10 +++-- .../tests => tests}/test_parentpoller.py | 3 +- {ipykernel/tests => tests}/test_pickleutil.py | 0 .../tests => tests}/test_start_kernel.py | 4 +- {ipykernel/tests => tests}/test_zmq_shell.py | 0 {ipykernel/tests => tests}/utils.py | 0 41 files changed, 133 insertions(+), 61 deletions(-) rename {ipykernel/tests => tests}/__init__.py (100%) rename {ipykernel/tests => tests}/_asyncio_utils.py (100%) rename {ipykernel/tests => tests}/conftest.py (100%) rename {ipykernel/inprocess/tests => tests/inprocess}/__init__.py (100%) rename {ipykernel/inprocess/tests => tests/inprocess}/test_kernel.py (98%) rename {ipykernel/inprocess/tests => tests/inprocess}/test_kernelmanager.py (100%) rename {ipykernel/tests => tests}/test_async.py (100%) rename {ipykernel/tests => tests}/test_comm.py (100%) rename {ipykernel/tests => tests}/test_connect.py (100%) rename {ipykernel/tests => tests}/test_debugger.py (98%) rename {ipykernel/tests => tests}/test_embed_kernel.py (100%) rename {ipykernel/tests => tests}/test_eventloop.py (97%) rename {ipykernel/tests => tests}/test_heartbeat.py (100%) rename {ipykernel/tests => tests}/test_io.py (99%) rename {ipykernel/tests => tests}/test_ipkernel_direct.py (98%) rename {ipykernel/tests => tests}/test_jsonutil.py (97%) rename {ipykernel/tests => tests}/test_kernel.py (98%) rename {ipykernel/tests => tests}/test_kernel_direct.py (99%) rename {ipykernel/tests => tests}/test_kernelapp.py (100%) rename {ipykernel/tests => tests}/test_kernelspec.py (100%) rename {ipykernel/tests => tests}/test_message_spec.py (98%) rename {ipykernel/tests => tests}/test_parentpoller.py (95%) rename {ipykernel/tests => tests}/test_pickleutil.py (100%) rename {ipykernel/tests => tests}/test_start_kernel.py (96%) rename {ipykernel/tests => tests}/test_zmq_shell.py (100%) rename {ipykernel/tests => tests}/utils.py (100%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9701cdf81..8187aa0d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,8 +81,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Run Linters - run: | + - name: Run Linters + run: | hatch run typing:test hatch run lint:style pipx run interrogate -vv . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0a955ea56..4d68ae2fb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,6 @@ ci: autoupdate_schedule: monthly + autoupdate_commit_msg: "chore: update pre-commit hooks" repos: - repo: https://github.com/pre-commit/pre-commit-hooks @@ -29,14 +30,47 @@ repos: rev: 0.7.17 hooks: - id: mdformat + additional_dependencies: + [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - - repo: https://github.com/psf/black + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.0.2" + hooks: + - id: prettier + types_or: [yaml, html, json] + + - repo: https://github.com/adamchainz/blacken-docs + rev: "1.16.0" + hooks: + - id: blacken-docs + additional_dependencies: [black==23.7.0] + + - repo: https://github.com/psf/black-pre-commit-mirror rev: 23.7.0 hooks: - id: black + - repo: https://github.com/codespell-project/codespell + rev: "v2.2.5" + hooks: + - id: codespell + args: ["-L", "sur,nd"] + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: "v1.10.0" + hooks: + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.0.287 hooks: - id: ruff - args: ["--fix"] + args: ["--fix", "--show-fixes"] + + - repo: https://github.com/scientific-python/cookie + rev: "2023.08.23" + hooks: + - id: sp-repo-review + additional_dependencies: ["repo-review[cli]"] diff --git a/examples/embedding/inprocess_qtconsole.py b/examples/embedding/inprocess_qtconsole.py index c346a97fb..18ce28638 100644 --- a/examples/embedding/inprocess_qtconsole.py +++ b/examples/embedding/inprocess_qtconsole.py @@ -19,7 +19,7 @@ def init_asyncio_patch(): asyncio implementation on Windows Pick the older SelectorEventLoopPolicy on Windows if the known-incompatible default policy is in use. - do this as early as possible to make it a low priority and overrideable + do this as early as possible to make it a low priority and overridable ref: https://github.com/tornadoweb/tornado/issues/2608 FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index 79e11e03a..b644c94af 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -19,7 +19,7 @@ def init_asyncio_patch(): asyncio implementation on Windows Pick the older SelectorEventLoopPolicy on Windows if the known-incompatible default policy is in use. - do this as early as possible to make it a low priority and overrideable + do this as early as possible to make it a low priority and overridable ref: https://github.com/tornadoweb/tornado/issues/2608 FIXME: if/when tornado supports the defaults in asyncio, remove and bump tornado requirement for py38 diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 1c9b94395..90e4f8885 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -40,7 +40,7 @@ raise e -# Required for backwards compatiblity +# Required for backwards compatibility ROUTING_ID = getattr(zmq, "ROUTING_ID", None) or zmq.IDENTITY @@ -644,7 +644,7 @@ async def richInspectVariables(self, message): repr_data = {} repr_metadata = {} if not self.stopped_threads: - # The code did not hit a breakpoint, we use the intepreter + # The code did not hit a breakpoint, we use the interpreter # to get the rich representation of the variable result = get_ipython().user_expressions({var_name: var_name})[var_name] if result.get("status", "error") == "ok": diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 4082f4cf7..ef54f4105 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -429,7 +429,7 @@ async def close_loop(): close_loop() elif not loop.is_closed(): - loop.run_until_complete(close_loop) # type:ignore + loop.run_until_complete(close_loop) # type:ignore[arg-type] loop.close() @@ -566,8 +566,12 @@ def enable_gui(gui, kernel=None): make_qt_app_for_kernel(gui, kernel) loop = loop_map[gui] - if loop and kernel.eventloop is not None and kernel.eventloop is not loop: - msg = "Cannot activate multiple GUI eventloops" + if ( + loop + and kernel.eventloop is not None + and kernel.eventloop is not loop # type:ignore[unreachable] + ): + msg = "Cannot activate multiple GUI eventloops" # type:ignore[unreachable] raise RuntimeError(msg) kernel.eventloop = loop # We set `eventloop`; the function the user chose is executed in `Kernel.enter_eventloop`, thus diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 964015ed3..d3f03fcfd 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -18,7 +18,7 @@ class InProcessChannel: proxy_methods: List[object] = [] def __init__(self, client=None): - """Initialze the channel.""" + """Initialize the channel.""" super().__init__() self.client = client self._is_alive = False diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index ea964ecde..1fa80bc20 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -193,7 +193,7 @@ def _dispatch_to_kernel(self, msg): dispatch_shell = run_sync(kernel.dispatch_shell) dispatch_shell(msg_parts) else: - loop = asyncio.get_event_loop() + loop = asyncio.get_event_loop() # type:ignore[unreachable] loop.run_until_complete(kernel.dispatch_shell(msg_parts)) idents, reply_msg = self.session.recv(stream, copy=False) self.shell_channel.call_handlers_later(reply_msg) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 3f7fcce9e..f3cac4a8c 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -111,7 +111,7 @@ def _input_request(self, prompt, ident, parent, password=False): # Await a response. while self.raw_input_str is None: frontend.stdin_channel.process_events() - return self.raw_input_str + return self.raw_input_str # type:ignore[unreachable] # ------------------------------------------------------------------------- # Protected interface diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 87834e26b..0c8a2fa9d 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -93,7 +93,7 @@ def _start_event_gc(): if self._event_pipe_gc_task is not None: # cancel gc task to avoid pending task warnings async def _cancel(): - self._event_pipe_gc_task.cancel() # type:ignore + self._event_pipe_gc_task.cancel() # type:ignore[union-attr] if not self._stopped: self.io_loop.run_sync(_cancel) @@ -373,7 +373,7 @@ def fileno(self): def _watch_pipe_fd(self): """ - We've redirected standards steams 0 and 1 into a pipe. + We've redirected standards streams 0 and 1 into a pipe. We need to watch in a thread and redirect them to the right places. @@ -424,7 +424,7 @@ def __init__( that will swap the give file descriptor for a pipe, read from the pipe, and insert this into the current Stream. isatty : bool (default, False) - Indication of whether this stream has termimal capabilities (e.g. can handle colors) + Indication of whether this stream has terminal capabilities (e.g. can handle colors) """ if pipe is not None: @@ -634,7 +634,7 @@ def write(self, string: str) -> Optional[int]: # type:ignore[override] """ if not isinstance(string, str): - msg = f"write() argument must be str, not {type(string)}" + msg = f"write() argument must be str, not {type(string)}" # type:ignore[unreachable] raise TypeError(msg) if self.echo is not None: diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index c1c79e942..f82831cc7 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -29,7 +29,7 @@ try: from IPython.core.interactiveshell import _asyncio_runner # type:ignore[attr-defined] except ImportError: - _asyncio_runner = None # type:ignore + _asyncio_runner = None # type:ignore[assignment] try: from IPython.core.completer import provisionalcompleter as _provisionalcompleter @@ -328,7 +328,7 @@ def set_sigint_result(): # use add_callback for thread safety self.io_loop.add_callback(set_sigint_result) - # set the custom sigint hander during this context + # set the custom sigint handler during this context save_sigint = signal.signal(signal.SIGINT, handle_sigint) try: yield @@ -377,7 +377,7 @@ async def run_cell(*args, **kwargs): preprocessing_exc_tuple = sys.exc_info() if ( - _asyncio_runner + _asyncio_runner # type:ignore[truthy-bool] and shell.loop_runner is _asyncio_runner and asyncio.get_event_loop().is_running() and should_run_async( diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index da6d19656..990d6ba4f 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -460,9 +460,9 @@ def init_blackhole(self): if self.no_stdout or self.no_stderr: blackhole = open(os.devnull, "w") # noqa if self.no_stdout: - sys.stdout = sys.__stdout__ = blackhole # type:ignore + sys.stdout = sys.__stdout__ = blackhole # type:ignore[misc] if self.no_stderr: - sys.stderr = sys.__stderr__ = blackhole # type:ignore + sys.stderr = sys.__stderr__ = blackhole # type:ignore[misc] def init_io(self): """Redirect input streams and set a display hook.""" @@ -634,7 +634,7 @@ def _init_asyncio_patch(self): but it is still preferable to run the Selector in the main thread instead of the background. - do this as early as possible to make it a low priority and overrideable + do this as early as possible to make it a low priority and overridable ref: https://github.com/tornadoweb/tornado/issues/2608 @@ -670,8 +670,8 @@ def init_pdb(self): if hasattr(debugger, "InterruptiblePdb"): # Only available in newer IPython releases: - debugger.Pdb = debugger.InterruptiblePdb # type:ignore - pdb.Pdb = debugger.Pdb # type:ignore + debugger.Pdb = debugger.InterruptiblePdb # type:ignore[misc] + pdb.Pdb = debugger.Pdb # type:ignore[assignment,misc] pdb.set_trace = debugger.set_trace # type:ignore[assignment] @catch_config_error diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index b6b34f7e5..a6d9c7538 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -66,7 +66,7 @@ def __init__(self, interrupt_handle=None, parent_handle=None): assert interrupt_handle or parent_handle super().__init__() if ctypes is None: - msg = "ParentPollerWindows requires ctypes" + msg = "ParentPollerWindows requires ctypes" # type:ignore[unreachable] raise ImportError(msg) self.daemon = True self.interrupt_handle = interrupt_handle diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index 62fc9ea35..977302a8a 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -65,6 +65,6 @@ async def loc(coro): self._cell_cancel_scope = trio.CancelScope() with self._cell_cancel_scope: return await coro - self._cell_cancel_scope = None + self._cell_cancel_scope = None # type:ignore[unreachable] return trio.from_thread.run(loc, async_fn, trio_token=self._trio_token) diff --git a/pyproject.toml b/pyproject.toml index 8cb453302..21181273e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,7 @@ path = "ipykernel/_version.py" features = ["docs"] [tool.hatch.envs.docs.scripts] build = "make -C docs html SPHINXOPTS='-W'" -api = "sphinx-apidoc -o docs/api -f -E ipykernel ipykernel/tests ipykernel/inprocess/tests ipykernel/datapub.py ipykernel/pickleutil.py ipykernel/serialize.py ipykernel/gui ipykernel/pylab" +api = "sphinx-apidoc -o docs/api -f -E ipykernel tests ipykernel/datapub.py ipykernel/pickleutil.py ipykernel/serialize.py ipykernel/gui ipykernel/pylab" [tool.hatch.envs.test] features = ["test"] @@ -112,7 +112,7 @@ matrix.qt.features = [ [tool.hatch.envs.typing] features = ["test"] -dependencies = ["mypy>=0.990"] +dependencies = ["mypy>=1.5.1"] [tool.hatch.envs.typing.scripts] test = "mypy --install-types --non-interactive {args:.}" @@ -135,6 +135,7 @@ fmt = [ check_untyped_defs = true disallow_incomplete_defs = true disallow_untyped_decorators = true +enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] follow_imports = "normal" ignore_missing_imports = true no_implicit_optional = true @@ -147,13 +148,34 @@ strict_optional = true warn_unused_configs = true warn_redundant_casts = true warn_return_any = true +warn_unreachable = true warn_unused_ignores = true +[[tool.mypy.overrides]] +module = "tests.*" +disable_error_code = ["ignore-without-code"] +warn_unreachable = false + [tool.pytest.ini_options] -addopts = "-raXs --durations 10 --color=yes --doctest-modules --ignore=ipykernel/pylab/backend_inline.py --ignore=ipykernel/pylab/config.py --ignore=ipykernel/gui/gtk3embed.py --ignore=ipykernel/gui/gtkembed.py --ignore=ipykernel/datapub.py --ignore=ipykernel/log.py --ignore=ipykernel/pickleutil.py --ignore=ipykernel/serialize.py --ignore=ipykernel/_eventloop_macos.py" +minversion = "6.0" +xfail_strict = true +log_cli_level = "info" +addopts = [ + "-raXs", "--durations=10", "--color=yes", "--doctest-modules", + "--showlocals", "--strict-markers", "--strict-config", + "--ignore=ipykernel/pylab/backend_inline.py", + "--ignore=ipykernel/pylab/config.py", + "--ignore=ipykernel/gui/gtk3embed.py", + "--ignore=ipykernel/gui/gtkembed.py", + "--ignore=ipykernel/datapub.py", + "--ignore=ipykernel/log.py", + "--ignore=ipykernel/pickleutil.py", + "--ignore=ipykernel/serialize.py", + "--ignore=ipykernel/_eventloop_macos.py" +] testpaths = [ - "ipykernel/tests", - "ipykernel/inprocess/tests" + "tests", + "tests/inprocess" ] asyncio_mode = "auto" timeout = 300 @@ -195,14 +217,13 @@ exclude_lines = [ relative_files = true source = ["ipykernel"] omit = [ - "ipykernel/tests/*", + "tests/*", "ipykernel/datapub.py", "ipykernel/debugger.py", "ipykernel/eventloops.py", "ipykernel/log.py", "ipykernel/pickleutil.py", "ipykernel/serialize.py", - "ipykernel/inprocess/tests/*", "ipykernel/gui/*", "ipykernel/pylab/*", ] @@ -304,7 +325,7 @@ unfixable = [ # PLC1901 `stderr == ""` can be simplified to `not stderr` as an empty string is falsey # B018 Found useless expression. Either assign it to a variable or remove it. # S603 `subprocess` call: check for execution of untrusted input -"ipykernel/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", +"tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", "EM102", "EM103", "PLR2004", "PLW0603", "PLW2901", "PLC1901", "B018", "S603"] [tool.interrogate] @@ -315,8 +336,11 @@ ignore-property-decorators=true ignore-nested-functions=true ignore-nested-classes=true fail-under=95 -exclude = ["docs", "*/tests"] +exclude = ["docs", "tests"] [tool.check-wheel-contents] toplevel = ["ipykernel/", "ipykernel_launcher.py"] ignore = ["W002"] + +[tool.repo-review] +ignore = ["PY007", "PP308", "GH102", "PC140", "MY101"] diff --git a/ipykernel/tests/__init__.py b/tests/__init__.py similarity index 100% rename from ipykernel/tests/__init__.py rename to tests/__init__.py diff --git a/ipykernel/tests/_asyncio_utils.py b/tests/_asyncio_utils.py similarity index 100% rename from ipykernel/tests/_asyncio_utils.py rename to tests/_asyncio_utils.py diff --git a/ipykernel/tests/conftest.py b/tests/conftest.py similarity index 100% rename from ipykernel/tests/conftest.py rename to tests/conftest.py diff --git a/ipykernel/inprocess/tests/__init__.py b/tests/inprocess/__init__.py similarity index 100% rename from ipykernel/inprocess/tests/__init__.py rename to tests/inprocess/__init__.py diff --git a/ipykernel/inprocess/tests/test_kernel.py b/tests/inprocess/test_kernel.py similarity index 98% rename from ipykernel/inprocess/tests/test_kernel.py rename to tests/inprocess/test_kernel.py index b85c4bdfe..793485d95 100644 --- a/ipykernel/inprocess/tests/test_kernel.py +++ b/tests/inprocess/test_kernel.py @@ -12,7 +12,8 @@ from ipykernel.inprocess.blocking import BlockingInProcessKernelClient from ipykernel.inprocess.ipkernel import InProcessKernel from ipykernel.inprocess.manager import InProcessKernelManager -from ipykernel.tests.utils import assemble_output + +from ..utils import assemble_output orig_msg = Session.msg diff --git a/ipykernel/inprocess/tests/test_kernelmanager.py b/tests/inprocess/test_kernelmanager.py similarity index 100% rename from ipykernel/inprocess/tests/test_kernelmanager.py rename to tests/inprocess/test_kernelmanager.py diff --git a/ipykernel/tests/test_async.py b/tests/test_async.py similarity index 100% rename from ipykernel/tests/test_async.py rename to tests/test_async.py diff --git a/ipykernel/tests/test_comm.py b/tests/test_comm.py similarity index 100% rename from ipykernel/tests/test_comm.py rename to tests/test_comm.py diff --git a/ipykernel/tests/test_connect.py b/tests/test_connect.py similarity index 100% rename from ipykernel/tests/test_connect.py rename to tests/test_connect.py diff --git a/ipykernel/tests/test_debugger.py b/tests/test_debugger.py similarity index 98% rename from ipykernel/tests/test_debugger.py rename to tests/test_debugger.py index c46064425..48fafb42e 100644 --- a/ipykernel/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -121,7 +121,11 @@ def test_set_breakpoints(kernel_with_debug): assert reply["body"]["breakpoints"][0]["source"]["path"] == source r = wait_for_debug_request(kernel_with_debug, "debugInfo") - assert source in map(lambda b: b["source"], r["body"]["breakpoints"]) # noqa + + def func(b): + return b["source"] + + assert source in map(func, r["body"]["breakpoints"]) r = wait_for_debug_request(kernel_with_debug, "configurationDone") assert r["success"] @@ -208,7 +212,11 @@ def test_rich_inspect_not_at_breakpoint(kernel_with_debug): get_reply(kernel_with_debug, msg_id) r = wait_for_debug_request(kernel_with_debug, "inspectVariables") - assert var_name in list(map(lambda v: v["name"], r["body"]["variables"])) # noqa + + def func(v): + return v["name"] + + assert var_name in list(map(func, r["body"]["variables"])) reply = wait_for_debug_request( kernel_with_debug, diff --git a/ipykernel/tests/test_embed_kernel.py b/tests/test_embed_kernel.py similarity index 100% rename from ipykernel/tests/test_embed_kernel.py rename to tests/test_embed_kernel.py diff --git a/ipykernel/tests/test_eventloop.py b/tests/test_eventloop.py similarity index 97% rename from ipykernel/tests/test_eventloop.py rename to tests/test_eventloop.py index a4d18d114..26924cfbe 100644 --- a/ipykernel/tests/test_eventloop.py +++ b/tests/test_eventloop.py @@ -57,7 +57,7 @@ def teardown(): async_code = """ -from ipykernel.tests._asyncio_utils import async_func +from tests._asyncio_utils import async_func async_func() """ @@ -144,7 +144,7 @@ def test_qt_enable_gui(kernel, capsys): enable_gui(gui, kernel) assert app == kernel.app - # Event loop intergration can be turned off. + # Event loop integration can be turned off. enable_gui(None, kernel) assert not hasattr(kernel, 'app') diff --git a/ipykernel/tests/test_heartbeat.py b/tests/test_heartbeat.py similarity index 100% rename from ipykernel/tests/test_heartbeat.py rename to tests/test_heartbeat.py diff --git a/ipykernel/tests/test_io.py b/tests/test_io.py similarity index 99% rename from ipykernel/tests/test_io.py rename to tests/test_io.py index fee1e6020..98f047899 100644 --- a/ipykernel/tests/test_io.py +++ b/tests/test_io.py @@ -125,7 +125,6 @@ async def test_event_pipe_gc(iopub_thread): isatty=True, watchfd=False, ) - save_stdout = sys.stdout assert iopub_thread._event_pipes == {} with stream, mock.patch.object(sys, "stdout", stream), ThreadPoolExecutor(1) as pool: pool.submit(print, "x").result() @@ -201,7 +200,6 @@ def test_echo_watch(ctx): port = s.bind_to_random_port("tcp://127.0.0.1") url = f"tcp://127.0.0.1:{port}" session = Session(key=b'abc') - messages = [] stdout_chunks = [] with s: env = dict(os.environ) diff --git a/ipykernel/tests/test_ipkernel_direct.py b/tests/test_ipkernel_direct.py similarity index 98% rename from ipykernel/tests/test_ipkernel_direct.py rename to tests/test_ipkernel_direct.py index b0dbb01f5..c9201348c 100644 --- a/ipykernel/tests/test_ipkernel_direct.py +++ b/tests/test_ipkernel_direct.py @@ -96,7 +96,8 @@ async def test_direct_interrupt_request(ipkernel): # test failure on interrupt request def raiseOSError(): - raise OSError("evalue") + msg = "evalue" + raise OSError(msg) ipkernel._send_interrupt_children = raiseOSError reply = await ipkernel.test_control_message("interrupt_request", {}) @@ -212,5 +213,5 @@ def test_finish_metadata(ipkernel: IPythonKernel) -> None: async def test_do_debug_request(ipkernel: IPythonKernel) -> None: msg = ipkernel.session.msg("debug_request", {}) - msg_list = ipkernel.session.serialize(msg) + ipkernel.session.serialize(msg) await ipkernel.do_debug_request(msg) diff --git a/ipykernel/tests/test_jsonutil.py b/tests/test_jsonutil.py similarity index 97% rename from ipykernel/tests/test_jsonutil.py rename to tests/test_jsonutil.py index faace16a7..2f7e5dbcd 100644 --- a/ipykernel/tests/test_jsonutil.py +++ b/tests/test_jsonutil.py @@ -11,8 +11,8 @@ import pytest from jupyter_client._version import version_info as jupyter_client_version -from .. import jsonutil -from ..jsonutil import encode_images, json_clean +from ipykernel import jsonutil +from ipykernel.jsonutil import encode_images, json_clean JUPYTER_CLIENT_MAJOR_VERSION: int = jupyter_client_version[0] # type:ignore diff --git a/ipykernel/tests/test_kernel.py b/tests/test_kernel.py similarity index 98% rename from ipykernel/tests/test_kernel.py rename to tests/test_kernel.py index 49fc1c08d..07411bd13 100644 --- a/ipykernel/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -42,7 +42,7 @@ def _check_master(kc, expected=True, stream="stdout"): def _check_status(content): """If status=error, show the traceback""" if content["status"] == "error": - assert False, "".join(["\n"] + content["traceback"]) + raise AssertionError("".join(["\n"] + content["traceback"])) # printing tests @@ -196,12 +196,10 @@ def test_subprocess_error(): def test_raw_input(): """test input""" with kernel() as kc: - iopub = kc.iopub_channel - input_f = "input" theprompt = "prompt> " code = f'print({input_f}("{theprompt}"))' - msg_id = kc.execute(code, allow_stdin=True) + kc.execute(code, allow_stdin=True) msg = kc.get_stdin_msg(timeout=TIMEOUT) assert msg["header"]["msg_type"] == "input_request" content = msg["content"] @@ -232,7 +230,7 @@ def test_save_history(): def test_smoke_faulthandler(): - faulthadler = pytest.importorskip("faulthandler", reason="this test needs faulthandler") + pytest.importorskip("faulthandler", reason="this test needs faulthandler") with kernel() as kc: # Note: faulthandler.register is not available on windows. code = "\n".join( diff --git a/ipykernel/tests/test_kernel_direct.py b/tests/test_kernel_direct.py similarity index 99% rename from ipykernel/tests/test_kernel_direct.py rename to tests/test_kernel_direct.py index 7f47aaedd..dfb8a70fe 100644 --- a/ipykernel/tests/test_kernel_direct.py +++ b/tests/test_kernel_direct.py @@ -75,7 +75,8 @@ async def test_direct_interrupt_request(kernel): # test failure on interrupt request def raiseOSError(): - raise OSError("evalue") + msg = "evalue" + raise OSError(msg) kernel._send_interrupt_children = raiseOSError reply = await kernel.test_control_message("interrupt_request", {}) diff --git a/ipykernel/tests/test_kernelapp.py b/tests/test_kernelapp.py similarity index 100% rename from ipykernel/tests/test_kernelapp.py rename to tests/test_kernelapp.py diff --git a/ipykernel/tests/test_kernelspec.py b/tests/test_kernelspec.py similarity index 100% rename from ipykernel/tests/test_kernelspec.py rename to tests/test_kernelspec.py diff --git a/ipykernel/tests/test_message_spec.py b/tests/test_message_spec.py similarity index 98% rename from ipykernel/tests/test_message_spec.py rename to tests/test_message_spec.py index 5710cf297..0c9e777cd 100644 --- a/ipykernel/tests/test_message_spec.py +++ b/tests/test_message_spec.py @@ -53,7 +53,7 @@ def check(self, d): try: setattr(self, key, d[key]) except TraitError as e: - assert False, str(e) + raise AssertionError(str(e)) from None class Version(Unicode): @@ -65,9 +65,11 @@ def __init__(self, *args, **kwargs): def validate(self, obj, value): if self.min and V(value) < V(self.min): - raise TraitError(f"bad version: {value} < {self.min}") + msg = f"bad version: {value} < {self.min}" + raise TraitError(msg) if self.max and (V(value) > V(self.max)): - raise TraitError(f"bad version: {value} > {self.max}") + msg = f"bad version: {value} > {self.max}" + raise TraitError(msg) class RMessage(Reference): @@ -469,7 +471,7 @@ def test_oinfo_detail(): def test_oinfo_not_found(): flush_channels() - msg_id = KC.inspect("dne") + msg_id = KC.inspect("does_not_exist") reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, "inspect_reply", msg_id) content = reply["content"] diff --git a/ipykernel/tests/test_parentpoller.py b/tests/test_parentpoller.py similarity index 95% rename from ipykernel/tests/test_parentpoller.py rename to tests/test_parentpoller.py index 22df132e7..c40a47204 100644 --- a/ipykernel/tests/test_parentpoller.py +++ b/tests/test_parentpoller.py @@ -20,7 +20,8 @@ def exit_mock(*args): poller.run() def mock_getppid(): - raise ValueError("hi") + msg = "hi" + raise ValueError(msg) with mock.patch("os.getppid", mock_getppid), pytest.raises(ValueError): poller.run() diff --git a/ipykernel/tests/test_pickleutil.py b/tests/test_pickleutil.py similarity index 100% rename from ipykernel/tests/test_pickleutil.py rename to tests/test_pickleutil.py diff --git a/ipykernel/tests/test_start_kernel.py b/tests/test_start_kernel.py similarity index 96% rename from ipykernel/tests/test_start_kernel.py rename to tests/test_start_kernel.py index b2b2d3b8c..f2a632be0 100644 --- a/ipykernel/tests/test_start_kernel.py +++ b/tests/test_start_kernel.py @@ -17,13 +17,13 @@ def test_ipython_start_kernel_userns(): cmd = dedent( """ from ipykernel.kernelapp import launch_new_instance - ns = {"tre": 123} + ns = {"custom": 123} launch_new_instance(user_ns=ns) """ ) with setup_kernel(cmd) as client: - client.inspect("tre") + client.inspect("custom") msg = client.get_shell_msg(timeout=TIMEOUT) content = msg["content"] assert content["found"] diff --git a/ipykernel/tests/test_zmq_shell.py b/tests/test_zmq_shell.py similarity index 100% rename from ipykernel/tests/test_zmq_shell.py rename to tests/test_zmq_shell.py diff --git a/ipykernel/tests/utils.py b/tests/utils.py similarity index 100% rename from ipykernel/tests/utils.py rename to tests/utils.py From 518785bbdbc030cfc18f26f4260c596be87a751a Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 30 Sep 2023 09:11:21 -0500 Subject: [PATCH 1020/1195] Update typing (#1150) --- ipykernel/comm/comm.py | 1 + ipykernel/datapub.py | 1 + ipykernel/displayhook.py | 30 +++++---- ipykernel/inprocess/client.py | 15 ++--- ipykernel/inprocess/ipkernel.py | 10 ++- ipykernel/inprocess/manager.py | 5 +- ipykernel/ipkernel.py | 29 ++++++--- ipykernel/kernelapp.py | 2 +- ipykernel/kernelbase.py | 107 +++++++++++++++++++++++--------- ipykernel/zmqshell.py | 12 ++-- pyproject.toml | 5 +- 11 files changed, 149 insertions(+), 68 deletions(-) diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index b8f4586f5..862e3bcf1 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -33,6 +33,7 @@ def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): if self.kernel is None: self.kernel = Kernel.instance() + assert self.kernel.session is not None self.kernel.session.send( self.kernel.iopub_socket, msg_type, diff --git a/ipykernel/datapub.py b/ipykernel/datapub.py index 4b1c7a501..5805c9afd 100644 --- a/ipykernel/datapub.py +++ b/ipykernel/datapub.py @@ -48,6 +48,7 @@ def publish_data(self, data): The data to be published. Think of it as a namespace. """ session = self.session + assert session is not None buffers = serialize_object( data, buffer_threshold=session.buffer_threshold, diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index 0727997cc..4dd0bb5eb 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -2,9 +2,11 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import builtins import sys +import typing as t from IPython.core.displayhook import DisplayHook from jupyter_client.session import Session, extract_header @@ -61,6 +63,7 @@ class ZMQShellDisplayHook(DisplayHook): session = Instance(Session, allow_none=True) pub_socket = Any(allow_none=True) parent_header = Dict({}) + msg: dict[str, t.Any] | None def set_parent(self, parent): """Set the parent for outbound messages.""" @@ -68,28 +71,31 @@ def set_parent(self, parent): def start_displayhook(self): """Start the display hook.""" - self.msg = self.session.msg( - "execute_result", - { - "data": {}, - "metadata": {}, - }, - parent=self.parent_header, - ) + if self.session: + self.msg = self.session.msg( + "execute_result", + { + "data": {}, + "metadata": {}, + }, + parent=self.parent_header, + ) def write_output_prompt(self): """Write the output prompt.""" - self.msg["content"]["execution_count"] = self.prompt_count + if self.msg: + self.msg["content"]["execution_count"] = self.prompt_count def write_format_data(self, format_dict, md_dict=None): """Write format data to the message.""" - self.msg["content"]["data"] = json_clean(encode_images(format_dict)) - self.msg["content"]["metadata"] = md_dict + if self.msg: + self.msg["content"]["data"] = json_clean(encode_images(format_dict)) + self.msg["content"]["metadata"] = md_dict def finish_displayhook(self): """Finish up all displayhook activities.""" sys.stdout.flush() sys.stderr.flush() - if self.msg["content"]["data"]: + if self.msg and self.msg["content"]["data"] and self.session: self.session.send(self.pub_socket, self.msg, ident=self.topic) self.msg = None diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 1fa80bc20..0c4a19670 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -60,42 +60,43 @@ def _default_blocking_class(self): def get_connection_info(self): """Get the connection info for the client.""" d = super().get_connection_info() - d["kernel"] = self.kernel + d["kernel"] = self.kernel # type:ignore[assignment] return d def start_channels(self, *args, **kwargs): """Start the channels on the client.""" super().start_channels() - self.kernel.frontends.append(self) + if self.kernel: + self.kernel.frontends.append(self) @property def shell_channel(self): if self._shell_channel is None: - self._shell_channel = self.shell_channel_class(self) + self._shell_channel = self.shell_channel_class(self) # type:ignore[operator] return self._shell_channel @property def iopub_channel(self): if self._iopub_channel is None: - self._iopub_channel = self.iopub_channel_class(self) + self._iopub_channel = self.iopub_channel_class(self) # type:ignore[operator] return self._iopub_channel @property def stdin_channel(self): if self._stdin_channel is None: - self._stdin_channel = self.stdin_channel_class(self) + self._stdin_channel = self.stdin_channel_class(self) # type:ignore[operator] return self._stdin_channel @property def control_channel(self): if self._control_channel is None: - self._control_channel = self.control_channel_class(self) + self._control_channel = self.control_channel_class(self) # type:ignore[operator] return self._control_channel @property def hb_channel(self): if self._hb_channel is None: - self._hb_channel = self.hb_channel_class(self) + self._hb_channel = self.hb_channel_class(self) # type:ignore[operator] return self._hb_channel # Methods for sending specific messages diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index f3cac4a8c..30daca885 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -51,7 +51,7 @@ class InProcessKernel(IPythonKernel): _underlying_iopub_socket = Instance(DummySocket, ()) iopub_thread: IOPubThread = Instance(IOPubThread) # type:ignore[assignment] - shell_stream = Instance(DummySocket, ()) + shell_stream = Instance(DummySocket, ()) # type:ignore[arg-type] @default("iopub_thread") def _default_iopub_thread(self): @@ -72,7 +72,8 @@ def __init__(self, **traits): super().__init__(**traits) self._underlying_iopub_socket.observe(self._io_dispatch, names=["message_sent"]) - self.shell.kernel = self + if self.shell: + self.shell.kernel = self async def execute_request(self, stream, ident, parent): """Override for temporary IO redirection.""" @@ -81,7 +82,8 @@ async def execute_request(self, stream, ident, parent): def start(self): """Override registration of dispatchers for streams.""" - self.shell.exit_now = False + if self.shell: + self.shell.exit_now = False def _abort_queues(self): """The in-process kernel doesn't abort requests.""" @@ -99,6 +101,7 @@ def _input_request(self, prompt, ident, parent, password=False): # Send the input request. content = json_clean(dict(prompt=prompt, password=password)) + assert self.session is not None msg = self.session.msg("input_request", content, parent) for frontend in self.frontends: if frontend.session.session == parent["header"]["session"]: @@ -132,6 +135,7 @@ def _redirected_io(self): def _io_dispatch(self, change): """Called when a message is sent to the IO socket.""" assert self.iopub_socket.io_thread is not None + assert self.session is not None ident, msg = self.session.recv(self.iopub_socket.io_thread.socket, copy=False) for frontend in self.frontends: frontend.iopub_channel.call_handlers(msg) diff --git a/ipykernel/inprocess/manager.py b/ipykernel/inprocess/manager.py index 3388dbf61..3a3f92c37 100644 --- a/ipykernel/inprocess/manager.py +++ b/ipykernel/inprocess/manager.py @@ -49,8 +49,9 @@ def start_kernel(self, **kwds): def shutdown_kernel(self): """Shutdown the kernel.""" - self.kernel.iopub_thread.stop() - self._kill_kernel() + if self.kernel: + self.kernel.iopub_thread.stop() + self._kill_kernel() def restart_kernel(self, now=False, **kwds): """Restart the kernel.""" diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index f82831cc7..990c5558b 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -118,7 +118,7 @@ def __init__(self, **kwargs): ) # Initialize the InteractiveShell subclass - self.shell = self.shell_class.instance( + self.shell = self.shell_class.instance( # type:ignore[attr-defined] parent=self, profile_dir=self.profile_dir, user_module=self.user_module, @@ -206,7 +206,8 @@ def dispatch_debugpy(self, msg): @property def banner(self): - return self.shell.banner + if self.shell: + return self.shell.banner async def poll_stopped_queue(self): """Poll the stopped queue.""" @@ -215,7 +216,8 @@ async def poll_stopped_queue(self): def start(self): """Start the kernel.""" - self.shell.exit_now = False + if self.shell: + self.shell.exit_now = False if self.debugpy_stream is None: self.log.warning("debugpy_stream undefined, debugging will not be enabled") else: @@ -231,7 +233,7 @@ def set_parent(self, ident, parent, channel="shell"): about the parent message. """ super().set_parent(ident, parent, channel) - if channel == "shell": + if channel == "shell" and self.shell: self.shell.set_parent(parent) def init_metadata(self, parent): @@ -284,7 +286,8 @@ def _restore_input(self): @property def execution_count(self): - return self.shell.execution_count + if self.shell: + return self.shell.execution_count @execution_count.setter def execution_count(self, value): @@ -348,6 +351,7 @@ async def do_execute( ): """Handle code execution.""" shell = self.shell # we'll need this a lot here + assert shell is not None self._forward_input(allow_stdin) @@ -371,7 +375,7 @@ async def run_cell(*args, **kwargs): # not just asyncio preprocessing_exc_tuple = None try: - transformed_cell = self.shell.transform_cell(code) + transformed_cell = shell.transform_cell(code) except Exception: transformed_cell = code preprocessing_exc_tuple = sys.exc_info() @@ -488,6 +492,7 @@ def do_complete(self, code, cursor_pos): cursor_pos = len(code) line, offset = line_at_cursor(code, cursor_pos) line_cursor = cursor_pos - offset + assert self.shell is not None txt, matches = self.shell.complete("", line, line_cursor) return { "matches": matches, @@ -509,6 +514,7 @@ def _experimental_do_complete(self, code, cursor_pos): if cursor_pos is None: cursor_pos = len(code) with _provisionalcompleter(): + assert self.shell is not None raw_completions = self.shell.Completer.completions(code, cursor_pos) completions = list(_rectify_completions(code, raw_completions)) @@ -548,6 +554,7 @@ def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): reply_content: t.Dict[str, t.Any] = {"status": "ok"} reply_content["data"] = {} reply_content["metadata"] = {} + assert self.shell is not None try: if release.version_info >= (8,): # `omit_sections` keyword will be available in IPython 8, see @@ -581,6 +588,7 @@ def do_history( unique=False, ): """Handle code history.""" + assert self.shell is not None if hist_access_type == "tail": hist = self.shell.history_manager.get_tail( n, raw=raw, output=output, include_latest=True @@ -605,7 +613,8 @@ def do_history( def do_shutdown(self, restart): """Handle kernel shutdown.""" - self.shell.exit_now = True + if self.shell: + self.shell.exit_now = True return dict(status="ok", restart=restart) def do_is_complete(self, code): @@ -613,6 +622,7 @@ def do_is_complete(self, code): transformer_manager = getattr(self.shell, "input_transformer_manager", None) if transformer_manager is None: # input_splitter attribute is deprecated + assert self.shell is not None transformer_manager = self.shell.input_splitter status, indent_spaces = transformer_manager.check_complete(code) r = {"status": status} @@ -628,6 +638,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): from .serialize import serialize_object, unpack_apply_message shell = self.shell + assert shell is not None try: working = shell.user_ns @@ -652,6 +663,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): for key in ns: working.pop(key) + assert self.session is not None result_buf = serialize_object( result, buffer_threshold=self.session.buffer_threshold, @@ -686,7 +698,8 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): def do_clear(self): """Clear the kernel.""" - self.shell.reset(False) + if self.shell: + self.shell.reset(False) return dict(status="ok") diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 990d6ba4f..2f51271f5 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -547,7 +547,7 @@ def init_kernel(self): control_stream = ZMQStream(self.control_socket, self.control_thread.io_loop) debugpy_stream = ZMQStream(self.debugpy_socket, self.control_thread.io_loop) self.control_thread.start() - kernel_factory = self.kernel_class.instance + kernel_factory = self.kernel_class.instance # type:ignore[attr-defined] kernel = kernel_factory( parent=self, diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 8b45156a7..6d06d4ab5 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -304,7 +304,8 @@ async def _flush_control_queue(self): def _flush(): # control_stream.flush puts messages on the queue - self.control_stream.flush() + if self.control_stream: + self.control_stream.flush() # put Future on the queue after all of those, # so we can wait for all queued messages to be processed self.control_queue.put(tracer_future) @@ -314,6 +315,8 @@ def _flush(): async def process_control(self, msg): """dispatch control requests""" + if not self.session: + return idents, msg = self.session.feed_identities(msg, copy=False) try: msg = self.session.deserialize(msg, content=True, copy=False) @@ -345,7 +348,8 @@ async def process_control(self, msg): sys.stderr.flush() self._publish_status("idle", "control") # flush to ensure reply is sent - self.control_stream.flush(zmq.POLLOUT) + if self.control_stream: + self.control_stream.flush(zmq.POLLOUT) def should_handle(self, stream, msg, idents): """Check whether a shell-channel message should be handled @@ -362,7 +366,8 @@ def should_handle(self, stream, msg, idents): async def dispatch_shell(self, msg): """dispatch shell requests""" - + if not self.session: + return # flush control queue before handling shell requests await self._flush_control_queue() @@ -385,7 +390,8 @@ async def dispatch_shell(self, msg): self._publish_status("idle", "shell") # flush to ensure reply is sent before # handling the next request - self.shell_stream.flush(zmq.POLLOUT) + if self.shell_stream: + self.shell_stream.flush(zmq.POLLOUT) return # Print some info about this message and leave a '--->' marker, so it's @@ -426,7 +432,8 @@ async def dispatch_shell(self, msg): self._publish_status("idle", "shell") # flush to ensure reply is sent before # handling the next request - self.shell_stream.flush(zmq.POLLOUT) + if self.shell_stream: + self.shell_stream.flush(zmq.POLLOUT) def pre_handler_hook(self): """Hook to execute before calling message handler""" @@ -486,7 +493,8 @@ async def do_one_iteration(self): This is now a coroutine """ # flush messages off of shell stream into the message queue - self.shell_stream.flush() + if self.shell_stream: + self.shell_stream.flush() # process at most one shell message per iteration await self.process_one(wait=False) @@ -546,19 +554,20 @@ def start(self): self.msg_queue: Queue[t.Any] = Queue() self.io_loop.add_callback(self.dispatch_queue) - self.control_stream.on_recv(self.dispatch_control, copy=False) + if self.control_stream: + self.control_stream.on_recv(self.dispatch_control, copy=False) control_loop = self.control_thread.io_loop if self.control_thread else self.io_loop asyncio.run_coroutine_threadsafe(self.poll_control_queue(), control_loop.asyncio_loop) - - self.shell_stream.on_recv( - partial( - self.schedule_dispatch, - self.dispatch_shell, - ), - copy=False, - ) + if self.shell_stream: + self.shell_stream.on_recv( + partial( + self.schedule_dispatch, + self.dispatch_shell, + ), + copy=False, + ) # publish idle status self._publish_status("starting", "shell") @@ -577,7 +586,8 @@ def record_ports(self, ports): def _publish_execute_input(self, code, parent, execution_count): """Publish the code request on the iopub stream.""" - + if not self.session: + return self.session.send( self.iopub_socket, "execute_input", @@ -588,6 +598,8 @@ def _publish_execute_input(self, code, parent, execution_count): def _publish_status(self, status, channel, parent=None): """send status (busy/idle) on IOPub""" + if not self.session: + return self.session.send( self.iopub_socket, "status", @@ -597,6 +609,8 @@ def _publish_status(self, status, channel, parent=None): ) def _publish_debug_event(self, event): + if not self.session: + return self.session.send( self.iopub_socket, "debug_event", @@ -662,6 +676,8 @@ def send_response( This relies on :meth:`set_parent` having been called for the current message. """ + if not self.session: + return return self.session.send( stream, msg_or_type, @@ -694,6 +710,8 @@ def finish_metadata(self, parent, metadata, reply_content): async def execute_request(self, stream, ident, parent): """handle an execute_request""" + if not self.session: + return try: content = parent["content"] code = content["code"] @@ -752,7 +770,7 @@ async def execute_request(self, stream, ident, parent): reply_content = json_clean(reply_content) metadata = self.finish_metadata(parent, metadata, reply_content) - reply_msg = self.session.send( + reply_msg: dict[str, t.Any] = self.session.send( # type:ignore[assignment] stream, "execute_reply", reply_content, @@ -781,6 +799,8 @@ def do_execute( async def complete_request(self, stream, ident, parent): """Handle a completion request.""" + if not self.session: + return content = parent["content"] code = content["code"] cursor_pos = content["cursor_pos"] @@ -804,6 +824,8 @@ def do_complete(self, code, cursor_pos): async def inspect_request(self, stream, ident, parent): """Handle an inspect request.""" + if not self.session: + return content = parent["content"] reply_content = self.do_inspect( @@ -826,6 +848,8 @@ def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): async def history_request(self, stream, ident, parent): """Handle a history request.""" + if not self.session: + return content = parent["content"] reply_content = self.do_history(**content) @@ -853,7 +877,9 @@ def do_history( async def connect_request(self, stream, ident, parent): """Handle a connect request.""" - content = self._recorded_ports.copy() if self._recorded_ports is not None else {} + if not self.session: + return + content = self._recorded_ports.copy() if self._recorded_ports else {} content["status"] = "ok" msg = self.session.send(stream, "connect_reply", content, parent, ident) self.log.debug("%s", msg) @@ -871,6 +897,8 @@ def kernel_info(self): async def kernel_info_request(self, stream, ident, parent): """Handle a kernel info request.""" + if not self.session: + return content = {"status": "ok"} content.update(self.kernel_info) msg = self.session.send(stream, "kernel_info_reply", content, parent, ident) @@ -878,6 +906,8 @@ async def kernel_info_request(self, stream, ident, parent): async def comm_info_request(self, stream, ident, parent): """Handle a comm info request.""" + if not self.session: + return content = parent["content"] target_name = content.get("target_name", None) @@ -913,6 +943,8 @@ def _send_interrupt_children(self): async def interrupt_request(self, stream, ident, parent): """Handle an interrupt request.""" + if not self.session: + return content: t.Dict[str, t.Any] = {"status": "ok"} try: self._send_interrupt_children() @@ -931,6 +963,8 @@ async def interrupt_request(self, stream, ident, parent): async def shutdown_request(self, stream, ident, parent): """Handle a shutdown request.""" + if not self.session: + return content = self.do_shutdown(parent["content"]["restart"]) if inspect.isawaitable(content): content = await content @@ -941,12 +975,14 @@ async def shutdown_request(self, stream, ident, parent): await self._at_shutdown() self.log.debug("Stopping control ioloop") - control_io_loop = self.control_stream.io_loop - control_io_loop.add_callback(control_io_loop.stop) + if self.control_stream: + control_io_loop = self.control_stream.io_loop + control_io_loop.add_callback(control_io_loop.stop) self.log.debug("Stopping shell ioloop") - shell_io_loop = self.shell_stream.io_loop - shell_io_loop.add_callback(shell_io_loop.stop) + if self.shell_stream: + shell_io_loop = self.shell_stream.io_loop + shell_io_loop.add_callback(shell_io_loop.stop) def do_shutdown(self, restart): """Override in subclasses to do things when the frontend shuts down the @@ -956,6 +992,8 @@ def do_shutdown(self, restart): async def is_complete_request(self, stream, ident, parent): """Handle an is_complete request.""" + if not self.session: + return content = parent["content"] code = content["code"] @@ -972,6 +1010,8 @@ def do_is_complete(self, code): async def debug_request(self, stream, ident, parent): """Handle a debug request.""" + if not self.session: + return content = parent["content"] reply_content = self.do_debug_request(content) if inspect.isawaitable(reply_content): @@ -995,6 +1035,8 @@ def get_process_metric_value(self, process, name, attribute=None): async def usage_request(self, stream, ident, parent): """Handle a usage request.""" + if not self.session: + return reply_content = {"hostname": socket.gethostname(), "pid": os.getpid()} current_process = psutil.Process() all_processes = [current_process, *current_process.children(recursive=True)] @@ -1053,7 +1095,8 @@ async def apply_request(self, stream, ident, parent): # pragma: no cover sys.stderr.flush() md = self.finish_metadata(parent, md, reply_content) - + if not self.session: + return self.session.send( stream, "apply_reply", @@ -1086,6 +1129,8 @@ async def abort_request(self, stream, ident, parent): # pragma: no cover self.aborted.add(str(mid)) content = dict(status="ok") + if not self.session: + return reply_msg = self.session.send( stream, "abort_reply", content=content, parent=parent, ident=ident ) @@ -1097,7 +1142,8 @@ async def clear_request(self, stream, idents, parent): # pragma: no cover "clear_request is deprecated in kernel_base. It is only part of IPython parallel" ) content = self.do_clear() - self.session.send(stream, "clear_reply", ident=idents, parent=parent, content=content) + if self.session: + self.session.send(stream, "clear_reply", ident=idents, parent=parent, content=content) def do_clear(self): """DEPRECATED since 4.0.3""" @@ -1123,7 +1169,8 @@ def _abort_queues(self): # flush streams, so all currently waiting messages # are added to the queue - self.shell_stream.flush() + if self.shell_stream: + self.shell_stream.flush() # Callback to signal that we are done aborting # dispatch functions _must_ be async @@ -1142,6 +1189,8 @@ async def stop_aborting(): def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" + if not self.session: + return self.log.info(f"Aborting {msg['header']['msg_id']}: {msg['header']['msg_type']}") reply_type = msg["header"]["msg_type"].rsplit("_", 1)[0] + "_reply" status = {"status": "aborted"} @@ -1222,6 +1271,7 @@ def _input_request(self, prompt, ident, parent, password=False): raise # Send the input request. + assert self.session is not None content = json_clean(dict(prompt=prompt, password=password)) self.session.send(self.stdin_socket, "input_request", content, parent, ident=ident) @@ -1247,7 +1297,7 @@ def _input_request(self, prompt, ident, parent, password=False): self.log.warning("Invalid Message:", exc_info=True) try: - value = reply["content"]["value"] + value = reply["content"]["value"] # type:ignore[index] except Exception: self.log.error("Bad input_reply: %s", parent) value = "" @@ -1325,11 +1375,12 @@ async def _at_shutdown(self): self.log.exception("Exception during subprocesses termination %s", e) finally: - if self._shutdown_message is not None: + if self._shutdown_message is not None and self.session: self.session.send( self.iopub_socket, self._shutdown_message, ident=self._topic("shutdown"), ) self.log.debug("%s", self._shutdown_message) - self.control_stream.flush(zmq.POLLOUT) + if self.control_stream: + self.control_stream.flush(zmq.POLLOUT) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 61c5ee69b..487008f56 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -115,6 +115,7 @@ def publish( # Use 2-stage process to send a message, # in order to put it through the transform # hooks before potentially sending. + assert self.session is not None msg = self.session.msg(msg_type, json_clean(content), parent=self.parent_header) # Each transform either returns a new @@ -123,7 +124,7 @@ def publish( for hook in self._hooks: msg = hook(msg) if msg is None: - return + return # type:ignore[unreachable] self.session.send( self.pub_socket, @@ -144,13 +145,14 @@ def clear_output(self, wait=False): """ content = dict(wait=wait) self._flush_streams() + assert self.session is not None msg = self.session.msg("clear_output", json_clean(content), parent=self.parent_header) # see publish() for details on how this works for hook in self._hooks: msg = hook(msg) if msg is None: - return + return # type:ignore[unreachable] self.session.send( self.pub_socket, @@ -538,7 +540,7 @@ def ask_exit(self): source="ask_exit", keepkernel=self.keepkernel_on_exit, ) - self.payload_manager.write_payload(payload) + self.payload_manager.write_payload(payload) # type:ignore[union-attr] def run_cell(self, *args, **kwargs): """Run a cell.""" @@ -583,7 +585,7 @@ def set_next_input(self, text, replace=False): text=text, replace=replace, ) - self.payload_manager.write_payload(payload) + self.payload_manager.write_payload(payload) # type:ignore[union-attr] def set_parent(self, parent): """Set the parent header for associating output with its triggering input""" @@ -609,7 +611,7 @@ def init_magics(self): """Initialize magics.""" super().init_magics() self.register_magics(KernelMagics) - self.magics_manager.register_alias("ed", "edit") + self.magics_manager.register_alias("ed", "edit") # type:ignore[union-attr] def init_virtualenv(self): """Initialize virtual environment.""" diff --git a/pyproject.toml b/pyproject.toml index 21181273e..ae96b1020 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,9 +112,9 @@ matrix.qt.features = [ [tool.hatch.envs.typing] features = ["test"] -dependencies = ["mypy>=1.5.1"] +dependencies = ["mypy>=1.5.1", "traitlets>=5.10.1"] [tool.hatch.envs.typing.scripts] -test = "mypy --install-types --non-interactive {args:.}" +test = "mypy --install-types --non-interactive {args}" [tool.hatch.envs.lint] dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.287"] @@ -132,6 +132,7 @@ fmt = [ ] [tool.mypy] +files = "ipykernel" check_untyped_defs = true disallow_incomplete_defs = true disallow_untyped_decorators = true From 31cd263c764643c97e310010bf613fcfec83566b Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 2 Oct 2023 20:00:52 -0500 Subject: [PATCH 1021/1195] Update IPython Typing Usage (#1152) --- ipykernel/zmqshell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 487008f56..c4b2e312c 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -27,7 +27,7 @@ from IPython.core.magic import Magics, line_magic, magics_class from IPython.core.magics import CodeMagics, MacroToEdit # type:ignore[attr-defined] from IPython.core.usage import default_banner -from IPython.display import Javascript, display # type:ignore[attr-defined] +from IPython.display import Javascript, display from IPython.utils import openpy from IPython.utils.process import arg_split, system # type:ignore[attr-defined] from jupyter_client.session import Session, extract_header From 11d7a33e097212740a83ae2c313e296990e51fd8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 19:14:03 -0500 Subject: [PATCH 1022/1195] chore: update pre-commit hooks (#1153) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 12 ++++++------ ipykernel/kernelapp.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4d68ae2fb..24ac14c0a 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.26.3 + rev: 0.27.0 hooks: - id: check-github-workflows @@ -34,7 +34,7 @@ repos: [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.0.2" + rev: "v3.0.3" hooks: - id: prettier types_or: [yaml, html, json] @@ -46,12 +46,12 @@ repos: additional_dependencies: [black==23.7.0] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.7.0 + rev: 23.9.1 hooks: - id: black - repo: https://github.com/codespell-project/codespell - rev: "v2.2.5" + rev: "v2.2.6" hooks: - id: codespell args: ["-L", "sur,nd"] @@ -64,13 +64,13 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.287 + rev: v0.0.292 hooks: - id: ruff args: ["--fix", "--show-fixes"] - repo: https://github.com/scientific-python/cookie - rev: "2023.08.23" + rev: "2023.09.21" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 2f51271f5..483f3844c 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -124,7 +124,7 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMix klass="ipykernel.kernelbase.Kernel", help="""The Kernel subclass to be used. - This should allow easy re-use of the IPKernelApp entry point + This should allow easy reuse of the IPKernelApp entry point to configure and launch kernels other than IPython's own. """, ).tag(config=True) From 515004a331b10dff026b8d81a0571e4cdf1847a3 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 3 Oct 2023 19:37:45 -0500 Subject: [PATCH 1023/1195] Update typing for traitlets 5.11 (#1154) --- ipykernel/embed.py | 2 +- ipykernel/inprocess/blocking.py | 6 +++--- ipykernel/inprocess/client.py | 22 ++++++++++++---------- ipykernel/inprocess/ipkernel.py | 2 +- ipykernel/ipkernel.py | 14 +++++++------- ipykernel/kernelapp.py | 6 +++--- ipykernel/zmqshell.py | 18 +++++++++--------- pyproject.toml | 2 +- 8 files changed, 37 insertions(+), 35 deletions(-) diff --git a/ipykernel/embed.py b/ipykernel/embed.py index de208eb2a..3e4abd390 100644 --- a/ipykernel/embed.py +++ b/ipykernel/embed.py @@ -53,5 +53,5 @@ def embed_kernel(module=None, local_ns=None, **kwargs): app.kernel.user_module = module app.kernel.user_ns = local_ns - app.shell.set_completer_frame() + app.shell.set_completer_frame() # type:ignore[union-attr] app.start() diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index f09bb2316..c598a44b4 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -76,9 +76,9 @@ class BlockingInProcessKernelClient(InProcessKernelClient): """A blocking in-process kernel client.""" # The classes to use for the various channels. - shell_channel_class = Type(BlockingInProcessChannel) - iopub_channel_class = Type(BlockingInProcessChannel) - stdin_channel_class = Type(BlockingInProcessStdInChannel) + shell_channel_class = Type(BlockingInProcessChannel) # type:ignore[arg-type] + iopub_channel_class = Type(BlockingInProcessChannel) # type:ignore[arg-type] + stdin_channel_class = Type(BlockingInProcessStdInChannel) # type:ignore[arg-type] def wait_for_ready(self): """Wait for kernel info reply on shell channel.""" diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 0c4a19670..d0ebfd226 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -39,11 +39,11 @@ class InProcessKernelClient(KernelClient): """ # The classes to use for the various channels. - shell_channel_class = Type(InProcessChannel) - iopub_channel_class = Type(InProcessChannel) - stdin_channel_class = Type(InProcessChannel) - control_channel_class = Type(InProcessChannel) - hb_channel_class = Type(InProcessHBChannel) + shell_channel_class = Type(InProcessChannel) # type:ignore[arg-type] + iopub_channel_class = Type(InProcessChannel) # type:ignore[arg-type] + stdin_channel_class = Type(InProcessChannel) # type:ignore[arg-type] + control_channel_class = Type(InProcessChannel) # type:ignore[arg-type] + hb_channel_class = Type(InProcessHBChannel) # type:ignore[arg-type] kernel = Instance("ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True) @@ -72,31 +72,33 @@ def start_channels(self, *args, **kwargs): @property def shell_channel(self): if self._shell_channel is None: - self._shell_channel = self.shell_channel_class(self) # type:ignore[operator] + self._shell_channel = self.shell_channel_class(self) # type:ignore[abstract,call-arg] return self._shell_channel @property def iopub_channel(self): if self._iopub_channel is None: - self._iopub_channel = self.iopub_channel_class(self) # type:ignore[operator] + self._iopub_channel = self.iopub_channel_class(self) # type:ignore[abstract,call-arg] return self._iopub_channel @property def stdin_channel(self): if self._stdin_channel is None: - self._stdin_channel = self.stdin_channel_class(self) # type:ignore[operator] + self._stdin_channel = self.stdin_channel_class(self) # type:ignore[abstract,call-arg] return self._stdin_channel @property def control_channel(self): if self._control_channel is None: - self._control_channel = self.control_channel_class(self) # type:ignore[operator] + self._control_channel = self.control_channel_class( + self + ) # type:ignore[abstract,call-arg] return self._control_channel @property def hb_channel(self): if self._hb_channel is None: - self._hb_channel = self.hb_channel_class(self) # type:ignore[operator] + self._hb_channel = self.hb_channel_class(self) # type:ignore[abstract,call-arg] return self._hb_channel # Methods for sending specific messages diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 30daca885..13b17217a 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -47,7 +47,7 @@ class InProcessKernel(IPythonKernel): # Kernel interface # ------------------------------------------------------------------------- - shell_class = Type(allow_none=True) + shell_class = Type(allow_none=True) # type:ignore[assignment] _underlying_iopub_socket = Instance(DummySocket, ()) iopub_thread: IOPubThread = Instance(IOPubThread) # type:ignore[assignment] diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 990c5558b..588218505 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -118,7 +118,7 @@ def __init__(self, **kwargs): ) # Initialize the InteractiveShell subclass - self.shell = self.shell_class.instance( # type:ignore[attr-defined] + self.shell = self.shell_class.instance( parent=self, profile_dir=self.profile_dir, user_module=self.user_module, @@ -126,21 +126,21 @@ def __init__(self, **kwargs): kernel=self, compiler_class=XCachingCompiler, ) - self.shell.displayhook.session = self.session + self.shell.displayhook.session = self.session # type:ignore[attr-defined] jupyter_session_name = os.environ.get('JPY_SESSION_NAME') if jupyter_session_name: self.shell.user_ns['__session__'] = jupyter_session_name - self.shell.displayhook.pub_socket = self.iopub_socket - self.shell.displayhook.topic = self._topic("execute_result") - self.shell.display_pub.session = self.session - self.shell.display_pub.pub_socket = self.iopub_socket + self.shell.displayhook.pub_socket = self.iopub_socket # type:ignore[attr-defined] + self.shell.displayhook.topic = self._topic("execute_result") # type:ignore[attr-defined] + self.shell.display_pub.session = self.session # type:ignore[attr-defined] + self.shell.display_pub.pub_socket = self.iopub_socket # type:ignore[attr-defined] self.comm_manager = comm.get_comm_manager() assert isinstance(self.comm_manager, HasTraits) - self.shell.configurables.append(self.comm_manager) + self.shell.configurables.append(self.comm_manager) # type:ignore[arg-type] comm_msg_types = ["comm_open", "comm_msg", "comm_close"] for msg_type in comm_msg_types: self.shell_handlers[msg_type] = getattr(self.comm_manager, msg_type) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 483f3844c..dae72defa 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -89,12 +89,12 @@ ) # inherit flags&aliases for any IPython shell apps -kernel_aliases.update(shell_aliases) # type:ignore[arg-type] +kernel_aliases.update(shell_aliases) kernel_flags.update(shell_flags) # inherit flags&aliases for Sessions -kernel_aliases.update(session_aliases) # type:ignore[arg-type] -kernel_flags.update(session_flags) # type:ignore[arg-type] +kernel_aliases.update(session_aliases) +kernel_flags.update(session_flags) _ctrl_c_message = """\ NOTE: When using the `ipython kernel` entry point, Ctrl-C will not work. diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index c4b2e312c..617c37a69 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -525,8 +525,8 @@ def data_pub(self): ) self._data_pub = self.data_pub_class(parent=self) # type:ignore[has-type] - self._data_pub.session = self.display_pub.session - self._data_pub.pub_socket = self.display_pub.pub_socket + self._data_pub.session = self.display_pub.session # type:ignore[attr-defined] + self._data_pub.pub_socket = self.display_pub.pub_socket # type:ignore[attr-defined] return self._data_pub @data_pub.setter @@ -562,14 +562,14 @@ def _showtraceback(self, etype, evalue, stb): # Send exception info over pub socket for other clients than the caller # to pick up topic = None - if dh.topic: - topic = dh.topic.replace(b"execute_result", b"error") + if dh.topic: # type:ignore[attr-defined] + topic = dh.topic.replace(b"execute_result", b"error") # type:ignore[attr-defined] - dh.session.send( - dh.pub_socket, + dh.session.send( # type:ignore[attr-defined] + dh.pub_socket, # type:ignore[attr-defined] "error", json_clean(exc_content), - dh.parent_header, + dh.parent_header, # type:ignore[attr-defined] ident=topic, ) @@ -590,8 +590,8 @@ def set_next_input(self, text, replace=False): def set_parent(self, parent): """Set the parent header for associating output with its triggering input""" self.parent_header = parent - self.displayhook.set_parent(parent) - self.display_pub.set_parent(parent) + self.displayhook.set_parent(parent) # type:ignore[attr-defined] + self.display_pub.set_parent(parent) # type:ignore[attr-defined] if hasattr(self, "_data_pub"): self.data_pub.set_parent(parent) try: diff --git a/pyproject.toml b/pyproject.toml index ae96b1020..f34c54826 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,7 @@ matrix.qt.features = [ [tool.hatch.envs.typing] features = ["test"] -dependencies = ["mypy>=1.5.1", "traitlets>=5.10.1"] +dependencies = ["mypy>=1.5.1", "traitlets>=5.11.2", "ipython>=8.16.1"] [tool.hatch.envs.typing.scripts] test = "mypy --install-types --non-interactive {args}" From 966e0a41fc61e7850378ae672e28202eb29b10b0 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 22 Oct 2023 10:05:14 -0500 Subject: [PATCH 1024/1195] Update lint deps and add more typing (#1156) --- ipykernel/connect.py | 20 +++++++++++++------- ipykernel/kernelapp.py | 1 + ipykernel/kernelspec.py | 41 ++++++++++++++++++++++++++--------------- ipykernel/zmqshell.py | 1 + pyproject.toml | 4 ++-- 5 files changed, 43 insertions(+), 24 deletions(-) diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 255716497..3336ced07 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -2,17 +2,21 @@ """ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import json import sys from subprocess import PIPE, Popen -from typing import Any, Dict +from typing import TYPE_CHECKING, Any import jupyter_client from jupyter_client import write_connection_file +if TYPE_CHECKING: + from ipykernel.kernelapp import IPKernelApp -def get_connection_file(app=None): + +def get_connection_file(app: IPKernelApp | None = None) -> str: """Return the path to the connection file of an app Parameters @@ -46,7 +50,9 @@ def _find_connection_file(connection_file): return jupyter_client.find_connection_file(connection_file) -def get_connection_info(connection_file=None, unpack=False): +def get_connection_info( + connection_file: str | None = None, unpack: bool = False +) -> str | dict[str, Any]: """Return the connection information for the current Kernel. Parameters @@ -77,12 +83,12 @@ def get_connection_info(connection_file=None, unpack=False): info = json.loads(info_str) # ensure key is bytes: info["key"] = info.get("key", "").encode() - return info + return info # type:ignore[no-any-return] return info_str -def connect_qtconsole(connection_file=None, argv=None): +def connect_qtconsole(connection_file: str | None = None, argv: list[str] | None = None) -> Popen: """Connect a qtconsole to the current kernel. This is useful for connecting a second qtconsole to a kernel, or to a @@ -111,13 +117,13 @@ def connect_qtconsole(connection_file=None, argv=None): cmd = ";".join(["from qtconsole import qtconsoleapp", "qtconsoleapp.main()"]) - kwargs: Dict[str, Any] = {} + kwargs: dict[str, Any] = {} # Launch the Qt console in a separate session & process group, so # interrupting the kernel doesn't kill it. kwargs["start_new_session"] = True return Popen( - [sys.executable, "-c", cmd, "--existing", cf, *argv], # noqa + [sys.executable, "-c", cmd, "--existing", cf, *argv], # noqa: S603 stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != "win32"), diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index dae72defa..de4682f86 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -275,6 +275,7 @@ def write_connection_file(self): # original file had port number 0, we update with the actual port # used. existing_connection_info = get_connection_info(cf, unpack=True) + assert isinstance(existing_connection_info, dict) connection_info = dict(existing_connection_info, **connection_info) if connection_info == existing_connection_info: self.log.debug("Connection file %s with current information already exists", cf) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 320afff98..6404c2a19 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -3,6 +3,8 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations + import errno import json import os @@ -10,6 +12,7 @@ import stat import sys import tempfile +from typing import Any from jupyter_client.kernelspec import KernelSpecManager from traitlets import Unicode @@ -27,7 +30,11 @@ RESOURCES = pjoin(os.path.dirname(__file__), "resources") -def make_ipkernel_cmd(mod="ipykernel_launcher", executable=None, extra_arguments=None): +def make_ipkernel_cmd( + mod: str = "ipykernel_launcher", + executable: str | None = None, + extra_arguments: list[str] | None = None, +) -> list[str]: """Build Popen command list for launching an IPython kernel. Parameters @@ -52,7 +59,7 @@ def make_ipkernel_cmd(mod="ipykernel_launcher", executable=None, extra_arguments return arguments -def get_kernel_dict(extra_arguments=None): +def get_kernel_dict(extra_arguments: list[str] | None = None) -> dict[str, Any]: """Construct dict for kernel.json""" return { "argv": make_ipkernel_cmd(extra_arguments=extra_arguments), @@ -62,7 +69,11 @@ def get_kernel_dict(extra_arguments=None): } -def write_kernel_spec(path=None, overrides=None, extra_arguments=None): +def write_kernel_spec( + path: str | None = None, + overrides: dict[str, Any] | None = None, + extra_arguments: list[str] | None = None, +) -> str: """Write a kernel spec directory to `path` If `path` is not specified, a temporary directory is created. @@ -93,14 +104,14 @@ def write_kernel_spec(path=None, overrides=None, extra_arguments=None): def install( - kernel_spec_manager=None, - user=False, - kernel_name=KERNEL_NAME, - display_name=None, - prefix=None, - profile=None, - env=None, -): + kernel_spec_manager: KernelSpecManager | None = None, + user: bool = False, + kernel_name: str = KERNEL_NAME, + display_name: str | None = None, + prefix: str | None = None, + profile: str | None = None, + env: dict[str, str] | None = None, +) -> str: """Install the IPython kernelspec for Jupyter Parameters @@ -136,7 +147,7 @@ def install( # kernel_name is specified and display_name is not # default display_name to kernel_name display_name = kernel_name - overrides = {} + overrides: dict[str, Any] = {} if display_name: overrides["display_name"] = display_name if profile: @@ -154,7 +165,7 @@ def install( ) # cleanup afterward shutil.rmtree(path) - return dest + return dest # type:ignore[no-any-return] # Entrypoint @@ -167,13 +178,13 @@ class InstallIPythonKernelSpecApp(Application): name = Unicode("ipython-kernel-install") - def initialize(self, argv=None): + def initialize(self, argv: list[str] | None = None) -> None: """Initialize the app.""" if argv is None: argv = sys.argv[1:] self.argv = argv - def start(self): + def start(self) -> None: """Start the app.""" import argparse diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 617c37a69..d381a5c98 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -378,6 +378,7 @@ def connect_info(self, arg_s): if jupyter_runtime_dir() == os.path.dirname(connection_file): connection_file = os.path.basename(connection_file) + assert isinstance(info, str) print(info + "\n") print( f"Paste the above JSON into a file, and connect with:\n" diff --git a/pyproject.toml b/pyproject.toml index f34c54826..8f9ac8942 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,12 +112,12 @@ matrix.qt.features = [ [tool.hatch.envs.typing] features = ["test"] -dependencies = ["mypy>=1.5.1", "traitlets>=5.11.2", "ipython>=8.16.1"] +dependencies = ["mypy>=1.6.0", "traitlets>=5.11.2", "ipython>=8.16.1"] [tool.hatch.envs.typing.scripts] test = "mypy --install-types --non-interactive {args}" [tool.hatch.envs.lint] -dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.0.287"] +dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.1.1"] detached = true [tool.hatch.envs.lint.scripts] style = [ From 58cc39d8b050f34cd1a767bdee7512a52887e610 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 24 Oct 2023 23:55:07 +0000 Subject: [PATCH 1025/1195] Publish 6.26.0 SHA256 hashes: ipykernel-6.26.0-py3-none-any.whl: 3ba3dc97424b87b31bb46586b5167b3161b32d7820b9201a9e698c71e271602c ipykernel-6.26.0.tar.gz: 553856658eb8430bbe9653ea041a41bff63e9606fc4628873fc92a6cf3abd404 --- CHANGELOG.md | 24 ++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0a0d1444..21540384d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ +## 6.26.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.25.2...966e0a41fc61e7850378ae672e28202eb29b10b0)) + +### Maintenance and upkeep improvements + +- Update lint deps and add more typing [#1156](https://github.com/ipython/ipykernel/pull/1156) ([@blink1073](https://github.com/blink1073)) +- Update typing for traitlets 5.11 [#1154](https://github.com/ipython/ipykernel/pull/1154) ([@blink1073](https://github.com/blink1073)) +- chore: update pre-commit hooks [#1153](https://github.com/ipython/ipykernel/pull/1153) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Update IPython Typing Usage [#1152](https://github.com/ipython/ipykernel/pull/1152) ([@blink1073](https://github.com/blink1073)) +- Update typing [#1150](https://github.com/ipython/ipykernel/pull/1150) ([@blink1073](https://github.com/blink1073)) +- Use sp-repo-review [#1146](https://github.com/ipython/ipykernel/pull/1146) ([@blink1073](https://github.com/blink1073)) +- Bump actions/checkout from 3 to 4 [#1144](https://github.com/ipython/ipykernel/pull/1144) ([@dependabot](https://github.com/dependabot)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-09-04&to=2023-10-24&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-09-04..2023-10-24&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2023-09-04..2023-10-24&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-09-04..2023-10-24&type=Issues) + + + ## 6.25.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.25.1...9d3f7aecc4fe68f14ebcc4dad4b65b19676e820e)) @@ -18,8 +40,6 @@ [@anntzer](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aanntzer+updated%3A2023-08-07..2023-09-04&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-08-07..2023-09-04&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2023-08-07..2023-09-04&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2023-08-07..2023-09-04&type=Issues) - - ## 6.25.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.25.0...18e54f31725d6645dd71a8749c9e1eb28281f804)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index c09e05b4a..efcef73aa 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.25.2" +__version__ = "6.26.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 2a8adb921a562e5d24376b2de40f7e31e02f50f8 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 24 Oct 2023 20:40:50 -0500 Subject: [PATCH 1026/1195] Update typing for jupyter_client 8.5 (#1160) --- ipykernel/kernelspec.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 6404c2a19..414b1d951 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -165,7 +165,7 @@ def install( ) # cleanup afterward shutil.rmtree(path) - return dest # type:ignore[no-any-return] + return dest # Entrypoint diff --git a/pyproject.toml b/pyproject.toml index 8f9ac8942..564803629 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,7 @@ matrix.qt.features = [ [tool.hatch.envs.typing] features = ["test"] -dependencies = ["mypy>=1.6.0", "traitlets>=5.11.2", "ipython>=8.16.1"] +dependencies = ["mypy>=1.6.0", "traitlets>=5.12.0", "ipython>=8.16.1", "jupyter_client>=8.5"] [tool.hatch.envs.typing.scripts] test = "mypy --install-types --non-interactive {args}" From 76aca773d90ad931722c762f0cc1492e000d2944 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sun, 29 Oct 2023 09:39:36 -0500 Subject: [PATCH 1027/1195] Adopt ruff format (#1161) --- .pre-commit-config.yaml | 12 ++++------ docs/conf.py | 2 +- ipykernel/comm/comm.py | 14 +++++------- ipykernel/eventloops.py | 42 +++++++++++++++++------------------ ipykernel/inprocess/client.py | 4 +--- ipykernel/iostream.py | 4 ++-- ipykernel/ipkernel.py | 4 ++-- pyproject.toml | 18 ++++++--------- tests/test_comm.py | 12 +++++----- tests/test_eventloop.py | 24 ++++++++++---------- tests/test_io.py | 6 ++--- 11 files changed, 64 insertions(+), 78 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 24ac14c0a..3f9c01b81 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-case-conflict - id: check-ast @@ -45,11 +45,6 @@ repos: - id: blacken-docs additional_dependencies: [black==23.7.0] - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.9.1 - hooks: - - id: black - - repo: https://github.com/codespell-project/codespell rev: "v2.2.6" hooks: @@ -64,13 +59,14 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.292 + rev: v0.1.3 hooks: - id: ruff args: ["--fix", "--show-fixes"] + - id: ruff-format - repo: https://github.com/scientific-python/cookie - rev: "2023.09.21" + rev: "2023.10.27" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/docs/conf.py b/docs/conf.py index 990da1e21..f3bb74bf5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -136,7 +136,7 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -# html_theme_options = {} +html_theme_options = {"navigation_with_keys": False} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 862e3bcf1..186b4fe79 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -73,7 +73,7 @@ def _default_comm_id(self): return uuid.uuid4().hex def __init__( - self, target_name='', data=None, metadata=None, buffers=None, show_warning=True, **kwargs + self, target_name="", data=None, metadata=None, buffers=None, show_warning=True, **kwargs ): """Initialize a comm.""" if show_warning: @@ -85,16 +85,14 @@ def __init__( ) # Handle differing arguments between base classes. - had_kernel = 'kernel' in kwargs - kernel = kwargs.pop('kernel', None) + had_kernel = "kernel" in kwargs + kernel = kwargs.pop("kernel", None) if target_name: - kwargs['target_name'] = target_name - BaseComm.__init__( - self, data=data, metadata=metadata, buffers=buffers, **kwargs - ) # type:ignore[call-arg] + kwargs["target_name"] = target_name + BaseComm.__init__(self, data=data, metadata=metadata, buffers=buffers, **kwargs) # type:ignore[call-arg] # only re-add kernel if explicitly provided if had_kernel: - kwargs['kernel'] = kernel + kwargs["kernel"] = kernel traitlets.config.LoggingConfigurable.__init__(self, **kwargs) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index ef54f4105..f954505db 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -95,7 +95,7 @@ def process_stream_events(): if not hasattr(kernel, "_qt_notifier"): fd = kernel.shell_stream.getsockopt(zmq.FD) kernel._qt_notifier = QtCore.QSocketNotifier( - fd, enum_helper('QtCore.QSocketNotifier.Type').Read, kernel.app.qt_event_loop + fd, enum_helper("QtCore.QSocketNotifier.Type").Read, kernel.app.qt_event_loop ) kernel._qt_notifier.activated.connect(process_stream_events) else: @@ -125,7 +125,7 @@ def loop_qt(kernel): # `exec` blocks until there's ZMQ activity. el = kernel.app.qt_event_loop # for brevity - el.exec() if hasattr(el, 'exec') else el.exec_() + el.exec() if hasattr(el, "exec") else el.exec_() kernel.app._in_event_loop = False @@ -465,16 +465,16 @@ def set_qt_api_env_from_gui(gui): loaded = loaded_api() qt_env2gui = { - QT_API_PYSIDE2: 'qt5', - QT_API_PYQT5: 'qt5', - QT_API_PYSIDE6: 'qt6', - QT_API_PYQT6: 'qt6', + QT_API_PYSIDE2: "qt5", + QT_API_PYQT5: "qt5", + QT_API_PYSIDE6: "qt6", + QT_API_PYQT6: "qt6", } - if loaded is not None and gui != 'qt' and qt_env2gui[loaded] != gui: - print(f'Cannot switch Qt versions for this session; you must use {qt_env2gui[loaded]}.') + if loaded is not None and gui != "qt" and qt_env2gui[loaded] != gui: + print(f"Cannot switch Qt versions for this session; you must use {qt_env2gui[loaded]}.") return - if qt_api is not None and gui != 'qt': + if qt_api is not None and gui != "qt": if qt_env2gui[qt_api] != gui: print( f'Request for "{gui}" will be ignored because `QT_API` ' @@ -482,7 +482,7 @@ def set_qt_api_env_from_gui(gui): ) return else: - if gui == 'qt5': + if gui == "qt5": try: import PyQt5 # noqa @@ -494,7 +494,7 @@ def set_qt_api_env_from_gui(gui): os.environ["QT_API"] = "pyside2" except ImportError: os.environ["QT_API"] = "pyqt5" - elif gui == 'qt6': + elif gui == "qt6": try: import PyQt6 # noqa @@ -506,10 +506,10 @@ def set_qt_api_env_from_gui(gui): os.environ["QT_API"] = "pyside6" except ImportError: os.environ["QT_API"] = "pyqt6" - elif gui == 'qt': + elif gui == "qt": # Don't set QT_API; let IPython logic choose the version. - if 'QT_API' in os.environ: - del os.environ['QT_API'] + if "QT_API" in os.environ: + del os.environ["QT_API"] else: print(f'Unrecognized Qt version: {gui}. Should be "qt5", "qt6", or "qt".') return @@ -519,7 +519,7 @@ def set_qt_api_env_from_gui(gui): from IPython.external.qt_for_kernel import QtCore, QtGui # noqa except Exception as e: # Clear the environment variable for the next attempt. - if 'QT_API' in os.environ: + if "QT_API" in os.environ: del os.environ["QT_API"] print(f"QT_API couldn't be set due to error {e}") return @@ -527,7 +527,7 @@ def set_qt_api_env_from_gui(gui): def make_qt_app_for_kernel(gui, kernel): """Sets the `QT_API` environment variable if it isn't already set.""" - if hasattr(kernel, 'app'): + if hasattr(kernel, "app"): # Kernel is already running a Qt event loop, so there's no need to # create another app for it. return @@ -558,18 +558,16 @@ def enable_gui(gui, kernel=None): raise RuntimeError(msg) if gui is None: # User wants to turn off integration; clear any evidence if Qt was the last one. - if hasattr(kernel, 'app'): - delattr(kernel, 'app') + if hasattr(kernel, "app"): + delattr(kernel, "app") else: - if gui.startswith('qt'): + if gui.startswith("qt"): # Prepare the kernel here so any exceptions are displayed in the client. make_qt_app_for_kernel(gui, kernel) loop = loop_map[gui] if ( - loop - and kernel.eventloop is not None - and kernel.eventloop is not loop # type:ignore[unreachable] + loop and kernel.eventloop is not None and kernel.eventloop is not loop # type:ignore[unreachable] ): msg = "Cannot activate multiple GUI eventloops" # type:ignore[unreachable] raise RuntimeError(msg) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index d0ebfd226..6250302d5 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -90,9 +90,7 @@ def stdin_channel(self): @property def control_channel(self): if self._control_channel is None: - self._control_channel = self.control_channel_class( - self - ) # type:ignore[abstract,call-arg] + self._control_channel = self.control_channel_class(self) # type:ignore[abstract,call-arg] return self._control_channel @property diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 0c8a2fa9d..1f1d1a7c3 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -387,7 +387,7 @@ def _watch_pipe_fd(self): try: bts = os.read(self._fid, PIPE_BUFFER_SIZE) while bts and self._should_watch: - self.write(bts.decode(errors='replace')) + self.write(bts.decode(errors="replace")) os.write(self._original_stdstream_copy, bts) bts = os.read(self._fid, PIPE_BUFFER_SIZE) except Exception: @@ -531,7 +531,7 @@ def close(self): self._should_watch = False # thread won't wake unless there's something to read # writing something after _should_watch will not be echoed - os.write(self._original_stdstream_fd, b'\0') + os.write(self._original_stdstream_fd, b"\0") self.watch_fd_thread.join() # restore original FDs os.dup2(self._original_stdstream_copy, self._original_stdstream_fd) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 588218505..4b2cc53d3 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -128,9 +128,9 @@ def __init__(self, **kwargs): ) self.shell.displayhook.session = self.session # type:ignore[attr-defined] - jupyter_session_name = os.environ.get('JPY_SESSION_NAME') + jupyter_session_name = os.environ.get("JPY_SESSION_NAME") if jupyter_session_name: - self.shell.user_ns['__session__'] = jupyter_session_name + self.shell.user_ns["__session__"] = jupyter_session_name self.shell.displayhook.pub_socket = self.iopub_socket # type:ignore[attr-defined] self.shell.displayhook.topic = self._topic("execute_result") # type:ignore[attr-defined] diff --git a/pyproject.toml b/pyproject.toml index 564803629..1868b40b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -117,17 +117,17 @@ dependencies = ["mypy>=1.6.0", "traitlets>=5.12.0", "ipython>=8.16.1", "jupyter_ test = "mypy --install-types --non-interactive {args}" [tool.hatch.envs.lint] -dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.1.1"] +dependencies = ["mdformat>0.7", "ruff==0.1.3"] detached = true [tool.hatch.envs.lint.scripts] style = [ "ruff {args:.}", - "black --check --diff {args:.}", + "ruff format {args:.}", "mdformat --check {args:docs *.md}" ] fmt = [ - "black {args:.}", "ruff --fix {args:.}", + "ruff format {args:.}", "mdformat {args:docs *.md}" ] @@ -229,14 +229,11 @@ omit = [ "ipykernel/pylab/*", ] -[tool.black] -line-length = 100 -skip-string-normalization = true -target-version = ["py37"] - [tool.ruff] -target-version = "py37" +target-version = "py38" line-length = 100 + +[tool.ruff.lint] select = [ "A", "B", @@ -248,7 +245,6 @@ select = [ "FBT", "I", "ICN", - "ISC", "N", "PLC", "PLE", @@ -310,7 +306,7 @@ unfixable = [ "RUF100", ] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] # B011 Do not call assert False since python -O removes these calls # F841 local variable 'foo' is assigned to but never used # C408 Unnecessary `dict` call diff --git a/tests/test_comm.py b/tests/test_comm.py index 728731ce0..c9d4df77f 100644 --- a/tests/test_comm.py +++ b/tests/test_comm.py @@ -43,7 +43,7 @@ def foo(comm, msg): comm.close() def fizz(comm, msg): - raise RuntimeError('hi') + raise RuntimeError("hi") def on_close(msg): msgs.append(msg) @@ -69,15 +69,15 @@ def on_msg(msg): Kernel.clear_instance() assert manager.get_comm(comm.comm_id) == comm - assert manager.get_comm('foo') is None + assert manager.get_comm("foo") is None - msg = dict(content=dict(comm_id=comm.comm_id, target_name='foo')) + msg = dict(content=dict(comm_id=comm.comm_id, target_name="foo")) manager.comm_open(None, None, msg) assert len(msgs) == 1 - msg['content']['target_name'] = 'bar' + msg["content"]["target_name"] = "bar" manager.comm_open(None, None, msg) assert len(msgs) == 1 - msg = dict(content=dict(comm_id=comm.comm_id, target_name='fizz')) + msg = dict(content=dict(comm_id=comm.comm_id, target_name="fizz")) manager.comm_open(None, None, msg) assert len(msgs) == 1 @@ -86,7 +86,7 @@ def on_msg(msg): msg = dict(content=dict(comm_id=comm.comm_id)) manager.comm_msg(None, None, msg) assert len(msgs) == 2 - msg['content']['comm_id'] = 'foo' + msg["content"]["comm_id"] = "foo" manager.comm_msg(None, None, msg) assert len(msgs) == 2 diff --git a/tests/test_eventloop.py b/tests/test_eventloop.py index 26924cfbe..ee9a68fca 100644 --- a/tests/test_eventloop.py +++ b/tests/test_eventloop.py @@ -22,19 +22,19 @@ qt_guis_avail = [] -gui_to_module = {'qt6': 'PySide6', 'qt5': 'PyQt5'} +gui_to_module = {"qt6": "PySide6", "qt5": "PyQt5"} def _get_qt_vers(): """If any version of Qt is available, this will populate `guis_avail` with 'qt' and 'qtx'. Due to the import mechanism, we can't import multiple versions of Qt in one session.""" - for gui in ['qt6', 'qt5']: - print(f'Trying {gui}') + for gui in ["qt6", "qt5"]: + print(f"Trying {gui}") try: __import__(gui_to_module[gui]) qt_guis_avail.append(gui) - if 'QT_API' in os.environ: - del os.environ['QT_API'] + if "QT_API" in os.environ: + del os.environ["QT_API"] except ImportError: pass # that version of Qt isn't available. @@ -126,7 +126,7 @@ def test_cocoa_loop(kernel): @pytest.mark.skipif( - len(qt_guis_avail) == 0, reason='No viable version of PyQt or PySide installed.' + len(qt_guis_avail) == 0, reason="No viable version of PyQt or PySide installed." ) def test_qt_enable_gui(kernel, capsys): gui = qt_guis_avail[0] @@ -134,10 +134,10 @@ def test_qt_enable_gui(kernel, capsys): enable_gui(gui, kernel) # We store the `QApplication` instance in the kernel. - assert hasattr(kernel, 'app') + assert hasattr(kernel, "app") # And the `QEventLoop` is added to `app`:` - assert hasattr(kernel.app, 'qt_event_loop') + assert hasattr(kernel.app, "qt_event_loop") # Don't create another app even if `gui` is the same. app = kernel.app @@ -146,18 +146,18 @@ def test_qt_enable_gui(kernel, capsys): # Event loop integration can be turned off. enable_gui(None, kernel) - assert not hasattr(kernel, 'app') + assert not hasattr(kernel, "app") # But now we're stuck with this version of Qt for good; can't switch. - for not_gui in ['qt6', 'qt5']: + for not_gui in ["qt6", "qt5"]: if not_gui not in qt_guis_avail: break enable_gui(not_gui, kernel) captured = capsys.readouterr() - assert captured.out == f'Cannot switch Qt versions for this session; you must use {gui}.\n' + assert captured.out == f"Cannot switch Qt versions for this session; you must use {gui}.\n" # Check 'qt' gui, which means "the best available" enable_gui(None, kernel) - enable_gui('qt', kernel) + enable_gui("qt", kernel) assert gui_to_module[gui] in str(kernel.app) diff --git a/tests/test_io.py b/tests/test_io.py index 98f047899..a7691ee5c 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -117,7 +117,7 @@ def test_outstream(iopub_thread): async def test_event_pipe_gc(iopub_thread): - session = Session(key=b'abc') + session = Session(key=b"abc") stream = OutStream( session, iopub_thread, @@ -150,7 +150,7 @@ async def run_gc(): def subprocess_test_echo_watch(): # handshake Pub subscription - session = Session(key=b'abc') + session = Session(key=b"abc") # use PUSH socket to avoid subscription issues with zmq.Context() as ctx, ctx.socket(zmq.PUSH) as pub: @@ -199,7 +199,7 @@ def test_echo_watch(ctx): s = ctx.socket(zmq.PULL) port = s.bind_to_random_port("tcp://127.0.0.1") url = f"tcp://127.0.0.1:{port}" - session = Session(key=b'abc') + session = Session(key=b"abc") stdout_chunks = [] with s: env = dict(os.environ) From 3c4b5bf055145aefefcbe33e1219952441c9ee08 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 1 Nov 2023 20:30:31 -0500 Subject: [PATCH 1028/1195] Update typing for traitlets 5.13 (#1162) --- ipykernel/inprocess/ipkernel.py | 2 ++ ipykernel/kernelapp.py | 4 ++-- ipykernel/kernelbase.py | 15 ++++++++------- pyproject.toml | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 13b17217a..cbbe85278 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -104,6 +104,7 @@ def _input_request(self, prompt, ident, parent, password=False): assert self.session is not None msg = self.session.msg("input_request", content, parent) for frontend in self.frontends: + assert frontend is not None if frontend.session.session == parent["header"]["session"]: frontend.stdin_channel.call_handlers(msg) break @@ -138,6 +139,7 @@ def _io_dispatch(self, change): assert self.session is not None ident, msg = self.session.recv(self.iopub_socket.io_thread.socket, copy=False) for frontend in self.frontends: + assert frontend is not None frontend.iopub_channel.call_handlers(msg) # ------ Trait initializers ----------------------------------------------- diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index de4682f86..af9629015 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -443,7 +443,7 @@ def log_connection_info(self): self.log.info(line) # also raw print to the terminal if no parent_handle (`ipython kernel`) # unless log-level is CRITICAL (--quiet) - if not self.parent_handle and int(self.log_level) < logging.CRITICAL: + if not self.parent_handle and int(self.log_level) < logging.CRITICAL: # type:ignore[call-overload] print(_ctrl_c_message, file=sys.__stdout__) for line in lines: print(line, file=sys.__stdout__) @@ -700,7 +700,7 @@ def initialize(self, argv=None): except Exception: # Catch exception when initializing signal fails, eg when running the # kernel on a separate thread - if int(self.log_level) < logging.CRITICAL: + if int(self.log_level) < logging.CRITICAL: # type:ignore[call-overload] self.log.error("Unable to initialize signal:", exc_info=True) self.init_kernel() # shell init steps diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 6d06d4ab5..8f3c0a50a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import asyncio import concurrent.futures @@ -80,7 +81,7 @@ class Kernel(SingletonConfigurable): # attribute to override with a GUI eventloop = Any(None) - processes: t.Dict[str, psutil.Process] = {} + processes: dict[str, psutil.Process] = {} @observe("eventloop") def _update_eventloop(self, change): @@ -93,7 +94,7 @@ def _update_eventloop(self, change): profile_dir = Instance("IPython.core.profiledir.ProfileDir", allow_none=True) shell_stream = Instance(ZMQStream, allow_none=True) - shell_streams = List( + shell_streams: List[t.Any] = List( help="""Deprecated shell_streams alias. Use shell_stream .. versionchanged:: 6.0 @@ -153,10 +154,10 @@ def _default_ident(self): # This should be overridden by wrapper kernels that implement any real # language. - language_info: t.Dict[str, object] = {} + language_info: dict[str, object] = {} # any links that should go in the help menu - help_links = List() + help_links: List[dict[str, str]] = List() # Experimental option to break in non-user code. # The ipykernel source is in the call stack, so the user @@ -180,7 +181,7 @@ def _default_ident(self): # track associations with current request _allow_stdin = Bool(False) - _parents = Dict({"shell": {}, "control": {}}) + _parents: Dict[str, t.Any] = Dict({"shell": {}, "control": {}}) _parent_ident = Dict({"shell": b"", "control": b""}) @property @@ -291,7 +292,7 @@ async def poll_control_queue(self): async def _flush_control_queue(self): """Flush the control queue, wait for processing of any pending messages""" - tracer_future: t.Union[concurrent.futures.Future[object], asyncio.Future[object]] + tracer_future: concurrent.futures.Future[object] | asyncio.Future[object] if self.control_thread: control_loop = self.control_thread.io_loop # concurrent.futures.Futures are threadsafe @@ -945,7 +946,7 @@ async def interrupt_request(self, stream, ident, parent): """Handle an interrupt request.""" if not self.session: return - content: t.Dict[str, t.Any] = {"status": "ok"} + content: dict[str, t.Any] = {"status": "ok"} try: self._send_interrupt_children() except OSError as err: diff --git a/pyproject.toml b/pyproject.toml index 1868b40b3..c06ebb18c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,7 @@ matrix.qt.features = [ [tool.hatch.envs.typing] features = ["test"] -dependencies = ["mypy>=1.6.0", "traitlets>=5.12.0", "ipython>=8.16.1", "jupyter_client>=8.5"] +dependencies = ["mypy>=1.6.0", "traitlets>=5.13.0", "ipython>=8.16.1", "jupyter_client>=8.5"] [tool.hatch.envs.typing.scripts] test = "mypy --install-types --non-interactive {args}" From 22b2d2189ea7c9c1ead5880cd6b569cab8fe2cd1 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Fri, 3 Nov 2023 09:34:38 -0500 Subject: [PATCH 1029/1195] Clean up typing config (#1163) --- .github/workflows/ci.yml | 2 +- .pre-commit-config.yaml | 15 +++++++++++ ipykernel/comm/comm.py | 2 +- ipykernel/comm/manager.py | 2 +- ipykernel/connect.py | 4 ++- ipykernel/eventloops.py | 4 +-- ipykernel/iostream.py | 2 +- ipykernel/ipkernel.py | 2 +- ipykernel/kernelapp.py | 5 ++-- ipykernel/kernelbase.py | 5 ++-- ipykernel/pylab/backend_inline.py | 2 +- ipykernel/pylab/config.py | 2 +- pyproject.toml | 41 ++++++------------------------- 13 files changed, 41 insertions(+), 47 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8187aa0d3..64bf5fb67 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,7 +84,7 @@ jobs: - name: Run Linters run: | hatch run typing:test - hatch run lint:style + hatch run lint:build pipx run interrogate -vv . pipx run doc8 --max-line-length=200 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3f9c01b81..946d63fdb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,6 +39,21 @@ repos: - id: prettier types_or: [yaml, html, json] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: "v1.6.1" + hooks: + - id: mypy + files: ipykernel + stages: [manual] + args: ["--install-types", "--non-interactive"] + additional_dependencies: + [ + "traitlets>=5.13", + "ipython>=8.16.1", + "jupyter_client>=8.5", + "appnope", + ] + - repo: https://github.com/adamchainz/blacken-docs rev: "1.16.0" hooks: diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 186b4fe79..bb9b077d5 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -16,7 +16,7 @@ # this is the class that will be created if we do comm.create_comm -class BaseComm(comm.base_comm.BaseComm): +class BaseComm(comm.base_comm.BaseComm): # type:ignore[misc] """The base class for comms.""" kernel: Optional["Kernel"] = None diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 7b0de6933..7e4850630 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -14,7 +14,7 @@ logger = logging.getLogger("ipykernel.comm") -class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): +class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): # type:ignore[misc] """A comm manager.""" kernel = traitlets.Instance("ipykernel.kernelbase.Kernel") diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 3336ced07..9f851caab 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -88,7 +88,9 @@ def get_connection_info( return info_str -def connect_qtconsole(connection_file: str | None = None, argv: list[str] | None = None) -> Popen: +def connect_qtconsole( + connection_file: str | None = None, argv: list[str] | None = None +) -> Popen[Any]: """Connect a qtconsole to the current kernel. This is useful for connecting a second qtconsole to a kernel, or to a diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index f954505db..bf1e3d0d6 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -168,7 +168,7 @@ def wake(): # We have to put the wx.Timer in a wx.Frame for it to fire properly. # We make the Frame hidden when we create it in the main app below. - class TimerFrame(wx.Frame): + class TimerFrame(wx.Frame): # type:ignore[misc] def __init__(self, func): wx.Frame.__init__(self, None, -1) self.timer = wx.Timer(self) @@ -182,7 +182,7 @@ def on_timer(self, event): # We need a custom wx.App to create our Frame subclass that has the # wx.Timer to defer back to the tornado event loop. - class IPWxApp(wx.App): + class IPWxApp(wx.App): # type:ignore[misc] def OnInit(self): self.frame = TimerFrame(wake) self.frame.Show(False) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 1f1d1a7c3..3588e5884 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -69,7 +69,7 @@ def __init__(self, socket, pipe=False): self._event_pipes: Dict[threading.Thread, Any] = {} self._event_pipe_gc_lock: threading.Lock = threading.Lock() self._event_pipe_gc_seconds: float = 10 - self._event_pipe_gc_task: Optional[asyncio.Task] = None + self._event_pipe_gc_task: Optional[asyncio.Task[Any]] = None self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main, name="IOPub") self.thread.daemon = True diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 4b2cc53d3..6e358c7d1 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -147,7 +147,7 @@ def __init__(self, **kwargs): if _use_appnope() and self._darwin_app_nap: # Disable app-nap as the kernel is not a gui but can have guis - import appnope + import appnope # type:ignore[import-untyped] appnope.nope() diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index af9629015..5bfcccbf5 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import atexit import errno @@ -10,10 +11,10 @@ import signal import sys import traceback +import typing as t from functools import partial from io import FileIO, TextIOWrapper from logging import StreamHandler -from typing import Optional import zmq from IPython.core.application import ( # type:ignore[attr-defined] @@ -132,7 +133,7 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMix poller = Any() # don't restrict this even though current pollers are all Threads heartbeat = Instance(Heartbeat, allow_none=True) - context: Optional[zmq.Context] = Any() # type:ignore[assignment] + context: zmq.Context[t.Any] | None = Any() # type:ignore[assignment] shell_socket = Any() control_socket = Any() debugpy_socket = Any() diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 8f3c0a50a..79ecddb3f 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1044,7 +1044,8 @@ async def usage_request(self, stream, ident, parent): # Ensure 1) self.processes is updated to only current subprocesses # and 2) we reuse processes when possible (needed for accurate CPU) self.processes = { - process.pid: self.processes.get(process.pid, process) for process in all_processes + process.pid: self.processes.get(process.pid, process) # type:ignore[misc,call-overload] + for process in all_processes } reply_content["kernel_cpu"] = sum( [ @@ -1062,7 +1063,7 @@ async def usage_request(self, stream, ident, parent): cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. - if cpu_percent is not None and cpu_percent != 0.0: + if cpu_percent is not None and cpu_percent != 0.0: # type:ignore[redundant-expr] reply_content["host_cpu_percent"] = cpu_percent reply_content["cpu_count"] = psutil.cpu_count(logical=True) reply_content["host_virtual_memory"] = dict(psutil.virtual_memory()._asdict()) diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index 18e0222a0..cc389ccae 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.backend_inline import * # analysis: ignore # noqa F401 +from matplotlib_inline.backend_inline import * # type:ignore[import-untyped] # analysis: ignore # noqa F401 warnings.warn( "`ipykernel.pylab.backend_inline` is deprecated, directly " diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index 56557b1e1..afd44d19d 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.config import * # analysis: ignore # noqa F401 +from matplotlib_inline.config import * # type:ignore[import-untyped] # analysis: ignore # noqa F401 warnings.warn( "`ipykernel.pylab.config` is deprecated, directly use `matplotlib_inline.config`", diff --git a/pyproject.toml b/pyproject.toml index c06ebb18c..db15f6a18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -111,51 +111,26 @@ matrix.qt.features = [ ] [tool.hatch.envs.typing] -features = ["test"] -dependencies = ["mypy>=1.6.0", "traitlets>=5.13.0", "ipython>=8.16.1", "jupyter_client>=8.5"] +dependencies = ["pre-commit"] +detached = true [tool.hatch.envs.typing.scripts] -test = "mypy --install-types --non-interactive {args}" +test = "pre-commit run --all-files --hook-stage manual mypy" [tool.hatch.envs.lint] -dependencies = ["mdformat>0.7", "ruff==0.1.3"] +dependencies = ["pre-commit"] detached = true [tool.hatch.envs.lint.scripts] -style = [ - "ruff {args:.}", - "ruff format {args:.}", - "mdformat --check {args:docs *.md}" -] -fmt = [ - "ruff --fix {args:.}", - "ruff format {args:.}", - "mdformat {args:docs *.md}" -] +build = ["pre-commit run --all-files ruff"] [tool.mypy] files = "ipykernel" -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_untyped_decorators = true +strict = true +disable_error_code = ["no-untyped-def", "no-untyped-call", "import-not-found"] enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] follow_imports = "normal" -ignore_missing_imports = true -no_implicit_optional = true -no_implicit_reexport = true pretty = true -show_error_context = true show_error_codes = true -strict_equality = true -strict_optional = true -warn_unused_configs = true -warn_redundant_casts = true -warn_return_any = true warn_unreachable = true -warn_unused_ignores = true - -[[tool.mypy.overrides]] -module = "tests.*" -disable_error_code = ["ignore-without-code"] -warn_unreachable = false [tool.pytest.ini_options] minversion = "6.0" @@ -340,4 +315,4 @@ toplevel = ["ipykernel/", "ipykernel_launcher.py"] ignore = ["W002"] [tool.repo-review] -ignore = ["PY007", "PP308", "GH102", "PC140", "MY101"] +ignore = ["PY007", "PP308", "GH102", "MY101"] From d7a113c0fd8186d2b5fb57d4a52aa85075d3c21b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 20:45:34 +0000 Subject: [PATCH 1030/1195] chore: update pre-commit hooks (#1164) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 946d63fdb..5ae0cdcbf 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.0 + rev: 0.27.1 hooks: - id: check-github-workflows @@ -74,7 +74,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.3 + rev: v0.1.4 hooks: - id: ruff args: ["--fix", "--show-fixes"] From bb9f350a6093384ef30eb00c5b4ab3b95550a035 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 9 Nov 2023 07:11:17 -0600 Subject: [PATCH 1031/1195] Clean up ruff config (#1165) --- .pre-commit-config.yaml | 4 +++- pyproject.toml | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5ae0cdcbf..3fefcf03c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -74,11 +74,13 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.4 + rev: v0.1.5 hooks: - id: ruff + types_or: [python, jupyter] args: ["--fix", "--show-fixes"] - id: ruff-format + types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie rev: "2023.10.27" diff --git a/pyproject.toml b/pyproject.toml index db15f6a18..1e5c71a88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,10 @@ test = "pre-commit run --all-files --hook-stage manual mypy" dependencies = ["pre-commit"] detached = true [tool.hatch.envs.lint.scripts] -build = ["pre-commit run --all-files ruff"] +build = [ + "pre-commit run --all-files ruff", + "pre-commit run --all-files ruff-format" +] [tool.mypy] files = "ipykernel" @@ -205,7 +208,6 @@ omit = [ ] [tool.ruff] -target-version = "py38" line-length = 100 [tool.ruff.lint] From 5b72bfe2e937790b6a7771df1ffec3e79bc5f47f Mon Sep 17 00:00:00 2001 From: Joshua James Venter <67124214+jjvraw@users.noreply.github.com> Date: Tue, 21 Nov 2023 13:19:31 +0200 Subject: [PATCH 1032/1195] Extend argument handling of do_execute with cell metadata (#1169) --- ipykernel/ipkernel.py | 11 ++++---- ipykernel/kernelbase.py | 56 ++++++++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 6e358c7d1..fafe02d55 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -23,7 +23,7 @@ from .debugger import Debugger, _is_debugpy_available from .eventloops import _use_appnope from .kernelbase import Kernel as KernelBase -from .kernelbase import _accepts_cell_id +from .kernelbase import _accepts_parameters from .zmqshell import ZMQInteractiveShell try: @@ -347,6 +347,7 @@ async def do_execute( user_expressions=None, allow_stdin=False, *, + cell_meta=None, cell_id=None, ): """Handle code execution.""" @@ -359,7 +360,7 @@ async def do_execute( if hasattr(shell, "run_cell_async") and hasattr(shell, "should_run_async"): run_cell = shell.run_cell_async should_run_async = shell.should_run_async - with_cell_id = _accepts_cell_id(run_cell) + accepts_params = _accepts_parameters(run_cell, ["cell_id"]) else: should_run_async = lambda cell: False # noqa # older IPython, @@ -368,7 +369,7 @@ async def do_execute( async def run_cell(*args, **kwargs): return shell.run_cell(*args, **kwargs) - with_cell_id = _accepts_cell_id(shell.run_cell) + accepts_params = _accepts_parameters(shell.run_cell, ["cell_id"]) try: # default case: runner is asyncio and asyncio is already running # TODO: this should check every case for "are we inside the runner", @@ -390,7 +391,7 @@ async def run_cell(*args, **kwargs): preprocessing_exc_tuple=preprocessing_exc_tuple, ) ): - if with_cell_id: + if accepts_params["cell_id"]: coro = run_cell( code, store_history=store_history, @@ -422,7 +423,7 @@ async def run_cell(*args, **kwargs): # runner isn't already running, # make synchronous call, # letting shell dispatch to loop runners - if with_cell_id: + if accepts_params["cell_id"]: res = shell.run_cell( code, store_history=store_history, diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 79ecddb3f..37ed40e2a 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -63,12 +63,18 @@ from ._version import kernel_protocol_version -def _accepts_cell_id(meth): +def _accepts_parameters(meth, param_names): parameters = inspect.signature(meth).parameters - cid_param = parameters.get("cell_id") - return (cid_param and cid_param.kind == cid_param.KEYWORD_ONLY) or any( - p.kind == p.VAR_KEYWORD for p in parameters.values() - ) + accepts = {param: False for param in param_names} + + for param in param_names: + param_spec = parameters.get(param) + accepts[param] = ( + param_spec + and param_spec.kind in [param_spec.KEYWORD_ONLY, param_spec.POSITIONAL_OR_KEYWORD] + ) or any(p.kind == p.VAR_KEYWORD for p in parameters.values()) + + return accepts class Kernel(SingletonConfigurable): @@ -735,25 +741,28 @@ async def execute_request(self, stream, ident, parent): self.execution_count += 1 self._publish_execute_input(code, parent, self.execution_count) - cell_id = (parent.get("metadata") or {}).get("cellId") + cell_meta = parent.get("metadata", {}) + cell_id = cell_meta.get("cellId") - if _accepts_cell_id(self.do_execute): - reply_content = self.do_execute( - code, - silent, - store_history, - user_expressions, - allow_stdin, - cell_id=cell_id, - ) - else: - reply_content = self.do_execute( - code, - silent, - store_history, - user_expressions, - allow_stdin, - ) + # Check which parameters do_execute can accept + accepts_params = _accepts_parameters(self.do_execute, ["cell_meta", "cell_id"]) + + # Arguments based on the do_execute signature + do_execute_args = { + "code": code, + "silent": silent, + "store_history": store_history, + "user_expressions": user_expressions, + "allow_stdin": allow_stdin, + } + + if accepts_params["cell_meta"]: + do_execute_args["cell_meta"] = cell_meta + if accepts_params["cell_id"]: + do_execute_args["cell_id"] = cell_id + + # Call do_execute with the appropriate arguments + reply_content = self.do_execute(**do_execute_args) if inspect.isawaitable(reply_content): reply_content = await reply_content @@ -793,6 +802,7 @@ def do_execute( user_expressions=None, allow_stdin=False, *, + cell_meta=None, cell_id=None, ): """Execute user code. Must be overridden by subclasses.""" From 465d34483103d23f471a4795fe5fabb9cf7ac3f5 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 21 Nov 2023 05:19:49 -0600 Subject: [PATCH 1033/1195] Update ruff and typings (#1167) --- .pre-commit-config.yaml | 8 +-- docs/conf.py | 14 ++-- hatch_build.py | 9 +-- ipykernel/__init__.py | 12 ++-- ipykernel/_eventloop_macos.py | 2 +- ipykernel/comm/comm.py | 1 + ipykernel/comm/manager.py | 4 +- ipykernel/compiler.py | 5 +- ipykernel/connect.py | 5 +- ipykernel/debugger.py | 66 +++++++++--------- ipykernel/eventloops.py | 14 ++-- ipykernel/gui/gtk3embed.py | 1 - ipykernel/gui/gtkembed.py | 1 - ipykernel/heartbeat.py | 11 ++- ipykernel/inprocess/__init__.py | 8 +-- ipykernel/inprocess/channels.py | 1 - ipykernel/inprocess/ipkernel.py | 2 - ipykernel/inprocess/socket.py | 1 - ipykernel/iostream.py | 43 ++++++------ ipykernel/ipkernel.py | 9 ++- ipykernel/jsonutil.py | 2 +- ipykernel/kernelapp.py | 29 ++++---- ipykernel/kernelbase.py | 34 +++++----- ipykernel/kernelspec.py | 13 ++-- ipykernel/log.py | 3 +- ipykernel/pickleutil.py | 40 +++++------ ipykernel/pylab/backend_inline.py | 2 +- ipykernel/pylab/config.py | 2 +- ipykernel/serialize.py | 6 +- ipykernel/trio_runner.py | 1 + ipykernel/zmqshell.py | 9 ++- pyproject.toml | 97 ++++++++++----------------- tests/conftest.py | 8 +-- tests/inprocess/test_kernel.py | 2 +- tests/inprocess/test_kernelmanager.py | 14 ++-- tests/test_debugger.py | 6 +- tests/test_io.py | 4 +- tests/test_jsonutil.py | 6 +- tests/test_message_spec.py | 2 +- tests/test_parentpoller.py | 4 +- tests/test_pickleutil.py | 2 +- tests/test_zmq_shell.py | 4 +- 42 files changed, 234 insertions(+), 273 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3fefcf03c..5418e5337 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,13 +34,13 @@ repos: [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.0.3" + rev: "v3.1.0" hooks: - id: prettier types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.6.1" + rev: "v1.7.0" hooks: - id: mypy files: ipykernel @@ -74,7 +74,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.5 + rev: v0.1.6 hooks: - id: ruff types_or: [python, jupyter] @@ -83,7 +83,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2023.10.27" + rev: "2023.11.17" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/docs/conf.py b/docs/conf.py index f3bb74bf5..643740ec0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 -# # IPython Kernel documentation build configuration file, created by # sphinx-quickstart on Mon Oct 5 11:32:44 2015. # @@ -14,6 +12,7 @@ import os import shutil +from pathlib import Path from typing import Any, Dict, List # If extensions (or modules to document with autodoc) are in another directory, @@ -38,7 +37,7 @@ ] try: - import enchant # noqa + import enchant extensions += ["sphinxcontrib.spelling"] except ImportError: @@ -72,10 +71,10 @@ # version_ns: Dict[str, Any] = {} -here = os.path.dirname(__file__) -version_py = os.path.join(here, os.pardir, "ipykernel", "_version.py") +here = Path(__file__).parent.resolve() +version_py = Path(here) / os.pardir / "ipykernel" / "_version.py" with open(version_py) as f: - exec(compile(f.read(), version_py, "exec"), version_ns) # noqa + exec(compile(f.read(), version_py, "exec"), version_ns) # The short X.Y version. version = "%i.%i" % version_ns["version_info"][:2] @@ -312,5 +311,4 @@ def setup(app): - here = os.path.dirname(os.path.abspath(__file__)) - shutil.copy(os.path.join(here, "..", "CHANGELOG.md"), "changelog.md") + shutil.copy(Path(here) / ".." / "CHANGELOG.md", "changelog.md") diff --git a/hatch_build.py b/hatch_build.py index 71dc49be3..410b4603e 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -2,6 +2,7 @@ import os import shutil import sys +from pathlib import Path from hatchling.builders.hooks.plugin.interface import BuildHookInterface @@ -11,8 +12,8 @@ class CustomHook(BuildHookInterface): def initialize(self, version, build_data): """Initialize the hook.""" - here = os.path.abspath(os.path.dirname(__file__)) - sys.path.insert(0, here) + here = Path(__file__).parent.resolve() + sys.path.insert(0, str(here)) from ipykernel.kernelspec import make_ipkernel_cmd, write_kernel_spec overrides = {} @@ -28,8 +29,8 @@ def initialize(self, version, build_data): overrides["argv"] = argv - dest = os.path.join(here, "data_kernelspec") - if os.path.exists(dest): + dest = Path(here) / "data_kernelspec" + if Path(dest).exists(): shutil.rmtree(dest) write_kernel_spec(dest, overrides=overrides) diff --git a/ipykernel/__init__.py b/ipykernel/__init__.py index a6c1666d6..bc5fbb726 100644 --- a/ipykernel/__init__.py +++ b/ipykernel/__init__.py @@ -1,5 +1,7 @@ -from ._version import __version__ # noqa -from ._version import kernel_protocol_version # noqa -from ._version import kernel_protocol_version_info # noqa -from ._version import version_info # noqa -from .connect import * # noqa +from ._version import ( + __version__, + kernel_protocol_version, + kernel_protocol_version_info, + version_info, +) +from .connect import * diff --git a/ipykernel/_eventloop_macos.py b/ipykernel/_eventloop_macos.py index f07ce6ffc..3a6692fce 100644 --- a/ipykernel/_eventloop_macos.py +++ b/ipykernel/_eventloop_macos.py @@ -75,7 +75,7 @@ def C(classname): CFRunLoopAddTimer.restype = None CFRunLoopAddTimer.argtypes = [void_p, void_p, void_p] -kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, "kCFRunLoopCommonModes") # noqa +kCFRunLoopCommonModes = void_p.in_dll(CoreFoundation, "kCFRunLoopCommonModes") def _NSApp(): diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index bb9b077d5..1747d4ce7 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -67,6 +67,7 @@ class Comm(BaseComm, traitlets.config.LoggingConfigurable): def _default_kernel(self): if Kernel.initialized(): return Kernel.instance() + return None @default("comm_id") def _default_comm_id(self): diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index 7e4850630..aaef027ce 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -49,13 +49,13 @@ def comm_open(self, stream, ident, msg): f(comm, msg) return except Exception: - logger.error("Exception opening comm with target: %s", target_name, exc_info=True) + logger.error("Exception opening comm with target: %s", target_name, exc_info=True) # noqa: G201 # Failure. try: comm.close() except Exception: - logger.error( + logger.error( # noqa: G201 """Could not close comm during `comm_open` failure clean-up. The comm may not have been opened yet.""", exc_info=True, diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index fe5561594..0657f5693 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -45,7 +45,7 @@ def murmur2_x86(data, seed): return h -convert_to_long_pathname = lambda filename: filename # noqa +convert_to_long_pathname = lambda filename: filename if sys.platform == "win32": try: @@ -80,8 +80,7 @@ def get_tmp_directory(): def get_tmp_hash_seed(): """Get a temp hash seed.""" - hash_seed = 0xC70F6907 - return hash_seed + return 0xC70F6907 def get_file_name(code): diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 9f851caab..59d36452d 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -46,8 +46,7 @@ def _find_connection_file(connection_file): if connection_file is None: # get connection file from current kernel return get_connection_file() - else: - return jupyter_client.find_connection_file(connection_file) + return jupyter_client.find_connection_file(connection_file) def get_connection_info( @@ -125,7 +124,7 @@ def connect_qtconsole( kwargs["start_new_session"] = True return Popen( - [sys.executable, "-c", cmd, "--existing", cf, *argv], # noqa: S603 + [sys.executable, "-c", cmd, "--existing", cf, *argv], stdout=PIPE, stderr=PIPE, close_fds=(sys.platform != "win32"), diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 90e4f8885..e236a7c2a 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -3,6 +3,7 @@ import re import sys import typing as t +from pathlib import Path import zmq from IPython.core.getipython import get_ipython @@ -20,7 +21,7 @@ try: # This import is required to have the next ones working... - from debugpy.server import api # noqa + from debugpy.server import api from _pydevd_bundle import pydevd_frame_utils # isort: skip from _pydevd_bundle.pydevd_suspended_frames import ( # isort: skip @@ -179,10 +180,10 @@ def put_tcp_frame(self, frame): self.tcp_buffer = "" self._reset_tcp_pos() return - else: - self.tcp_buffer = self.tcp_buffer[self.message_pos + self.message_size :] - self.log.debug("QUEUE - slicing tcp_buffer: %s", self.tcp_buffer) - self._reset_tcp_pos() + + self.tcp_buffer = self.tcp_buffer[self.message_pos + self.message_size :] + self.log.debug("QUEUE - slicing tcp_buffer: %s", self.tcp_buffer) + self._reset_tcp_pos() async def get_message(self): """Get a message from the queue.""" @@ -256,8 +257,7 @@ async def _handle_init_sequence(self): await self._wait_for_response() # 4] Waits for attachResponse and returns it - attach_rep = await self._wait_for_response() - return attach_rep + return await self._wait_for_response() def get_host_port(self): """Get the host debugpy port.""" @@ -294,11 +294,11 @@ async def send_dap_request(self, msg): rep = await self._handle_init_sequence() self.wait_for_attach = False return rep - else: - rep = await self._wait_for_response() - self.log.debug("DEBUGPYCLIENT - returning:") - self.log.debug(rep) - return rep + + rep = await self._wait_for_response() + self.log.debug("DEBUGPYCLIENT - returning:") + self.log.debug(rep) + return rep class Debugger: @@ -363,9 +363,8 @@ def _handle_event(self, msg): self.stopped_queue.put_nowait(msg) # Do not forward the event now, will be done in the handle_stopped_event return - else: - self.stopped_threads.add(msg["body"]["threadId"]) - self.event_callback(msg) + self.stopped_threads.add(msg["body"]["threadId"]) + self.event_callback(msg) elif msg["event"] == "continued": if msg["body"]["allThreadsContinued"]: self.stopped_threads = set() @@ -380,7 +379,7 @@ async def _forward_message(self, msg): def _build_variables_response(self, request, variables): var_list = [var for var in variables if self.accept_variable(var["name"])] - reply = { + return { "seq": request["seq"], "type": "response", "request_seq": request["seq"], @@ -388,7 +387,6 @@ def _build_variables_response(self, request, variables): "command": request["command"], "body": {"variables": var_list}, } - return reply def _accept_stopped_thread(self, thread_name): # TODO: identify Thread-2, Thread-3 and Thread-4. These are NOT @@ -416,8 +414,8 @@ def start(self): """Start the debugger.""" if not self.debugpy_initialized: tmp_dir = get_tmp_directory() - if not os.path.exists(tmp_dir): - os.makedirs(tmp_dir) + if not Path(tmp_dir).exists(): + Path(tmp_dir).mkdir(parents=True) host, port = self.debugpy_client.get_host_port() code = "import debugpy;" code += 'debugpy.listen(("' + host + '",' + port + "))" @@ -460,14 +458,13 @@ async def dumpCell(self, message): with open(file_name, "w", encoding="utf-8") as f: f.write(code) - reply = { + return { "type": "response", "request_seq": message["seq"], "success": True, "command": message["command"], "body": {"sourcePath": file_name}, } - return reply async def setBreakpoints(self, message): """Handle a set breakpoints message.""" @@ -487,7 +484,7 @@ async def source(self, message): """Handle a source message.""" reply = {"type": "response", "request_seq": message["seq"], "command": message["command"]} source_path = message["arguments"]["source"]["path"] - if os.path.isfile(source_path): + if Path(source_path).is_file(): with open(source_path, encoding="utf-8") as f: reply["success"] = True reply["body"] = {"content": f.read()} @@ -547,7 +544,7 @@ def accept_variable(self, variable_name): cond = variable_name not in forbid_list cond = cond and not bool(re.search(r"^_\d", variable_name)) cond = cond and variable_name[0:2] != "_i" - return cond + return cond # noqa: RET504 async def variables(self, message): """Handle a variables message.""" @@ -557,12 +554,12 @@ async def variables(self, message): message["arguments"]["variablesReference"] ) return self._build_variables_response(message, variables) - else: - reply = await self._forward_message(message) - # TODO : check start and count arguments work as expected in debugpy - reply["body"]["variables"] = [ - var for var in reply["body"]["variables"] if self.accept_variable(var["name"]) - ] + + reply = await self._forward_message(message) + # TODO : check start and count arguments work as expected in debugpy + reply["body"]["variables"] = [ + var for var in reply["body"]["variables"] if self.accept_variable(var["name"]) + ] return reply async def attach(self, message): @@ -580,21 +577,20 @@ async def attach(self, message): async def configurationDone(self, message): """Handle a configuration done message.""" - reply = { + return { "seq": message["seq"], "type": "response", "request_seq": message["seq"], "success": True, "command": message["command"], } - return reply async def debugInfo(self, message): """Handle a debug info message.""" breakpoint_list = [] for key, value in self.breakpoint_list.items(): breakpoint_list.append({"source": key, "breakpoints": value}) - reply = { + return { "type": "response", "request_seq": message["seq"], "success": True, @@ -611,7 +607,6 @@ async def debugInfo(self, message): "exceptionPaths": ["Python Exceptions"], }, } - return reply async def inspectVariables(self, message): """Handle an insepct variables message.""" @@ -665,7 +660,7 @@ async def richInspectVariables(self, message): } ) if reply["success"]: - repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) # noqa: S307 + repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) # noqa: PGH001 body = { "data": repr_data, @@ -708,8 +703,7 @@ async def modules(self, message): if filename and filename.endswith(".py"): mods.append({"id": i, "name": module.__name__, "path": filename}) - reply = {"body": {"modules": mods, "totalModules": len(modules)}} - return reply + return {"body": {"modules": mods, "totalModules": len(modules)}} async def process_request(self, message): """Process a request.""" diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index bf1e3d0d6..401aebd3e 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -9,7 +9,7 @@ from functools import partial import zmq -from packaging.version import Version as V # noqa +from packaging.version import Version as V from traitlets.config.application import Application @@ -52,7 +52,7 @@ def decorator(func): for name in toolkitnames: loop_map[name] = func - func.exit_hook = lambda kernel: None + func.exit_hook = lambda kernel: None # noqa: ARG005 def exit_decorator(exit_func): """@func.exit is now a decorator @@ -484,24 +484,24 @@ def set_qt_api_env_from_gui(gui): else: if gui == "qt5": try: - import PyQt5 # noqa + import PyQt5 os.environ["QT_API"] = "pyqt5" except ImportError: try: - import PySide2 # noqa + import PySide2 os.environ["QT_API"] = "pyside2" except ImportError: os.environ["QT_API"] = "pyqt5" elif gui == "qt6": try: - import PyQt6 # noqa + import PyQt6 os.environ["QT_API"] = "pyqt6" except ImportError: try: - import PySide6 # noqa + import PySide6 os.environ["QT_API"] = "pyside6" except ImportError: @@ -516,7 +516,7 @@ def set_qt_api_env_from_gui(gui): # Do the actual import now that the environment variable is set to make sure it works. try: - from IPython.external.qt_for_kernel import QtCore, QtGui # noqa + from IPython.external.qt_for_kernel import QtCore, QtGui except Exception as e: # Clear the environment variable for the next attempt. if "QT_API" in os.environ: diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index baf8c5ee9..f270b5dd0 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -91,7 +91,6 @@ def _hijack_gtk(self): def dummy(*args, **kw): """No-op.""" - pass # save and trap main and main_quit from gtk orig_main, Gtk.main = Gtk.main, dummy diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index ac23afc71..f6b8bf5bb 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -88,7 +88,6 @@ def _hijack_gtk(self): def dummy(*args, **kw): """No-op.""" - pass # save and trap main and main_quit from gtk orig_main, gtk.main = gtk.main, dummy diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 33beaad66..d2890f672 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -13,8 +13,8 @@ # ----------------------------------------------------------------------------- import errno -import os import socket +from pathlib import Path from threading import Thread import zmq @@ -54,7 +54,7 @@ def pick_port(self): s.close() elif self.transport == "ipc": self.port = 1 - while os.path.exists(f"{self.ip}-{self.port}"): + while Path(f"{self.ip}-{self.port}").exists(): self.port = self.port + 1 else: raise ValueError("Unrecognized zmq transport: %s" % self.transport) @@ -108,7 +108,7 @@ def run(self): if e.errno == errno.EINTR: # signal interrupt, resume heartbeat continue - elif e.errno == zmq.ETERM: + if e.errno == zmq.ETERM: # context terminated, close socket and exit try: self.socket.close() @@ -117,10 +117,9 @@ def run(self): # this shouldn't happen, though pass break - elif e.errno == zmq.ENOTSOCK: + if e.errno == zmq.ENOTSOCK: # socket closed elsewhere, exit break - else: - raise + raise else: break diff --git a/ipykernel/inprocess/__init__.py b/ipykernel/inprocess/__init__.py index 5d5147172..b10698910 100644 --- a/ipykernel/inprocess/__init__.py +++ b/ipykernel/inprocess/__init__.py @@ -1,4 +1,4 @@ -from .blocking import BlockingInProcessKernelClient # noqa -from .channels import InProcessChannel, InProcessHBChannel # noqa -from .client import InProcessKernelClient # noqa -from .manager import InProcessKernelManager # noqa +from .blocking import BlockingInProcessKernelClient +from .channels import InProcessChannel, InProcessHBChannel +from .client import InProcessKernelClient +from .manager import InProcessKernelManager diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index d3f03fcfd..378416dcc 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -45,7 +45,6 @@ def call_handlers(self, msg): def flush(self, timeout=1.0): """Flush the channel.""" - pass def call_handlers_later(self, *args, **kwds): """Call the message handlers later. diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index cbbe85278..873b96d20 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -87,11 +87,9 @@ def start(self): def _abort_queues(self): """The in-process kernel doesn't abort requests.""" - pass async def _flush_control_queue(self): """No need to flush control queues for in-process""" - pass def _input_request(self, prompt, ident, parent, password=False): # Flush output before making the request. diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index 7e48789e9..2df72b5e1 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -39,4 +39,3 @@ def send_multipart(self, msg_parts, flags=0, copy=True, track=False): def flush(self, timeout=1.0): """no-op to comply with stream API""" - pass diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 3588e5884..0bbdbe275 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -216,8 +216,7 @@ def _check_mp_mode(self): """check for forks, and switch to zmq pipeline if necessary""" if not self._pipe_flag or self._is_master_process(): return MASTER - else: - return CHILD + return CHILD def start(self): """Start the IOPub thread""" @@ -319,7 +318,7 @@ def __getattr__(self, attr): stacklevel=2, ) return getattr(self.io_thread.socket, attr) - super().__getattr__(attr) # type:ignore[misc] + return super().__getattr__(attr) # type:ignore[misc] def __setattr__(self, attr, value): """Set an attribute on the socket.""" @@ -367,9 +366,8 @@ def fileno(self): """ if getattr(self, "_original_stdstream_copy", None) is not None: return self._original_stdstream_copy - else: - msg = "fileno" - raise io.UnsupportedOperation(msg) + msg = "fileno" + raise io.UnsupportedOperation(msg) def _watch_pipe_fd(self): """ @@ -647,21 +645,21 @@ def write(self, string: str) -> Optional[int]: # type:ignore[override] if self.pub_thread is None: msg = "I/O operation on closed file" raise ValueError(msg) + + is_child = not self._is_master_process() + # only touch the buffer in the IO thread to avoid races + with self._buffer_lock: + self._buffer.write(string) + if is_child: + # mp.Pool cannot be trusted to flush promptly (or ever), + # and this helps. + if self._subprocess_flush_pending: + return None + self._subprocess_flush_pending = True + # We can not rely on self._io_loop.call_later from a subprocess + self.pub_thread.schedule(self._flush) else: - is_child = not self._is_master_process() - # only touch the buffer in the IO thread to avoid races - with self._buffer_lock: - self._buffer.write(string) - if is_child: - # mp.Pool cannot be trusted to flush promptly (or ever), - # and this helps. - if self._subprocess_flush_pending: - return None - self._subprocess_flush_pending = True - # We can not rely on self._io_loop.call_later from a subprocess - self.pub_thread.schedule(self._flush) - else: - self._schedule_flush() + self._schedule_flush() return len(string) @@ -670,9 +668,8 @@ def writelines(self, sequence): if self.pub_thread is None: msg = "I/O operation on closed file" raise ValueError(msg) - else: - for string in sequence: - self.write(string) + for string in sequence: + self.write(string) def writable(self): """Test whether the stream is writable.""" diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index fafe02d55..2d0ec6ff2 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -55,7 +55,7 @@ def _create_comm(*args, **kwargs): def _get_comm_manager(*args, **kwargs): """Create a new CommManager.""" - global _comm_manager # noqa + global _comm_manager # noqa: PLW0603 if _comm_manager is None: with _comm_lock: if _comm_manager is None: @@ -208,6 +208,7 @@ def dispatch_debugpy(self, msg): def banner(self): if self.shell: return self.shell.banner + return None async def poll_stopped_queue(self): """Poll the stopped queue.""" @@ -288,6 +289,7 @@ def _restore_input(self): def execution_count(self): if self.shell: return self.shell.execution_count + return None @execution_count.setter def execution_count(self, value): @@ -362,7 +364,7 @@ async def do_execute( should_run_async = shell.should_run_async accepts_params = _accepts_parameters(run_cell, ["cell_id"]) else: - should_run_async = lambda cell: False # noqa + should_run_async = lambda cell: False # noqa: ARG005 # older IPython, # use blocking run_cell and wrap it in coroutine @@ -507,6 +509,7 @@ async def do_debug_request(self, msg): """Handle a debug request.""" if _is_debugpy_available: return await self.debugger.process_request(msg) + return None def _experimental_do_complete(self, code, cursor_pos): """ @@ -658,7 +661,7 @@ def do_apply(self, content, bufs, msg_id, reply_metadata): working.update(ns) code = f"{resultname} = {fname}(*{argname},**{kwargname})" try: - exec(code, shell.user_global_ns, shell.user_ns) # noqa + exec(code, shell.user_global_ns, shell.user_ns) result = working.get(resultname) finally: for key in ns: diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 629dfc045..6a463cf1b 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -26,7 +26,7 @@ # holy crap, strptime is not threadsafe. # Calling it once at import seems to help. -datetime.strptime("1", "%d") # noqa +datetime.strptime("1", "%d") # ----------------------------------------------------------------------------- # Classes and functions diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 5bfcccbf5..9cfb55a54 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -15,6 +15,7 @@ from functools import partial from io import FileIO, TextIOWrapper from logging import StreamHandler +from pathlib import Path import zmq from IPython.core.application import ( # type:ignore[attr-defined] @@ -161,10 +162,9 @@ def _default_connection_dir(self): @property def abs_connection_file(self): - if os.path.basename(self.connection_file) == self.connection_file: - return os.path.join(self.connection_dir, self.connection_file) - else: - return self.connection_file + if Path(self.connection_file).name == self.connection_file and self.connection_dir: + return str(Path(str(self.connection_dir)) / self.connection_file) + return self.connection_file # streams, etc. no_stdout = Bool(False, help="redirect stdout to the null device").tag(config=True) @@ -231,7 +231,7 @@ def _try_bind_socket(self, s, port): if port <= 0: port = 1 path = "%s-%i" % (self.ip, port) - while os.path.exists(path): + while Path(path).exists(): port = port + 1 path = "%s-%i" % (self.ip, port) else: @@ -257,6 +257,7 @@ def _bind_socket(self, s, port): raise if attempt == max_attempts - 1: raise + return None def write_connection_file(self): """write connection info to JSON file""" @@ -271,7 +272,7 @@ def write_connection_file(self): iopub_port=self.iopub_port, control_port=self.control_port, ) - if os.path.exists(cf): + if Path(cf).exists(): # If the file exists, merge our info into it. For example, if the # original file had port number 0, we update with the actual port # used. @@ -291,7 +292,7 @@ def cleanup_connection_file(self): cf = self.abs_connection_file self.log.debug("Cleaning up connection file: %s", cf) try: - os.remove(cf) + Path(cf).unlink() except OSError: pass @@ -306,14 +307,14 @@ def init_connection_file(self): except OSError: self.log.debug("Connection file not found: %s", self.connection_file) # This means I own it, and I'll create it in this directory: - os.makedirs(os.path.dirname(self.abs_connection_file), mode=0o700, exist_ok=True) + Path(self.abs_connection_file).parent.mkdir(mode=0o700, exist_ok=True, parents=True) # Also, I will clean it up: atexit.register(self.cleanup_connection_file) return try: self.load_connection_file() except Exception: - self.log.error( + self.log.error( # noqa: G201 "Failed to load connection file: %r", self.connection_file, exc_info=True ) self.exit(1) @@ -423,10 +424,10 @@ def close(self): def log_connection_info(self): """display connection info, and store ports""" - basename = os.path.basename(self.connection_file) + basename = Path(self.connection_file).name if ( basename == self.connection_file - or os.path.dirname(self.connection_file) == self.connection_dir + or str(Path(self.connection_file).parent) == self.connection_dir ): # use shortname tail = basename @@ -460,7 +461,7 @@ def log_connection_info(self): def init_blackhole(self): """redirects stdout/stderr to devnull if necessary""" if self.no_stdout or self.no_stderr: - blackhole = open(os.devnull, "w") # noqa + blackhole = open(os.devnull, "w") # noqa: SIM115 if self.no_stdout: sys.stdout = sys.__stdout__ = blackhole # type:ignore[misc] if self.no_stderr: @@ -666,7 +667,7 @@ def init_pdb(self): With the non-interruptible version, stopping pdb() locks up the kernel in a non-recoverable state. """ - import pdb # noqa + import pdb from IPython.core import debugger @@ -702,7 +703,7 @@ def initialize(self, argv=None): # Catch exception when initializing signal fails, eg when running the # kernel on a separate thread if int(self.log_level) < logging.CRITICAL: # type:ignore[call-overload] - self.log.error("Unable to initialize signal:", exc_info=True) + self.log.error("Unable to initialize signal:", exc_info=True) # noqa: G201 self.init_kernel() # shell init steps self.init_path() diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 37ed40e2a..bd3fb074b 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -121,8 +121,7 @@ def _shell_streams_default(self): # pragma: no cover ) if self.shell_stream is not None: return [self.shell_stream] - else: - return [] + return [] @observe("shell_streams") def _shell_streams_changed(self, change): # pragma: no cover @@ -328,7 +327,7 @@ async def process_control(self, msg): try: msg = self.session.deserialize(msg, content=True, copy=False) except Exception: - self.log.error("Invalid Control Message", exc_info=True) + self.log.error("Invalid Control Message", exc_info=True) # noqa: G201 return self.log.debug("Control received: %s", msg) @@ -349,7 +348,7 @@ async def process_control(self, msg): if inspect.isawaitable(result): await result except Exception: - self.log.error("Exception in control handler:", exc_info=True) + self.log.error("Exception in control handler:", exc_info=True) # noqa: G201 sys.stdout.flush() sys.stderr.flush() @@ -382,7 +381,7 @@ async def dispatch_shell(self, msg): try: msg = self.session.deserialize(msg, content=True, copy=False) except Exception: - self.log.error("Invalid Message", exc_info=True) + self.log.error("Invalid Message", exc_info=True) # noqa: G201 return # Set the parent message for side effects. @@ -424,7 +423,7 @@ async def dispatch_shell(self, msg): if inspect.isawaitable(result): await result except Exception: - self.log.error("Exception in message handler:", exc_info=True) + self.log.error("Exception in message handler:", exc_info=True) # noqa: G201 except KeyboardInterrupt: # Ctrl-c shouldn't crash the kernel here. self.log.error("KeyboardInterrupt caught in kernel.") @@ -476,7 +475,6 @@ def advance_eventloop(): except KeyboardInterrupt: # Ctrl-C shouldn't crash the kernel self.log.error("KeyboardInterrupt caught in kernel") - pass if self.eventloop is eventloop: # schedule advance again schedule_next() @@ -516,7 +514,7 @@ async def process_one(self, wait=True): try: t, dispatch, args = self.msg_queue.get_nowait() except (asyncio.QueueEmpty, QueueEmpty): - return None + return await dispatch(*args) async def dispatch_queue(self): @@ -684,7 +682,7 @@ def send_response( message. """ if not self.session: - return + return None return self.session.send( stream, msg_or_type, @@ -1037,8 +1035,8 @@ def get_process_metric_value(self, process, name, attribute=None): metric_value = getattr(process, name)() if attribute is not None: # ... a named tuple return getattr(metric_value, attribute) - else: # ... or a number - return metric_value + # ... or a number + return metric_value # Avoid littering logs with stack traces # complaining about dead processes except BaseException: @@ -1095,7 +1093,7 @@ async def apply_request(self, stream, ident, parent): # pragma: no cover bufs = parent["buffers"] msg_id = parent["header"]["msg_id"] except Exception: - self.log.error("Got bad msg: %s", parent, exc_info=True) + self.log.error("Got bad msg: %s", parent, exc_info=True) # noqa: G201 return md = self.init_metadata(parent) @@ -1203,7 +1201,7 @@ def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" if not self.session: return - self.log.info(f"Aborting {msg['header']['msg_id']}: {msg['header']['msg_type']}") + self.log.info("Aborting %s: %s", msg["header"]["msg_id"], msg["header"]["msg_type"]) reply_type = msg["header"]["msg_type"].rsplit("_", 1)[0] + "_reply" status = {"status": "aborted"} md = self.init_metadata(msg) @@ -1279,8 +1277,7 @@ def _input_request(self, prompt, ident, parent, password=False): except zmq.ZMQError as e: if e.errno == zmq.EAGAIN: break - else: - raise + raise # Send the input request. assert self.session is not None @@ -1325,8 +1322,9 @@ def _signal_children(self, signum): Like `killpg`, but does not include the current process (or possible parents). """ + sig_rep = f"{Signals(signum)!r}" for p in self._process_children(): - self.log.debug(f"Sending {Signals(signum)!r} to subprocess {p}") + self.log.debug("Sending %s to subprocess %s", sig_rep, p) try: if signum == SIGTERM: p.terminate() @@ -1375,7 +1373,9 @@ async def _progressively_terminate_all_children(self): # signals only children, not current process self._signal_children(signum) self.log.debug( - f"Will sleep {delay}s before checking for children and retrying. {children}" + "Will sleep %s sec before checking for children and retrying. %s", + delay, + children, ) await asyncio.sleep(delay) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 414b1d951..70253c15c 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -12,6 +12,7 @@ import stat import sys import tempfile +from pathlib import Path from typing import Any from jupyter_client.kernelspec import KernelSpecManager @@ -27,7 +28,7 @@ KERNEL_NAME = "python%i" % sys.version_info[0] # path to kernelspec resources -RESOURCES = pjoin(os.path.dirname(__file__), "resources") +RESOURCES = pjoin(Path(__file__).parent, "resources") def make_ipkernel_cmd( @@ -70,7 +71,7 @@ def get_kernel_dict(extra_arguments: list[str] | None = None) -> dict[str, Any]: def write_kernel_spec( - path: str | None = None, + path: Path | str | None = None, overrides: dict[str, Any] | None = None, extra_arguments: list[str] | None = None, ) -> str: @@ -82,15 +83,15 @@ def write_kernel_spec( The path to the kernelspec is always returned. """ if path is None: - path = os.path.join(tempfile.mkdtemp(suffix="_kernels"), KERNEL_NAME) + path = Path(tempfile.mkdtemp(suffix="_kernels")) / KERNEL_NAME # stage resources shutil.copytree(RESOURCES, path) # ensure path is writable - mask = os.stat(path).st_mode + mask = Path(path).stat().st_mode if not mask & stat.S_IWUSR: - os.chmod(path, mask | stat.S_IWUSR) + Path(path).chmod(mask | stat.S_IWUSR) # write kernel.json kernel_dict = get_kernel_dict(extra_arguments) @@ -100,7 +101,7 @@ def write_kernel_spec( with open(pjoin(path, "kernel.json"), "w") as f: json.dump(kernel_dict, f, indent=1) - return path + return str(path) def install( diff --git a/ipykernel/log.py b/ipykernel/log.py index d55c21d04..bbd4c445b 100644 --- a/ipykernel/log.py +++ b/ipykernel/log.py @@ -26,5 +26,4 @@ def root_topic(self): before the engine gets registered with an id""" if isinstance(getattr(self.engine, "id", None), int): return "engine.%i" % self.engine.id # type:ignore[union-attr] - else: - return "engine" + return "engine" diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index c6b71096e..3cfb74a50 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -17,7 +17,7 @@ from types import FunctionType # This registers a hook when it's imported -from ipyparallel.serialize import codeutil # noqa F401 +from ipyparallel.serialize import codeutil from traitlets.log import get_logger from traitlets.utils.importstring import import_item @@ -77,7 +77,7 @@ def use_dill(): # dill doesn't work with cPickle, # tell the two relevant modules to use plain pickle - global pickle # noqa + global pickle # noqa: PLW0603 pickle = dill try: @@ -98,7 +98,7 @@ def use_cloudpickle(): """ import cloudpickle - global pickle # noqa + global pickle # noqa: PLW0603 pickle = cloudpickle try: @@ -179,7 +179,7 @@ def get_object(self, g=None): if g is None: g = {} - return eval(self.name, g) # noqa: S307 + return eval(self.name, g) # noqa: PGH001 class CannedCell(CannedObject): @@ -238,8 +238,7 @@ def get_object(self, g=None): g = {} defaults = tuple(uncan(cfd, g) for cfd in self.defaults) if self.defaults else None closure = tuple(uncan(cell, g) for cell in self.closure) if self.closure else None - newFunc = FunctionType(self.code, g, self.__name__, defaults, closure) - return newFunc + return FunctionType(self.code, g, self.__name__, defaults, closure) class CannedClass(CannedObject): @@ -300,9 +299,8 @@ def get_object(self, g=None): data = self.buffers[0] if self.pickled: # we just pickled it - return pickle.loads(data) # noqa - else: - return frombuffer(data, dtype=self.dtype).reshape(self.shape) + return pickle.loads(data) + return frombuffer(data, dtype=self.dtype).reshape(self.shape) class CannedBytes(CannedObject): @@ -355,7 +353,7 @@ def _import_mapping(mapping, original=None): except Exception: if original and key not in original: # only message on user-added classes - log.error("canning class not importable: %r", key, exc_info=True) + log.error("canning class not importable: %r", key, exc_info=True) # noqa: G201 mapping.pop(key) else: mapping[cls] = mapping.pop(key) @@ -368,8 +366,7 @@ def istype(obj, check): """ if isinstance(check, tuple): return any(type(obj) is cls for cls in check) - else: - return type(obj) is check + return type(obj) is check def can(obj): @@ -381,7 +378,7 @@ def can(obj): if isinstance(cls, str): import_needed = True break - elif istype(obj, cls): + if istype(obj, cls): return canner(obj) if import_needed: @@ -397,8 +394,7 @@ def can_class(obj): """Can a class object.""" if isinstance(obj, class_type) and obj.__module__ == "__main__": return CannedClass(obj) - else: - return obj + return obj def can_dict(obj): @@ -408,8 +404,7 @@ def can_dict(obj): for k, v in obj.items(): newobj[k] = can(v) return newobj - else: - return obj + return obj sequence_types = (list, tuple, set) @@ -420,8 +415,7 @@ def can_sequence(obj): if istype(obj, sequence_types): t = type(obj) return t([can(i) for i in obj]) - else: - return obj + return obj def uncan(obj, g=None): @@ -432,7 +426,7 @@ def uncan(obj, g=None): if isinstance(cls, str): import_needed = True break - elif isinstance(obj, cls): + if isinstance(obj, cls): return uncanner(obj, g) if import_needed: @@ -451,8 +445,7 @@ def uncan_dict(obj, g=None): for k, v in obj.items(): newobj[k] = uncan(v, g) return newobj - else: - return obj + return obj def uncan_sequence(obj, g=None): @@ -460,8 +453,7 @@ def uncan_sequence(obj, g=None): if istype(obj, sequence_types): t = type(obj) return t([uncan(i, g) for i in obj]) - else: - return obj + return obj # ------------------------------------------------------------------------------- diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index cc389ccae..cbc253064 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.backend_inline import * # type:ignore[import-untyped] # analysis: ignore # noqa F401 +from matplotlib_inline.backend_inline import * # type:ignore[import-untyped] # analysis: ignore warnings.warn( "`ipykernel.pylab.backend_inline` is deprecated, directly " diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index afd44d19d..f32787283 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.config import * # type:ignore[import-untyped] # analysis: ignore # noqa F401 +from matplotlib_inline.config import * # type:ignore[import-untyped] # analysis: ignore warnings.warn( "`ipykernel.pylab.config` is deprecated, directly use `matplotlib_inline.config`", diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 12e5b9a99..616410c81 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -122,7 +122,7 @@ def deserialize_object(buffers, g=None): """ bufs = list(buffers) pobj = bufs.pop(0) - canned = pickle.loads(pobj) # noqa + canned = pickle.loads(pobj) if istype(canned, sequence_types) and len(canned) < MAX_ITEMS: for c in canned: _restore_buffers(c, bufs) @@ -183,9 +183,9 @@ def unpack_apply_message(bufs, g=None, copy=True): bufs = list(bufs) # allow us to pop assert len(bufs) >= 2, "not enough buffers!" pf = bufs.pop(0) - f = uncan(pickle.loads(pf), g) # noqa + f = uncan(pickle.loads(pf), g) pinfo = bufs.pop(0) - info = pickle.loads(pinfo) # noqa + info = pickle.loads(pinfo) arg_bufs, kwarg_bufs = bufs[: info["narg_bufs"]], bufs[info["narg_bufs"] :] args_list = [] diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index 977302a8a..45f738acb 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -66,5 +66,6 @@ async def loc(coro): with self._cell_cancel_scope: return await coro self._cell_cancel_scope = None # type:ignore[unreachable] + return None return trio.from_thread.run(loc, async_fn, trio_token=self._trio_token) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index d381a5c98..21983b504 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -17,6 +17,7 @@ import os import sys import warnings +from pathlib import Path from threading import local from IPython.core import page, payloadpage @@ -301,7 +302,7 @@ def edit(self, parameter_s="", last_call=None): # Make sure we send to the client an absolute path, in case the working # directory of client and kernel don't match - filename = os.path.abspath(filename) + filename = Path(filename).resolve() payload = {"source": "edit_magic", "filename": filename, "line_number": lineno} assert self.shell is not None @@ -375,8 +376,8 @@ def connect_info(self, arg_s): return # if it's in the default dir, truncate to basename - if jupyter_runtime_dir() == os.path.dirname(connection_file): - connection_file = os.path.basename(connection_file) + if jupyter_runtime_dir() == str(Path(connection_file).parent): + connection_file = Path(connection_file).name assert isinstance(info, str) print(info + "\n") @@ -514,7 +515,6 @@ def init_hooks(self): def init_data_pub(self): """Delay datapub init until request, for deprecation warnings""" - pass @property def data_pub(self): @@ -620,7 +620,6 @@ def init_virtualenv(self): # not appropriate in a kernel. To use a kernel in a virtualenv, install # it inside the virtualenv. # https://ipython.readthedocs.io/en/latest/install/kernel_install.html - pass def system_piped(self, cmd): """Call the given cmd in a subprocess, piping stdout/err diff --git a/pyproject.toml b/pyproject.toml index 1e5c71a88..dec3ae13a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -132,7 +132,6 @@ disable_error_code = ["no-untyped-def", "no-untyped-call", "import-not-found"] enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] follow_imports = "normal" pretty = true -show_error_codes = true warn_unreachable = true [tool.pytest.ini_options] @@ -212,69 +211,55 @@ line-length = 100 [tool.ruff.lint] select = [ - "A", - "B", - "C", - "DTZ", - "E", - "EM", - "F", - "FBT", - "I", - "ICN", - "N", - "PLC", - "PLE", - "PLW", - "Q", - "RUF", - "S", - "SIM", - "T", - "TID", - "UP", - "W", - "YTT", + "B", # flake8-bugbear + "I", # isort + "ARG", # flake8-unused-arguments + "C4", # flake8-comprehensions + "EM", # flake8-errmsg + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "RET", # flake8-return + "RUF", # Ruff-specific + "SIM", # flake8-simplify + "T20", # flake8-print + "UP", # pyupgrade + "YTT", # flake8-2020 + "EXE", # flake8-executable + "NPY", # NumPy specific rules + "PD", # pandas-vet + "PYI", # flake8-pyi ] ignore = [ + "PLR", # Design related pylint codes # Allow non-abstract empty methods in abstract base classes "B027", - # Ignore McCabe complexity - "C901", - # Allow boolean positional values in function calls, like `dict.get(... True)` - "FBT003", # Use of `assert` detected "S101", - # Line too long - "E501", - # Relative imports are banned - "TID252", - # Boolean ... in function definition - "FBT001", "FBT002", - # Module level import not at top of file - "E402", - # A001/A002/A003 .. is shadowing a python builtin - "A001", "A002", "A003", # Possible hardcoded password "S105", "S106", - # Q000 Single quotes found but double quotes preferred - "Q000", - # N806 Variable `B` in function should be lowercase - "N806", - # T201 `print` found + # `print` found "T201", - # N802 Function name `CreateWellKnownSid` should be lowercase - "N802", "N803", - # C408 Unnecessary `dict` call (rewrite as a literal) + # Unnecessary `dict` call (rewrite as a literal) "C408", - # N801 Class name `directional_link` should use CapWords convention - "N801", - # SIM105 Use `contextlib.suppress(ValueError)` instead of try-except-pass + # Use `contextlib.suppress(ValueError)` instead of try-except-pass "SIM105", - # S110 `try`-`except`-`pass` detected + # `try`-`except`-`pass` detected "S110", - # RUF012 Mutable class attributes should be annotated with `typing.ClassVar` + # Mutable class attributes should be annotated with `typing.ClassVar` "RUF012", + # Unused function argument: + "ARG001", + # Unused method argument: + "ARG002", + # Logging statement uses `%` + "G002", + # `open()` should be replaced by `Path.open()` + "PTH123", ] unfixable = [ # Don't touch print statements @@ -285,22 +270,14 @@ unfixable = [ [tool.ruff.lint.per-file-ignores] # B011 Do not call assert False since python -O removes these calls -# F841 local variable 'foo' is assigned to but never used # C408 Unnecessary `dict` call -# E402 Module level import not at top of file # T201 `print` found # B007 Loop control variable `i` not used within the loop body. -# N802 Function name `assertIn` should be lowercase -# F841 Local variable `t` is assigned to but never used -# EM101 Exception must not use a string literal, assign to variable first -# PLR2004 Magic value used in comparison # PLW0603 Using the global statement to update ... -# PLW2901 `for` loop variable ... # PLC1901 `stderr == ""` can be simplified to `not stderr` as an empty string is falsey # B018 Found useless expression. Either assign it to a variable or remove it. # S603 `subprocess` call: check for execution of untrusted input -"tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "EM101", - "EM102", "EM103", "PLR2004", "PLW0603", "PLW2901", "PLC1901", "B018", "S603"] +"tests/*" = ["B011", "C408", "T201", "B007", "EM", "PTH", "PLW", "PLC1901", "B018", "S603", "ARG", "RET", "PGH"] [tool.interrogate] ignore-init-module=true diff --git a/tests/conftest.py b/tests/conftest.py index 00f8394f6..7fe026a03 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -143,16 +143,16 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture -async def kernel(): +@pytest.fixture() +def kernel(): kernel = MockKernel() kernel.io_loop = IOLoop.current() yield kernel kernel.destroy() -@pytest.fixture -async def ipkernel(): +@pytest.fixture() +def ipkernel(): kernel = MockIPyKernel() kernel.io_loop = IOLoop.current() yield kernel diff --git a/tests/inprocess/test_kernel.py b/tests/inprocess/test_kernel.py index 793485d95..33692e85f 100644 --- a/tests/inprocess/test_kernel.py +++ b/tests/inprocess/test_kernel.py @@ -45,7 +45,7 @@ def kc(): kc = km.client() kc.start_channels() kc.wait_for_ready() - yield kc + return kc def test_with_cell_id(kc): diff --git a/tests/inprocess/test_kernelmanager.py b/tests/inprocess/test_kernelmanager.py index 2b6d5faa0..d3ee99913 100644 --- a/tests/inprocess/test_kernelmanager.py +++ b/tests/inprocess/test_kernelmanager.py @@ -3,6 +3,7 @@ import unittest +import pytest from flaky import flaky from ipykernel.inprocess.manager import InProcessKernelManager @@ -38,14 +39,17 @@ def test_interface(self): old_kernel = km.kernel km.restart_kernel() - self.assertIsNotNone(km.kernel) + assert km.kernel is not None assert km.kernel != old_kernel km.shutdown_kernel() assert not km.has_kernel - self.assertRaises(NotImplementedError, km.interrupt_kernel) - self.assertRaises(NotImplementedError, km.signal_kernel, 9) + with pytest.raises(NotImplementedError): + km.interrupt_kernel() + + with pytest.raises(NotImplementedError): + km.signal_kernel(9) kc.stop_channels() assert not kc.channels_running @@ -71,7 +75,7 @@ def test_complete(self): kc.complete("my_ba", 5) msg = kc.get_shell_msg() assert msg["header"]["msg_type"] == "complete_reply" - self.assertEqual(sorted(msg["content"]["matches"]), ["my_bar", "my_baz"]) + assert sorted(msg["content"]["matches"]) == ["my_bar", "my_baz"] def test_inspect(self): """Does requesting object information from an in-process kernel work?""" @@ -87,7 +91,7 @@ def test_inspect(self): content = msg["content"] assert content["found"] text = content["data"]["text/plain"] - self.assertIn("int", text) + assert "int" in text def test_history(self): """Does requesting history from an in-process kernel work?""" diff --git a/tests/test_debugger.py b/tests/test_debugger.py index 48fafb42e..262f102de 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -32,13 +32,13 @@ def wait_for_debug_request(kernel, command, arguments=None, full_reply=False): return reply if full_reply else reply["content"] -@pytest.fixture +@pytest.fixture() def kernel(): with new_kernel() as kc: yield kc -@pytest.fixture +@pytest.fixture() def kernel_with_debug(kernel): # Initialize wait_for_debug_request( @@ -386,4 +386,4 @@ def my_test(): assert global_var is not None # Compare local and global variable - assert global_var["value"] == local_var["value"] and global_var["type"] == local_var["type"] + assert global_var["value"] == local_var["value"] and global_var["type"] == local_var["type"] # noqa: PT018 diff --git a/tests/test_io.py b/tests/test_io.py index a7691ee5c..0e23b4b14 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -17,14 +17,14 @@ from ipykernel.iostream import MASTER, BackgroundSocket, IOPubThread, OutStream -@pytest.fixture +@pytest.fixture() def ctx(): ctx = zmq.Context() yield ctx ctx.destroy() -@pytest.fixture +@pytest.fixture() def iopub_thread(ctx): with ctx.socket(zmq.PUB) as pub: thread = IOPubThread(pub) diff --git a/tests/test_jsonutil.py b/tests/test_jsonutil.py index 2f7e5dbcd..2c6b95372 100644 --- a/tests/test_jsonutil.py +++ b/tests/test_jsonutil.py @@ -53,7 +53,7 @@ def test(): # More exotic objects ((x for x in range(3)), [0, 1, 2]), (iter([1, 2]), [1, 2]), - (datetime(1991, 7, 3, 12, 00), "1991-07-03T12:00:00.000000"), # noqa + (datetime(1991, 7, 3, 12, 00), "1991-07-03T12:00:00.000000"), (date(1991, 7, 3), "1991-07-03T00:00:00.000000"), (MyFloat(), 3.14), (MyInt(), 389), @@ -98,7 +98,7 @@ def test_encode_images(): @pytest.mark.skipif(JUPYTER_CLIENT_MAJOR_VERSION >= 7, reason="json_clean is a no-op") def test_lambda(): - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 json_clean(lambda: 1) @@ -109,7 +109,7 @@ def test_exception(): {True: "bool", "True": "string"}, ] for d in bad_dicts: - with pytest.raises(ValueError): + with pytest.raises(ValueError): # noqa: PT011 json_clean(d) diff --git a/tests/test_message_spec.py b/tests/test_message_spec.py index 0c9e777cd..db6ea7d7f 100644 --- a/tests/test_message_spec.py +++ b/tests/test_message_spec.py @@ -10,7 +10,7 @@ import pytest from jupyter_client._version import version_info from jupyter_client.blocking.client import BlockingKernelClient -from packaging.version import Version as V # noqa +from packaging.version import Version as V from traitlets import Bool, Dict, Enum, HasTraits, Integer, List, TraitError, Unicode, observe from .utils import TIMEOUT, execute, flush_channels, get_reply, start_global_kernel diff --git a/tests/test_parentpoller.py b/tests/test_parentpoller.py index c40a47204..97cd80440 100644 --- a/tests/test_parentpoller.py +++ b/tests/test_parentpoller.py @@ -11,7 +11,7 @@ @pytest.mark.skipif(os.name == "nt", reason="only works on posix") def test_parent_poller_unix(): poller = ParentPollerUnix() - with mock.patch("os.getppid", lambda: 1): + with mock.patch("os.getppid", lambda: 1): # noqa: PT008 def exit_mock(*args): sys.exit(1) @@ -23,7 +23,7 @@ def mock_getppid(): msg = "hi" raise ValueError(msg) - with mock.patch("os.getppid", mock_getppid), pytest.raises(ValueError): + with mock.patch("os.getppid", mock_getppid), pytest.raises(ValueError): # noqa: PT011 poller.run() diff --git a/tests/test_pickleutil.py b/tests/test_pickleutil.py index 3351d7882..c48eadf77 100644 --- a/tests/test_pickleutil.py +++ b/tests/test_pickleutil.py @@ -16,7 +16,7 @@ def dumps(obj): def loads(obj): - return uncan(pickle.loads(obj)) # noqa + return uncan(pickle.loads(obj)) def test_no_closure(): diff --git a/tests/test_zmq_shell.py b/tests/test_zmq_shell.py index 665139271..57a70f304 100644 --- a/tests/test_zmq_shell.py +++ b/tests/test_zmq_shell.py @@ -198,7 +198,7 @@ def test_unregister_hook(self): first = self.disp_pub.unregister_hook(hook) self.disp_pub.publish(data) - self.assertTrue(first) + assert bool(first) assert hook.call_count == 1 assert self.session.send_count == 1 @@ -207,7 +207,7 @@ def test_unregister_hook(self): # should return false. # second = self.disp_pub.unregister_hook(hook) - self.assertFalse(second) + assert not bool(second) def test_magics(tmp_path): From a09892e376a2bdd5b77a45851e3d7a70568f8c08 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 21 Nov 2023 11:23:36 +0000 Subject: [PATCH 1034/1195] Publish 6.27.0 SHA256 hashes: ipykernel-6.27.0-py3-none-any.whl: 4388caa3c2cba0a381e20d289545e88a8aef1fe57a884d4c018718ec8c23c121 ipykernel-6.27.0.tar.gz: 7f4986f606581be73bfb32dc7a1ac9fa0e804c9be50ddf1c7a119413e982693f --- CHANGELOG.md | 28 ++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21540384d..f0b4856bd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ +## 6.27.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.26.0...465d34483103d23f471a4795fe5fabb9cf7ac3f5)) + +### Enhancements made + +- Extend argument handling of do_execute with cell metadata [#1169](https://github.com/ipython/ipykernel/pull/1169) ([@jjvraw](https://github.com/jjvraw)) + +### Maintenance and upkeep improvements + +- Update ruff and typings [#1167](https://github.com/ipython/ipykernel/pull/1167) ([@blink1073](https://github.com/blink1073)) +- Clean up ruff config [#1165](https://github.com/ipython/ipykernel/pull/1165) ([@blink1073](https://github.com/blink1073)) +- chore: update pre-commit hooks [#1164](https://github.com/ipython/ipykernel/pull/1164) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Clean up typing config [#1163](https://github.com/ipython/ipykernel/pull/1163) ([@blink1073](https://github.com/blink1073)) +- Update typing for traitlets 5.13 [#1162](https://github.com/ipython/ipykernel/pull/1162) ([@blink1073](https://github.com/blink1073)) +- Adopt ruff format [#1161](https://github.com/ipython/ipykernel/pull/1161) ([@blink1073](https://github.com/blink1073)) +- Update typing for jupyter_client 8.5 [#1160](https://github.com/ipython/ipykernel/pull/1160) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-10-24&to=2023-11-21&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-10-24..2023-11-21&type=Issues) | [@jjvraw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajjvraw+updated%3A2023-10-24..2023-11-21&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-10-24..2023-11-21&type=Issues) + + + ## 6.26.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.25.2...966e0a41fc61e7850378ae672e28202eb29b10b0)) @@ -22,8 +48,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-09-04..2023-10-24&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2023-09-04..2023-10-24&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-09-04..2023-10-24&type=Issues) - - ## 6.25.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.25.1...9d3f7aecc4fe68f14ebcc4dad4b65b19676e820e)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index efcef73aa..e90a9717f 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.26.0" +__version__ = "6.27.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From f9c517e868462d05d6854204c2ad0a244db1cd19 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 27 Nov 2023 12:21:00 -0600 Subject: [PATCH 1035/1195] Fix edit magic payload type (#1171) --- ipykernel/zmqshell.py | 2 +- tests/test_zmq_shell.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 21983b504..4f7e2f5a9 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -302,7 +302,7 @@ def edit(self, parameter_s="", last_call=None): # Make sure we send to the client an absolute path, in case the working # directory of client and kernel don't match - filename = Path(filename).resolve() + filename = str(Path(filename).resolve()) payload = {"source": "edit_magic", "filename": filename, "line_number": lineno} assert self.shell is not None diff --git a/tests/test_zmq_shell.py b/tests/test_zmq_shell.py index 57a70f304..dfd22dec0 100644 --- a/tests/test_zmq_shell.py +++ b/tests/test_zmq_shell.py @@ -220,6 +220,9 @@ def test_magics(tmp_path): tmp_file = tmp_path / "test.txt" tmp_file.write_text("hi", "utf8") magics.edit(str(tmp_file)) + payload = shell.payload_manager.read_payload()[0] + assert payload["filename"] == str(tmp_file) + magics.clear([]) magics.less(str(tmp_file)) if os.name == "posix": From 5b4b7a0c07c48742ac17c60cbd2d6a13d09e29c2 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 27 Nov 2023 18:24:51 +0000 Subject: [PATCH 1036/1195] Publish 6.27.1 SHA256 hashes: ipykernel-6.27.1-py3-none-any.whl: dab88b47f112f9f7df62236511023c9bdeef67abc73af7c652e4ce4441601686 ipykernel-6.27.1.tar.gz: 7d5d594b6690654b4d299edba5e872dc17bb7396a8d0609c97cb7b8a1c605de6 --- CHANGELOG.md | 18 ++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0b4856bd..f6404b4c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ +## 6.27.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.27.0...f9c517e868462d05d6854204c2ad0a244db1cd19)) + +### Bugs fixed + +- Fix edit magic payload type [#1171](https://github.com/ipython/ipykernel/pull/1171) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-11-21&to=2023-11-27&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-11-21..2023-11-27&type=Issues) + + + ## 6.27.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.26.0...465d34483103d23f471a4795fe5fabb9cf7ac3f5)) @@ -26,8 +42,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-10-24..2023-11-21&type=Issues) | [@jjvraw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajjvraw+updated%3A2023-10-24..2023-11-21&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-10-24..2023-11-21&type=Issues) - - ## 6.26.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.25.2...966e0a41fc61e7850378ae672e28202eb29b10b0)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index e90a9717f..95309e141 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.27.0" +__version__ = "6.27.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From e35e2f57d1329b49b81f858cb8763f2588a4ed38 Mon Sep 17 00:00:00 2001 From: Joshua James Venter <67124214+jjvraw@users.noreply.github.com> Date: Sun, 3 Dec 2023 22:53:52 +0200 Subject: [PATCH 1037/1195] Refactor execute_request to reduce redundancy and improve consistency (#1177) --- ipykernel/kernelbase.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index bd3fb074b..f9eb2b942 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -283,6 +283,11 @@ def __init__(self, **kwargs): self.control_queue: Queue[t.Any] = Queue() + # Storing the accepted parameters for do_execute, used in execute_request + self._do_exec_accepted_params = _accepts_parameters( + self.do_execute, ["cell_meta", "cell_id"] + ) + def dispatch_control(self, msg): self.control_queue.put_nowait(msg) @@ -724,6 +729,8 @@ async def execute_request(self, stream, ident, parent): store_history = content.get("store_history", not silent) user_expressions = content.get("user_expressions", {}) allow_stdin = content.get("allow_stdin", False) + cell_meta = parent.get("metadata", {}) + cell_id = cell_meta.get("cellId") except Exception: self.log.error("Got bad msg: ") self.log.error("%s", parent) @@ -739,12 +746,6 @@ async def execute_request(self, stream, ident, parent): self.execution_count += 1 self._publish_execute_input(code, parent, self.execution_count) - cell_meta = parent.get("metadata", {}) - cell_id = cell_meta.get("cellId") - - # Check which parameters do_execute can accept - accepts_params = _accepts_parameters(self.do_execute, ["cell_meta", "cell_id"]) - # Arguments based on the do_execute signature do_execute_args = { "code": code, @@ -754,9 +755,9 @@ async def execute_request(self, stream, ident, parent): "allow_stdin": allow_stdin, } - if accepts_params["cell_meta"]: + if self._do_exec_accepted_params["cell_meta"]: do_execute_args["cell_meta"] = cell_meta - if accepts_params["cell_id"]: + if self._do_exec_accepted_params["cell_id"]: do_execute_args["cell_id"] = cell_id # Call do_execute with the appropriate arguments From 6154ba323b332de4189d75a98881859c970dc050 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 4 Dec 2023 10:54:06 +0000 Subject: [PATCH 1038/1195] Update pytest commands in README (#1178) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec39e4785..fd6a09cae 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Follow the instructions from `Installation from source`. and then from the root directory ```bash -pytest ipykernel +pytest ``` ## Running tests with coverage @@ -30,7 +30,7 @@ Follow the instructions from `Installation from source`. and then from the root directory ```bash -pytest ipykernel -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 +pytest -vv -s --cov ipykernel --cov-branch --cov-report term-missing:skip-covered --durations 10 ``` ## About the IPython Development Team From e9ddcf50a30b59d63f585ffa9ffffd368095a54a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Dec 2023 20:48:20 +0000 Subject: [PATCH 1039/1195] chore: update pre-commit hooks (#1179) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5418e5337..7a1ccd2dc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.1 + rev: 0.27.2 hooks: - id: check-github-workflows @@ -40,7 +40,7 @@ repos: types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.7.0" + rev: "v1.7.1" hooks: - id: mypy files: ipykernel From 6d1805487960166350846b9a04a1758bccdacf6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Dec 2023 20:00:12 -0600 Subject: [PATCH 1040/1195] Bump actions/setup-python from 4 to 5 (#1181) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/downstream.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index b63f64d9f..68afcbf34 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -92,7 +92,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" architecture: "x64" @@ -124,7 +124,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.9" architecture: "x64" From 4001f8af9dc46e97735a46f20ca247075fb35dcb Mon Sep 17 00:00:00 2001 From: Nicolas Brichet <32258950+brichet@users.noreply.github.com> Date: Tue, 12 Dec 2023 03:02:09 +0100 Subject: [PATCH 1041/1195] Adds a flag in debug_info for the copyToGlobals support (#1099) --- ipykernel/debugger.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index e236a7c2a..138b77261 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -605,6 +605,7 @@ async def debugInfo(self, message): "stoppedThreads": list(self.stopped_threads), "richRendering": True, "exceptionPaths": ["Python Exceptions"], + "copyToGlobals": True, }, } From 07f7437a43eec62b4c1950a4084b386a2e7592f5 Mon Sep 17 00:00:00 2001 From: NewUserHa <32261870+NewUserHa@users.noreply.github.com> Date: Thu, 21 Dec 2023 09:27:28 +0800 Subject: [PATCH 1042/1195] Enable `ProactorEventLoop` on windows for `ipykernel` (#1184) --- ipykernel/kernelapp.py | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 9cfb55a54..6b42eda7c 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -624,43 +624,6 @@ def configure_tornado_logger(self): handler.setFormatter(formatter) logger.addHandler(handler) - def _init_asyncio_patch(self): - """set default asyncio policy to be compatible with tornado - - Tornado 6 (at least) is not compatible with the default - asyncio implementation on Windows - - Pick the older SelectorEventLoopPolicy on Windows - if the known-incompatible default policy is in use. - - Support for Proactor via a background thread is available in tornado 6.1, - but it is still preferable to run the Selector in the main thread - instead of the background. - - do this as early as possible to make it a low priority and overridable - - ref: https://github.com/tornadoweb/tornado/issues/2608 - - FIXME: if/when tornado supports the defaults in asyncio without threads, - remove and bump tornado requirement for py38. - Most likely, this will mean a new Python version - where asyncio.ProactorEventLoop supports add_reader and friends. - - """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8): - import asyncio - - try: - from asyncio import WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy - except ImportError: - pass - # not affected - else: - if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: - # WindowsProactorEventLoopPolicy is not compatible with tornado 6 - # fallback to the pre-3.8 default of Selector - asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) - def init_pdb(self): """Replace pdb with IPython's version that is interruptible. @@ -680,7 +643,6 @@ def init_pdb(self): @catch_config_error def initialize(self, argv=None): """Initialize the application.""" - self._init_asyncio_patch() super().initialize(argv) if self.subapp is not None: return From de45c7a49e197f0889f867f33f24cce322768a0e Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 20 Dec 2023 20:03:50 -0600 Subject: [PATCH 1043/1195] Support python 3.12 (#1185) --- .github/workflows/ci.yml | 10 +++++----- pyproject.toml | 9 ++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64bf5fb67..e6d7e864a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,16 +22,16 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.8", "3.11"] + python-version: ["3.8", "3.12"] include: - os: windows-latest python-version: "3.9" - os: ubuntu-latest - python-version: "pypy-3.8" + python-version: "pypy-3.9" - os: macos-latest python-version: "3.10" - os: ubuntu-latest - python-version: "3.8" + python-version: "3.11" steps: - name: Checkout uses: actions/checkout@v4 @@ -153,11 +153,11 @@ jobs: - name: List installed packages run: | - hatch run test:list + hatch -v run test:list - name: Run the unit tests run: | - hatch run test:nowarn || hatch run test:nowarn --lf + hatch -v run test:nowarn || hatch run test:nowarn --lf test_prereleases: name: Test Prereleases diff --git a/pyproject.toml b/pyproject.toml index dec3ae13a..57379b4b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,10 +17,6 @@ classifiers = [ "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", ] urls = {Homepage = "https://ipython.org"} requires-python = ">=3.8" @@ -36,7 +32,7 @@ dependencies = [ "tornado>=6.1", "matplotlib-inline>=0.1", 'appnope;platform_system=="Darwin"', - "pyzmq>=20", + "pyzmq>=24", "psutil", "packaging", ] @@ -175,6 +171,9 @@ filterwarnings= [ "ignore:unclosed event loop:ResourceWarning", "ignore:There is no current event loop:DeprecationWarning", "module:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning", + + # Ignore datetime warning. + "ignore:datetime.datetime.utc:DeprecationWarning", ] [tool.coverage.report] From 69967dcaffab58a713075025ba5b8a8e23acb381 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 26 Dec 2023 15:00:52 +0000 Subject: [PATCH 1044/1195] Publish 6.28.0 SHA256 hashes: ipykernel-6.28.0-py3-none-any.whl: c6e9a9c63a7f4095c0a22a79f765f079f9ec7be4f2430a898ddea889e8665661 ipykernel-6.28.0.tar.gz: 69c11403d26de69df02225916f916b37ea4b9af417da0a8c827f84328d88e5f3 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6404b4c9..4a8c9b18a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,34 @@ +## 6.28.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.27.1...de45c7a49e197f0889f867f33f24cce322768a0e)) + +### Enhancements made + +- Enable `ProactorEventLoop` on windows for `ipykernel` [#1184](https://github.com/ipython/ipykernel/pull/1184) ([@NewUserHa](https://github.com/NewUserHa)) +- Adds a flag in debug_info for the copyToGlobals support [#1099](https://github.com/ipython/ipykernel/pull/1099) ([@brichet](https://github.com/brichet)) + +### Maintenance and upkeep improvements + +- Support python 3.12 [#1185](https://github.com/ipython/ipykernel/pull/1185) ([@blink1073](https://github.com/blink1073)) +- Bump actions/setup-python from 4 to 5 [#1181](https://github.com/ipython/ipykernel/pull/1181) ([@dependabot](https://github.com/dependabot)) +- chore: update pre-commit hooks [#1179](https://github.com/ipython/ipykernel/pull/1179) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Refactor execute_request to reduce redundancy and improve consistency [#1177](https://github.com/ipython/ipykernel/pull/1177) ([@jjvraw](https://github.com/jjvraw)) + +### Documentation improvements + +- Update pytest commands in README [#1178](https://github.com/ipython/ipykernel/pull/1178) ([@ianthomas23](https://github.com/ianthomas23)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-11-27&to=2023-12-26&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-11-27..2023-12-26&type=Issues) | [@brichet](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abrichet+updated%3A2023-11-27..2023-12-26&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2023-11-27..2023-12-26&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2023-11-27..2023-12-26&type=Issues) | [@jjvraw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajjvraw+updated%3A2023-11-27..2023-12-26&type=Issues) | [@NewUserHa](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ANewUserHa+updated%3A2023-11-27..2023-12-26&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-11-27..2023-12-26&type=Issues) + + + ## 6.27.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.27.0...f9c517e868462d05d6854204c2ad0a244db1cd19)) @@ -16,8 +44,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-11-21..2023-11-27&type=Issues) - - ## 6.27.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.26.0...465d34483103d23f471a4795fe5fabb9cf7ac3f5)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 95309e141..bf7fe5057 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.27.1" +__version__ = "6.28.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 1e3bb9da993abb25a4e0dc6ad31670632fb6f13e Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Thu, 11 Jan 2024 20:48:22 +0000 Subject: [PATCH 1045/1195] Pin pytest-asyncio to 0.23.2 (#1189) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 57379b4b1..a4e79372d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ test = [ "flaky", "ipyparallel", "pre-commit", - "pytest-asyncio", + "pytest-asyncio==0.23.2", "pytest-timeout" ] cov = [ From 4868558a4ed3d8b09316a974e7a0c7e93d74e763 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Sat, 13 Jan 2024 03:23:01 +0000 Subject: [PATCH 1046/1195] Always set debugger to true in kernelspec (#1191) --- ipykernel/kernelspec.py | 2 +- tests/test_debugger.py | 98 +++++++++++++++++++++++++++++++++-------- 2 files changed, 81 insertions(+), 19 deletions(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 70253c15c..ae77d9cc8 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -66,7 +66,7 @@ def get_kernel_dict(extra_arguments: list[str] | None = None) -> dict[str, Any]: "argv": make_ipkernel_cmd(extra_arguments=extra_arguments), "display_name": "Python %i (ipykernel)" % sys.version_info[0], "language": "python", - "metadata": {"debugger": _is_debugpy_available}, + "metadata": {"debugger": True}, } diff --git a/tests/test_debugger.py b/tests/test_debugger.py index 262f102de..28866c3b4 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -6,8 +6,13 @@ seq = 0 -# Skip if debugpy is not available -pytest.importorskip("debugpy") +# Tests support debugpy not being installed, in which case the tests don't do anything useful +# functionally as the debug message replies are usually empty dictionaries, but they confirm that +# ipykernel doesn't block, or segfault, or raise an exception. +try: + import debugpy +except ImportError: + debugpy = None def wait_for_debug_request(kernel, command, arguments=None, full_reply=False): @@ -85,15 +90,21 @@ def test_debug_initialize(kernel): "locale": "en", }, ) - assert reply["success"] + if debugpy: + assert reply["success"] + else: + assert reply == {} def test_attach_debug(kernel_with_debug): reply = wait_for_debug_request( kernel_with_debug, "evaluate", {"expression": "'a' + 'b'", "context": "repl"} ) - assert reply["success"] - assert reply["body"]["result"] == "" + if debugpy: + assert reply["success"] + assert reply["body"]["result"] == "" + else: + assert reply == {} def test_set_breakpoints(kernel_with_debug): @@ -104,7 +115,11 @@ def test_set_breakpoints(kernel_with_debug): f(2, 3)""" r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) - source = r["body"]["sourcePath"] + if debugpy: + source = r["body"]["sourcePath"] + else: + assert r == {} + source = "non-existent path" reply = wait_for_debug_request( kernel_with_debug, @@ -115,20 +130,29 @@ def test_set_breakpoints(kernel_with_debug): "sourceModified": False, }, ) - assert reply["success"] - assert len(reply["body"]["breakpoints"]) == 1 - assert reply["body"]["breakpoints"][0]["verified"] - assert reply["body"]["breakpoints"][0]["source"]["path"] == source + if debugpy: + assert reply["success"] + assert len(reply["body"]["breakpoints"]) == 1 + assert reply["body"]["breakpoints"][0]["verified"] + assert reply["body"]["breakpoints"][0]["source"]["path"] == source + else: + assert reply == {} r = wait_for_debug_request(kernel_with_debug, "debugInfo") def func(b): return b["source"] - assert source in map(func, r["body"]["breakpoints"]) + if debugpy: + assert source in map(func, r["body"]["breakpoints"]) + else: + assert r == {} r = wait_for_debug_request(kernel_with_debug, "configurationDone") - assert r["success"] + if debugpy: + assert r["success"] + else: + assert r == {} def test_stop_on_breakpoint(kernel_with_debug): @@ -139,7 +163,11 @@ def test_stop_on_breakpoint(kernel_with_debug): f(2, 3)""" r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) - source = r["body"]["sourcePath"] + if debugpy: + source = r["body"]["sourcePath"] + else: + assert r == {} + source = "some path" wait_for_debug_request(kernel_with_debug, "debugInfo") @@ -157,6 +185,10 @@ def test_stop_on_breakpoint(kernel_with_debug): kernel_with_debug.execute(code) + if not debugpy: + # Cannot stop on breakpoint if debugpy not installed + return + # Wait for stop on breakpoint msg: dict = {"msg_type": "", "content": {}} while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": @@ -175,7 +207,11 @@ def f(a, b): f(2, 3)""" r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) - source = r["body"]["sourcePath"] + if debugpy: + source = r["body"]["sourcePath"] + else: + assert r == {} + source = "some path" wait_for_debug_request(kernel_with_debug, "debugInfo") @@ -193,6 +229,10 @@ def f(a, b): kernel_with_debug.execute(code) + if not debugpy: + # Cannot stop on breakpoint if debugpy not installed + return + # Wait for stop on breakpoint msg: dict = {"msg_type": "", "content": {}} while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": @@ -216,7 +256,10 @@ def test_rich_inspect_not_at_breakpoint(kernel_with_debug): def func(v): return v["name"] - assert var_name in list(map(func, r["body"]["variables"])) + if debugpy: + assert var_name in list(map(func, r["body"]["variables"])) + else: + assert r == {} reply = wait_for_debug_request( kernel_with_debug, @@ -224,7 +267,10 @@ def func(v): {"variableName": var_name}, ) - assert reply["body"]["data"] == {"text/plain": f"'{value}'"} + if debugpy: + assert reply["body"]["data"] == {"text/plain": f"'{value}'"} + else: + assert reply == {} def test_rich_inspect_at_breakpoint(kernel_with_debug): @@ -235,7 +281,11 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): f(2, 3)""" r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) - source = r["body"]["sourcePath"] + if debugpy: + source = r["body"]["sourcePath"] + else: + assert r == {} + source = "some path" wait_for_debug_request( kernel_with_debug, @@ -253,6 +303,10 @@ def test_rich_inspect_at_breakpoint(kernel_with_debug): kernel_with_debug.execute(code) + if not debugpy: + # Cannot stop on breakpoint if debugpy not installed + return + # Wait for stop on breakpoint msg: dict = {"msg_type": "", "content": {}} while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": @@ -304,7 +358,11 @@ def my_test(): # Init debugger and set breakpoint r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) - source = r["body"]["sourcePath"] + if debugpy: + source = r["body"]["sourcePath"] + else: + assert r == {} + source = "some path" wait_for_debug_request( kernel_with_debug, @@ -323,6 +381,10 @@ def my_test(): # Execute code kernel_with_debug.execute(code) + if not debugpy: + # Cannot stop on breakpoint if debugpy not installed + return + # Wait for stop on breakpoint msg: dict = {"msg_type": "", "content": {}} while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": From d57bbf2333c0630178e1d5d107574338a3f24c57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jan 2024 06:21:16 -0600 Subject: [PATCH 1047/1195] chore: update pre-commit hooks (#1187) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .github/dependabot.yml | 8 ++++++++ .pre-commit-config.yaml | 10 +++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cbd920f6b..fcdd3e88b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,15 @@ updates: directory: "/" schedule: interval: "weekly" + groups: + actions: + patterns: + - "*" - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" + groups: + actions: + patterns: + - "*" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7a1ccd2dc..a464ba480 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.2 + rev: 0.27.3 hooks: - id: check-github-workflows @@ -34,13 +34,13 @@ repos: [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.1.0" + rev: "v4.0.0-alpha.8" hooks: - id: prettier types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.7.1" + rev: "v1.8.0" hooks: - id: mypy files: ipykernel @@ -74,7 +74,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.6 + rev: v0.1.9 hooks: - id: ruff types_or: [python, jupyter] @@ -83,7 +83,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2023.11.17" + rev: "2023.12.21" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] From e8185dfc4a8102b20d17c948f3b822987a4ebb0d Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Mon, 15 Jan 2024 06:24:56 -0600 Subject: [PATCH 1048/1195] Revert "Enable `ProactorEventLoop` on windows for `ipykernel`" (#1194) --- ipykernel/kernelapp.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 6b42eda7c..9cfb55a54 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -624,6 +624,43 @@ def configure_tornado_logger(self): handler.setFormatter(formatter) logger.addHandler(handler) + def _init_asyncio_patch(self): + """set default asyncio policy to be compatible with tornado + + Tornado 6 (at least) is not compatible with the default + asyncio implementation on Windows + + Pick the older SelectorEventLoopPolicy on Windows + if the known-incompatible default policy is in use. + + Support for Proactor via a background thread is available in tornado 6.1, + but it is still preferable to run the Selector in the main thread + instead of the background. + + do this as early as possible to make it a low priority and overridable + + ref: https://github.com/tornadoweb/tornado/issues/2608 + + FIXME: if/when tornado supports the defaults in asyncio without threads, + remove and bump tornado requirement for py38. + Most likely, this will mean a new Python version + where asyncio.ProactorEventLoop supports add_reader and friends. + + """ + if sys.platform.startswith("win") and sys.version_info >= (3, 8): + import asyncio + + try: + from asyncio import WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy + except ImportError: + pass + # not affected + else: + if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: + # WindowsProactorEventLoopPolicy is not compatible with tornado 6 + # fallback to the pre-3.8 default of Selector + asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) + def init_pdb(self): """Replace pdb with IPython's version that is interruptible. @@ -643,6 +680,7 @@ def init_pdb(self): @catch_config_error def initialize(self, argv=None): """Initialize the application.""" + self._init_asyncio_patch() super().initialize(argv) if self.subapp is not None: return From 84955484ec1636ee4c7611471d20df2016b5cb57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Tue, 16 Jan 2024 14:41:20 +0000 Subject: [PATCH 1049/1195] Make outputs go to correct cell when generated in threads/asyncio (#1186) Co-authored-by: Steven Silvester --- ipykernel/iostream.py | 105 ++++++++++++++++++++++++++-------------- ipykernel/ipkernel.py | 94 +++++++++++++++++++++++++++++++++++ ipykernel/kernelbase.py | 8 +++ tests/test_kernel.py | 100 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 271 insertions(+), 36 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 0bbdbe275..257b5c800 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -5,6 +5,7 @@ import asyncio import atexit +import contextvars import io import os import sys @@ -12,7 +13,7 @@ import traceback import warnings from binascii import b2a_hex -from collections import deque +from collections import defaultdict, deque from io import StringIO, TextIOBase from threading import local from typing import Any, Callable, Deque, Dict, Optional @@ -412,7 +413,7 @@ def __init__( name : str {'stderr', 'stdout'} the name of the standard stream to replace pipe : object - the pip object + the pipe object echo : bool whether to echo output watchfd : bool (default, True) @@ -446,13 +447,19 @@ def __init__( self.pub_thread = pub_thread self.name = name self.topic = b"stream." + name.encode() - self.parent_header = {} + self._parent_header: contextvars.ContextVar[Dict[str, Any]] = contextvars.ContextVar( + "parent_header" + ) + self._parent_header.set({}) + self._thread_to_parent = {} + self._thread_to_parent_header = {} + self._parent_header_global = {} self._master_pid = os.getpid() self._flush_pending = False self._subprocess_flush_pending = False self._io_loop = pub_thread.io_loop self._buffer_lock = threading.RLock() - self._buffer = StringIO() + self._buffers = defaultdict(StringIO) self.echo = None self._isatty = bool(isatty) self._should_watch = False @@ -495,6 +502,30 @@ def __init__( msg = "echo argument must be a file-like object" raise ValueError(msg) + @property + def parent_header(self): + try: + # asyncio-specific + return self._parent_header.get() + except LookupError: + try: + # thread-specific + identity = threading.current_thread().ident + # retrieve the outermost (oldest ancestor, + # discounting the kernel thread) thread identity + while identity in self._thread_to_parent: + identity = self._thread_to_parent[identity] + # use the header of the oldest ancestor + return self._thread_to_parent_header[identity] + except KeyError: + # global (fallback) + return self._parent_header_global + + @parent_header.setter + def parent_header(self, value): + self._parent_header_global = value + return self._parent_header.set(value) + def isatty(self): """Return a bool indicating whether this is an 'interactive' stream. @@ -598,28 +629,28 @@ def _flush(self): if self.echo is not sys.__stderr__: print(f"Flush failed: {e}", file=sys.__stderr__) - data = self._flush_buffer() - if data: - # FIXME: this disables Session's fork-safe check, - # since pub_thread is itself fork-safe. - # There should be a better way to do this. - self.session.pid = os.getpid() - content = {"name": self.name, "text": data} - msg = self.session.msg("stream", content, parent=self.parent_header) - - # Each transform either returns a new - # message or None. If None is returned, - # the message has been 'used' and we return. - for hook in self._hooks: - msg = hook(msg) - if msg is None: - return - - self.session.send( - self.pub_thread, - msg, - ident=self.topic, - ) + for parent, data in self._flush_buffers(): + if data: + # FIXME: this disables Session's fork-safe check, + # since pub_thread is itself fork-safe. + # There should be a better way to do this. + self.session.pid = os.getpid() + content = {"name": self.name, "text": data} + msg = self.session.msg("stream", content, parent=parent) + + # Each transform either returns a new + # message or None. If None is returned, + # the message has been 'used' and we return. + for hook in self._hooks: + msg = hook(msg) + if msg is None: + return + + self.session.send( + self.pub_thread, + msg, + ident=self.topic, + ) def write(self, string: str) -> Optional[int]: # type:ignore[override] """Write to current stream after encoding if necessary @@ -630,6 +661,7 @@ def write(self, string: str) -> Optional[int]: # type:ignore[override] number of items from input parameter written to stream. """ + parent = self.parent_header if not isinstance(string, str): msg = f"write() argument must be str, not {type(string)}" # type:ignore[unreachable] @@ -649,7 +681,7 @@ def write(self, string: str) -> Optional[int]: # type:ignore[override] is_child = not self._is_master_process() # only touch the buffer in the IO thread to avoid races with self._buffer_lock: - self._buffer.write(string) + self._buffers[frozenset(parent.items())].write(string) if is_child: # mp.Pool cannot be trusted to flush promptly (or ever), # and this helps. @@ -675,19 +707,20 @@ def writable(self): """Test whether the stream is writable.""" return True - def _flush_buffer(self): + def _flush_buffers(self): """clear the current buffer and return the current buffer data.""" - buf = self._rotate_buffer() - data = buf.getvalue() - buf.close() - return data + buffers = self._rotate_buffers() + for frozen_parent, buffer in buffers.items(): + data = buffer.getvalue() + buffer.close() + yield dict(frozen_parent), data - def _rotate_buffer(self): + def _rotate_buffers(self): """Returns the current buffer and replaces it with an empty buffer.""" with self._buffer_lock: - old_buffer = self._buffer - self._buffer = StringIO() - return old_buffer + old_buffers = self._buffers + self._buffers = defaultdict(StringIO) + return old_buffers @property def _hooks(self): diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 2d0ec6ff2..40d57945f 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -2,6 +2,7 @@ import asyncio import builtins +import gc import getpass import os import signal @@ -14,6 +15,7 @@ import comm from IPython.core import release from IPython.utils.tokenutil import line_at_cursor, token_at_cursor +from jupyter_client.session import extract_header from traitlets import Any, Bool, HasTraits, Instance, List, Type, observe, observe_compat from zmq.eventloop.zmqstream import ZMQStream @@ -22,6 +24,7 @@ from .compiler import XCachingCompiler from .debugger import Debugger, _is_debugpy_available from .eventloops import _use_appnope +from .iostream import OutStream from .kernelbase import Kernel as KernelBase from .kernelbase import _accepts_parameters from .zmqshell import ZMQInteractiveShell @@ -151,6 +154,14 @@ def __init__(self, **kwargs): appnope.nope() + self._new_threads_parent_header = {} + self._initialize_thread_hooks() + + if hasattr(gc, "callbacks"): + # while `gc.callbacks` exists since Python 3.3, pypy does not + # implement it even as of 3.9. + gc.callbacks.append(self._clean_thread_parent_frames) + help_links = List( [ { @@ -341,6 +352,12 @@ def set_sigint_result(): # restore the previous sigint handler signal.signal(signal.SIGINT, save_sigint) + async def execute_request(self, stream, ident, parent): + """Override for cell output - cell reconciliation.""" + parent_header = extract_header(parent) + self._associate_new_top_level_threads_with(parent_header) + await super().execute_request(stream, ident, parent) + async def do_execute( self, code, @@ -706,6 +723,83 @@ def do_clear(self): self.shell.reset(False) return dict(status="ok") + def _associate_new_top_level_threads_with(self, parent_header): + """Store the parent header to associate it with new top-level threads""" + self._new_threads_parent_header = parent_header + + def _initialize_thread_hooks(self): + """Store thread hierarchy and thread-parent_header associations.""" + stdout = self._stdout + stderr = self._stderr + kernel_thread_ident = threading.get_ident() + kernel = self + _threading_Thread_run = threading.Thread.run + _threading_Thread__init__ = threading.Thread.__init__ + + def run_closure(self: threading.Thread): + """Wrap the `threading.Thread.start` to intercept thread identity. + + This is needed because there is no "start" hook yet, but there + might be one in the future: https://bugs.python.org/issue14073 + + This is a no-op if the `self._stdout` and `self._stderr` are not + sub-classes of `OutStream`. + """ + + try: + parent = self._ipykernel_parent_thread_ident # type:ignore[attr-defined] + except AttributeError: + return + for stream in [stdout, stderr]: + if isinstance(stream, OutStream): + if parent == kernel_thread_ident: + stream._thread_to_parent_header[ + self.ident + ] = kernel._new_threads_parent_header + else: + stream._thread_to_parent[self.ident] = parent + _threading_Thread_run(self) + + def init_closure(self: threading.Thread, *args, **kwargs): + _threading_Thread__init__(self, *args, **kwargs) + self._ipykernel_parent_thread_ident = threading.get_ident() # type:ignore[attr-defined] + + threading.Thread.__init__ = init_closure # type:ignore[method-assign] + threading.Thread.run = run_closure # type:ignore[method-assign] + + def _clean_thread_parent_frames( + self, phase: t.Literal["start", "stop"], info: t.Dict[str, t.Any] + ): + """Clean parent frames of threads which are no longer running. + This is meant to be invoked by garbage collector callback hook. + + The implementation enumerates the threads because there is no "exit" hook yet, + but there might be one in the future: https://bugs.python.org/issue14073 + + This is a no-op if the `self._stdout` and `self._stderr` are not + sub-classes of `OutStream`. + """ + # Only run before the garbage collector starts + if phase != "start": + return + active_threads = {thread.ident for thread in threading.enumerate()} + for stream in [self._stdout, self._stderr]: + if isinstance(stream, OutStream): + thread_to_parent_header = stream._thread_to_parent_header + for identity in list(thread_to_parent_header.keys()): + if identity not in active_threads: + try: + del thread_to_parent_header[identity] + except KeyError: + pass + thread_to_parent = stream._thread_to_parent + for identity in list(thread_to_parent.keys()): + if identity not in active_threads: + try: + del thread_to_parent[identity] + except KeyError: + pass + # This exists only for backwards compatibility - use IPythonKernel instead diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index f9eb2b942..79bca7b4e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -61,6 +61,7 @@ from ipykernel.jsonutil import json_clean from ._version import kernel_protocol_version +from .iostream import OutStream def _accepts_parameters(meth, param_names): @@ -272,6 +273,13 @@ def _parent_header(self): def __init__(self, **kwargs): """Initialize the kernel.""" super().__init__(**kwargs) + + # Kernel application may swap stdout and stderr to OutStream, + # which is the case in `IPKernelApp.init_io`, hence `sys.stdout` + # can already by different from TextIO at initialization time. + self._stdout: OutStream | t.TextIO = sys.stdout + self._stderr: OutStream | t.TextIO = sys.stderr + # Build dict of handlers for message types self.shell_handlers = {} for msg_type in self.msg_types: diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 07411bd13..313388965 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -58,6 +58,106 @@ def test_simple_print(): _check_master(kc, expected=True) +def test_print_to_correct_cell_from_thread(): + """should print to the cell that spawned the thread, not a subsequently run cell""" + iterations = 5 + interval = 0.25 + code = f"""\ + from threading import Thread + from time import sleep + + def thread_target(): + for i in range({iterations}): + print(i, end='', flush=True) + sleep({interval}) + + Thread(target=thread_target).start() + """ + with kernel() as kc: + thread_msg_id = kc.execute(code) + _ = kc.execute("pass") + + received = 0 + while received < iterations: + msg = kc.get_iopub_msg(timeout=interval * 2) + if msg["msg_type"] != "stream": + continue + content = msg["content"] + assert content["name"] == "stdout" + assert content["text"] == str(received) + # this is crucial as the parent header decides to which cell the output goes + assert msg["parent_header"]["msg_id"] == thread_msg_id + received += 1 + + +def test_print_to_correct_cell_from_child_thread(): + """should print to the cell that spawned the thread, not a subsequently run cell""" + iterations = 5 + interval = 0.25 + code = f"""\ + from threading import Thread + from time import sleep + + def child_target(): + for i in range({iterations}): + print(i, end='', flush=True) + sleep({interval}) + + def parent_target(): + sleep({interval}) + Thread(target=child_target).start() + + Thread(target=parent_target).start() + """ + with kernel() as kc: + thread_msg_id = kc.execute(code) + _ = kc.execute("pass") + + received = 0 + while received < iterations: + msg = kc.get_iopub_msg(timeout=interval * 2) + if msg["msg_type"] != "stream": + continue + content = msg["content"] + assert content["name"] == "stdout" + assert content["text"] == str(received) + # this is crucial as the parent header decides to which cell the output goes + assert msg["parent_header"]["msg_id"] == thread_msg_id + received += 1 + + +def test_print_to_correct_cell_from_asyncio(): + """should print to the cell that scheduled the task, not a subsequently run cell""" + iterations = 5 + interval = 0.25 + code = f"""\ + import asyncio + + async def async_task(): + for i in range({iterations}): + print(i, end='', flush=True) + await asyncio.sleep({interval}) + + loop = asyncio.get_event_loop() + loop.create_task(async_task()); + """ + with kernel() as kc: + thread_msg_id = kc.execute(code) + _ = kc.execute("pass") + + received = 0 + while received < iterations: + msg = kc.get_iopub_msg(timeout=interval * 2) + if msg["msg_type"] != "stream": + continue + content = msg["content"] + assert content["name"] == "stdout" + assert content["text"] == str(received) + # this is crucial as the parent header decides to which cell the output goes + assert msg["parent_header"]["msg_id"] == thread_msg_id + received += 1 + + @pytest.mark.skip(reason="Currently don't capture during test as pytest does its own capturing") def test_capture_fd(): """simple print statement in kernel""" From 93a63fb7b8752899ed95118fa35e56f74eedd0c6 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 16 Jan 2024 14:44:29 +0000 Subject: [PATCH 1050/1195] Publish 6.29.0 SHA256 hashes: ipykernel-6.29.0-py3-none-any.whl: 076663ca68492576f051e4af7720d33f34383e655f2be0d544c8b1c9de915b2f ipykernel-6.29.0.tar.gz: b5dd3013cab7b330df712891c96cd1ab868c27a7159e606f762015e9bf8ceb3f --- CHANGELOG.md | 28 ++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a8c9b18a..81db9387a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ +## 6.29.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.28.0...84955484ec1636ee4c7611471d20df2016b5cb57)) + +### Enhancements made + +- Always set debugger to true in kernelspec [#1191](https://github.com/ipython/ipykernel/pull/1191) ([@ianthomas23](https://github.com/ianthomas23)) + +### Bugs fixed + +- Revert "Enable `ProactorEventLoop` on windows for `ipykernel`" [#1194](https://github.com/ipython/ipykernel/pull/1194) ([@blink1073](https://github.com/blink1073)) +- Make outputs go to correct cell when generated in threads/asyncio [#1186](https://github.com/ipython/ipykernel/pull/1186) ([@krassowski](https://github.com/krassowski)) + +### Maintenance and upkeep improvements + +- Pin pytest-asyncio to 0.23.2 [#1189](https://github.com/ipython/ipykernel/pull/1189) ([@ianthomas23](https://github.com/ianthomas23)) +- chore: update pre-commit hooks [#1187](https://github.com/ipython/ipykernel/pull/1187) ([@pre-commit-ci](https://github.com/pre-commit-ci)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2023-12-26&to=2024-01-16&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-12-26..2024-01-16&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2023-12-26..2024-01-16&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2023-12-26..2024-01-16&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-12-26..2024-01-16&type=Issues) + + + ## 6.28.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.27.1...de45c7a49e197f0889f867f33f24cce322768a0e)) @@ -28,8 +54,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-11-27..2023-12-26&type=Issues) | [@brichet](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abrichet+updated%3A2023-11-27..2023-12-26&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2023-11-27..2023-12-26&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2023-11-27..2023-12-26&type=Issues) | [@jjvraw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajjvraw+updated%3A2023-11-27..2023-12-26&type=Issues) | [@NewUserHa](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ANewUserHa+updated%3A2023-11-27..2023-12-26&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-11-27..2023-12-26&type=Issues) - - ## 6.27.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.27.0...f9c517e868462d05d6854204c2ad0a244db1cd19)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index bf7fe5057..4c05812c4 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.28.0" +__version__ = "6.29.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From e63d063d96e1de610d104217d40d0a08d7f6ea65 Mon Sep 17 00:00:00 2001 From: Sven Date: Sun, 28 Jan 2024 18:36:36 +0100 Subject: [PATCH 1051/1195] Fix handling of "silent" in execute request (#1200) --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 79bca7b4e..83aebe6f0 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -733,7 +733,7 @@ async def execute_request(self, stream, ident, parent): try: content = parent["content"] code = content["code"] - silent = content["silent"] + silent = content.get("silent", False) store_history = content.get("store_history", not silent) user_expressions = content.get("user_expressions", {}) allow_stdin = content.get("allow_stdin", False) From 8672cb0a4454f8090f7c6779bec5b39a3f08b159 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 17:31:39 -0600 Subject: [PATCH 1052/1195] Bump the actions group with 1 update (#1201) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a4e79372d..3d45d68ee 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ test = [ "flaky", "ipyparallel", "pre-commit", - "pytest-asyncio==0.23.2", + "pytest-asyncio==0.23.4", "pytest-timeout" ] cov = [ From 6172dc0d28973b494c600c2286f057947ed4fb56 Mon Sep 17 00:00:00 2001 From: Peter Vandenabeele Date: Tue, 6 Feb 2024 01:10:57 +0100 Subject: [PATCH 1053/1195] Do git ignore of /node_modules/.cache (#1203) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a986023db..1902acdc1 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ __pycache__ \#*# .#* .coverage +.cache data_kernelspec .pytest_cache From a9936266b54f3c674fdb9e73e74411a53b1a6c29 Mon Sep 17 00:00:00 2001 From: Peter Vandenabeele Date: Tue, 6 Feb 2024 01:13:46 +0100 Subject: [PATCH 1054/1195] fix: on exception, return a 0, so that the "sum" still computes (#1204) --- ipykernel/kernelbase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 83aebe6f0..4bf2dee35 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -1049,7 +1049,7 @@ def get_process_metric_value(self, process, name, attribute=None): # Avoid littering logs with stack traces # complaining about dead processes except BaseException: - return None + return 0 async def usage_request(self, stream, ident, parent): """Handle a usage request.""" From 09c9b2ad9c15202c5d1896ba24ec978b726c073b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 18:28:22 -0600 Subject: [PATCH 1055/1195] chore: update pre-commit hooks (#1205) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Steven Silvester --- .pre-commit-config.yaml | 6 +++--- ipykernel/debugger.py | 4 ++-- ipykernel/kernelapp.py | 2 +- ipykernel/pickleutil.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a464ba480..cc2cfd9d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.3 + rev: 0.27.4 hooks: - id: check-github-workflows @@ -74,7 +74,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.9 + rev: v0.2.0 hooks: - id: ruff types_or: [python, jupyter] @@ -83,7 +83,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2023.12.21" + rev: "2024.01.24" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 138b77261..657cc80cc 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -473,7 +473,7 @@ async def setBreakpoints(self, message): message_response = await self._forward_message(message) # debugpy can set breakpoints on different lines than the ones requested, # so we want to record the breakpoints that were actually added - if "success" in message_response and message_response["success"]: + if message_response.get("success"): self.breakpoint_list[source] = [ {"line": breakpoint["line"]} for breakpoint in message_response["body"]["breakpoints"] @@ -661,7 +661,7 @@ async def richInspectVariables(self, message): } ) if reply["success"]: - repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) # noqa: PGH001 + repr_data, repr_metadata = eval(reply["body"]["result"], {}, {}) body = { "data": repr_data, diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 9cfb55a54..097b65aa9 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -675,7 +675,7 @@ def init_pdb(self): # Only available in newer IPython releases: debugger.Pdb = debugger.InterruptiblePdb # type:ignore[misc] pdb.Pdb = debugger.Pdb # type:ignore[assignment,misc] - pdb.set_trace = debugger.set_trace # type:ignore[assignment] + pdb.set_trace = debugger.set_trace @catch_config_error def initialize(self, argv=None): diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 3cfb74a50..a52115da2 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -179,7 +179,7 @@ def get_object(self, g=None): if g is None: g = {} - return eval(self.name, g) # noqa: PGH001 + return eval(self.name, g) class CannedCell(CannedObject): From e8dd96143695967cdfe084bc49349eb667b91a76 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Tue, 6 Feb 2024 00:32:19 +0000 Subject: [PATCH 1056/1195] Publish 6.29.1 SHA256 hashes: ipykernel-6.29.1-py3-none-any.whl: e5dfba210fc9da74a5dae8fa6c41f816e11bd18d10381b2517d9a0d57cc987c4 ipykernel-6.29.1.tar.gz: 1547352b32da95a2761011a8dac2af930c26a0703dfa07690d16b7d74dac0ba1 --- CHANGELOG.md | 25 +++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81db9387a..b866255a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,29 @@ +## 6.29.1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.0...09c9b2ad9c15202c5d1896ba24ec978b726c073b)) + +### Bugs fixed + +- fix: on exception, return a 0, so that the "sum" still computes [#1204](https://github.com/ipython/ipykernel/pull/1204) ([@petervandenabeele](https://github.com/petervandenabeele)) +- Fix handling of "silent" in execute request [#1200](https://github.com/ipython/ipykernel/pull/1200) ([@Haadem](https://github.com/Haadem)) + +### Maintenance and upkeep improvements + +- chore: update pre-commit hooks [#1205](https://github.com/ipython/ipykernel/pull/1205) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Do git ignore of /node_modules/.cache [#1203](https://github.com/ipython/ipykernel/pull/1203) ([@petervandenabeele](https://github.com/petervandenabeele)) +- Bump the actions group with 1 update [#1201](https://github.com/ipython/ipykernel/pull/1201) ([@dependabot](https://github.com/dependabot)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-01-16&to=2024-02-06&type=c)) + +[@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2024-01-16..2024-02-06&type=Issues) | [@Haadem](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AHaadem+updated%3A2024-01-16..2024-02-06&type=Issues) | [@petervandenabeele](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apetervandenabeele+updated%3A2024-01-16..2024-02-06&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2024-01-16..2024-02-06&type=Issues) + + + ## 6.29.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.28.0...84955484ec1636ee4c7611471d20df2016b5cb57)) @@ -26,8 +49,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-12-26..2024-01-16&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2023-12-26..2024-01-16&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2023-12-26..2024-01-16&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2023-12-26..2024-01-16&type=Issues) - - ## 6.28.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.27.1...de45c7a49e197f0889f867f33f24cce322768a0e)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 4c05812c4..459076a4e 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.29.0" +__version__ = "6.29.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From edbbd63bd3f957c1482f7390de90cce07a448a3a Mon Sep 17 00:00:00 2001 From: "Takuya. O" <16006732+stdll00@users.noreply.github.com> Date: Wed, 7 Feb 2024 22:39:20 +0900 Subject: [PATCH 1057/1195] Fix: ipykernel_launcher, delete absolute sys.path[0] (#1206) --- ipykernel_launcher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ipykernel_launcher.py b/ipykernel_launcher.py index 49aa2651a..0739d4b1a 100644 --- a/ipykernel_launcher.py +++ b/ipykernel_launcher.py @@ -5,11 +5,12 @@ """ import sys +from pathlib import Path if __name__ == "__main__": # Remove the CWD from sys.path while we load stuff. # This is added back by InteractiveShellApp.init_path() - if sys.path[0] == "": + if sys.path[0] == "" or Path(sys.path[0]) == Path.cwd(): del sys.path[0] from ipykernel import kernelapp as app From d45fe71990d26c0bd5b7b3b2a4ccd3d1f6609899 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 7 Feb 2024 13:39:57 +0000 Subject: [PATCH 1058/1195] Re-enable skipped debugger test (#1207) --- tests/test_debugger.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_debugger.py b/tests/test_debugger.py index 28866c3b4..fa64d2d09 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -197,7 +197,6 @@ def test_stop_on_breakpoint(kernel_with_debug): assert msg["content"]["body"]["reason"] == "breakpoint" -@pytest.mark.skipif(sys.version_info >= (3, 10), reason="TODO Does not work on Python 3.10") def test_breakpoint_in_cell_with_leading_empty_lines(kernel_with_debug): code = """ def f(a, b): From 1b4eb5e57dc7ab575870087a7909c7d041b48c02 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Wed, 7 Feb 2024 13:42:59 +0000 Subject: [PATCH 1059/1195] Publish 6.29.2 SHA256 hashes: ipykernel-6.29.2-py3-none-any.whl: 50384f5c577a260a1d53f1f59a828c7266d321c9b7d00d345693783f66616055 ipykernel-6.29.2.tar.gz: 3bade28004e3ff624ed57974948116670604ac5f676d12339693f3142176d3f0 --- CHANGELOG.md | 22 ++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b866255a2..ce6c1611b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ +## 6.29.2 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.1...d45fe71990d26c0bd5b7b3b2a4ccd3d1f6609899)) + +### Bugs fixed + +- Fix: ipykernel_launcher, delete absolute sys.path\[0\] [#1206](https://github.com/ipython/ipykernel/pull/1206) ([@stdll00](https://github.com/stdll00)) + +### Maintenance and upkeep improvements + +- Re-enable skipped debugger test [#1207](https://github.com/ipython/ipykernel/pull/1207) ([@ianthomas23](https://github.com/ianthomas23)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-02-06&to=2024-02-07&type=c)) + +[@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-02-06..2024-02-07&type=Issues) | [@stdll00](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Astdll00+updated%3A2024-02-06..2024-02-07&type=Issues) + + + ## 6.29.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.0...09c9b2ad9c15202c5d1896ba24ec978b726c073b)) @@ -23,8 +43,6 @@ [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2024-01-16..2024-02-06&type=Issues) | [@Haadem](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AHaadem+updated%3A2024-01-16..2024-02-06&type=Issues) | [@petervandenabeele](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apetervandenabeele+updated%3A2024-01-16..2024-02-06&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2024-01-16..2024-02-06&type=Issues) - - ## 6.29.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.28.0...84955484ec1636ee4c7611471d20df2016b5cb57)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 459076a4e..7ee214639 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.29.1" +__version__ = "6.29.2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 05c615337d29b2e333a3766f4b41bf69d7a21cf0 Mon Sep 17 00:00:00 2001 From: Josiah Outram Halstead Date: Fri, 9 Feb 2024 01:50:31 +0000 Subject: [PATCH 1060/1195] Correct spelling mistake (#1208) --- ipykernel/debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 657cc80cc..728d2dae9 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -610,7 +610,7 @@ async def debugInfo(self, message): } async def inspectVariables(self, message): - """Handle an insepct variables message.""" + """Handle an inspect variables message.""" self.variable_explorer.untrack_all() # looks like the implementation of untrack_all in ptvsd # destroys objects we nee din track. We have no choice but From c6d5ad6ce0410a828d65a8c4cf1356dab2ed69a2 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 10 Feb 2024 12:56:52 -0600 Subject: [PATCH 1061/1195] Unpin pytest-asyncio and update ruff config (#1209) --- docs/conf.py | 2 +- hatch_build.py | 1 - ipykernel/__init__.py | 2 +- ipykernel/compiler.py | 2 +- ipykernel/datapub.py | 18 +++++------ ipykernel/debugger.py | 2 +- ipykernel/eventloops.py | 10 +++--- ipykernel/gui/gtk3embed.py | 10 +++--- ipykernel/gui/gtkembed.py | 8 ++--- ipykernel/ipkernel.py | 2 +- ipykernel/kernelspec.py | 3 +- ipykernel/pickleutil.py | 18 +++++------ ipykernel/pylab/backend_inline.py | 2 +- ipykernel/pylab/config.py | 2 +- ipykernel/serialize.py | 15 +++++---- pyproject.toml | 52 +++++++++++++++++-------------- 16 files changed, 74 insertions(+), 75 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 643740ec0..4bb599327 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -37,7 +37,7 @@ ] try: - import enchant + import enchant # noqa: F401 extensions += ["sphinxcontrib.spelling"] except ImportError: diff --git a/hatch_build.py b/hatch_build.py index 410b4603e..934348050 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -1,5 +1,4 @@ """A custom hatch build hook for ipykernel.""" -import os import shutil import sys from pathlib import Path diff --git a/ipykernel/__init__.py b/ipykernel/__init__.py index bc5fbb726..978700d30 100644 --- a/ipykernel/__init__.py +++ b/ipykernel/__init__.py @@ -4,4 +4,4 @@ kernel_protocol_version_info, version_info, ) -from .connect import * +from .connect import * # noqa: F403 diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index 0657f5693..e42007ed6 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -45,7 +45,7 @@ def murmur2_x86(data, seed): return h -convert_to_long_pathname = lambda filename: filename +convert_to_long_pathname = lambda filename: filename # noqa: E731 if sys.platform == "win32": try: diff --git a/ipykernel/datapub.py b/ipykernel/datapub.py index 5805c9afd..cc19696db 100644 --- a/ipykernel/datapub.py +++ b/ipykernel/datapub.py @@ -1,17 +1,11 @@ +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + """Publishing native (typically pickled) objects. """ import warnings -warnings.warn( - "ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", - DeprecationWarning, - stacklevel=2, -) - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - from traitlets import Any, CBytes, Dict, Instance from traitlets.config import Configurable @@ -26,6 +20,12 @@ from jupyter_client.session import Session, extract_header +warnings.warn( + "ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", + DeprecationWarning, + stacklevel=2, +) + class ZMQDataPublisher(Configurable): """A zmq data publisher.""" diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 728d2dae9..fd192e157 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -21,7 +21,7 @@ try: # This import is required to have the next ones working... - from debugpy.server import api + from debugpy.server import api # noqa: F401 from _pydevd_bundle import pydevd_frame_utils # isort: skip from _pydevd_bundle.pydevd_suspended_frames import ( # isort: skip diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 401aebd3e..c3ddd301e 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -484,24 +484,24 @@ def set_qt_api_env_from_gui(gui): else: if gui == "qt5": try: - import PyQt5 + import PyQt5 # noqa: F401 os.environ["QT_API"] = "pyqt5" except ImportError: try: - import PySide2 + import PySide2 # noqa: F401 os.environ["QT_API"] = "pyside2" except ImportError: os.environ["QT_API"] = "pyqt5" elif gui == "qt6": try: - import PyQt6 + import PyQt6 # noqa: F401 os.environ["QT_API"] = "pyqt6" except ImportError: try: - import PySide6 + import PySide6 # noqa: F401 os.environ["QT_API"] = "pyside6" except ImportError: @@ -516,7 +516,7 @@ def set_qt_api_env_from_gui(gui): # Do the actual import now that the environment variable is set to make sure it works. try: - from IPython.external.qt_for_kernel import QtCore, QtGui + pass except Exception as e: # Clear the environment variable for the next attempt. if "QT_API" in os.environ: diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index f270b5dd0..3317ecfe4 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -14,16 +14,16 @@ import sys import warnings -warnings.warn( - "The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning, stacklevel=2 -) - # Third-party import gi gi.require_version("Gdk", "3.0") gi.require_version("Gtk", "3.0") -from gi.repository import GObject, Gtk +from gi.repository import GObject, Gtk # noqa: E402 + +warnings.warn( + "The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning, stacklevel=2 +) # ----------------------------------------------------------------------------- # Classes and functions diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index f6b8bf5bb..e87249ead 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -14,14 +14,14 @@ import sys import warnings -warnings.warn( - "The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning, stacklevel=2 -) - # Third-party import gobject import gtk +warnings.warn( + "The Gtk3 event loop for ipykernel is deprecated", category=DeprecationWarning, stacklevel=2 +) + # ----------------------------------------------------------------------------- # Classes and functions # ----------------------------------------------------------------------------- diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 40d57945f..9bea4d56b 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -381,7 +381,7 @@ async def do_execute( should_run_async = shell.should_run_async accepts_params = _accepts_parameters(run_cell, ["cell_id"]) else: - should_run_async = lambda cell: False # noqa: ARG005 + should_run_async = lambda cell: False # noqa: ARG005, E731 # older IPython, # use blocking run_cell and wrap it in coroutine diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index ae77d9cc8..a1d508c38 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -17,6 +17,7 @@ from jupyter_client.kernelspec import KernelSpecManager from traitlets import Unicode +from traitlets.config import Application try: from .debugger import _is_debugpy_available @@ -171,8 +172,6 @@ def install( # Entrypoint -from traitlets.config import Application - class InstallIPythonKernelSpecApp(Application): """Dummy app wrapping argparse""" diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index a52115da2..c6a2e4484 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -2,25 +2,23 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import typing -import warnings - -warnings.warn( - "ipykernel.pickleutil is deprecated. It has moved to ipyparallel.", - DeprecationWarning, - stacklevel=2, -) - import copy import pickle import sys +import typing +import warnings from types import FunctionType # This registers a hook when it's imported -from ipyparallel.serialize import codeutil from traitlets.log import get_logger from traitlets.utils.importstring import import_item +warnings.warn( + "ipykernel.pickleutil is deprecated. It has moved to ipyparallel.", + DeprecationWarning, + stacklevel=2, +) + buffer = memoryview class_type = type diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index cbc253064..fdeece28c 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.backend_inline import * # type:ignore[import-untyped] # analysis: ignore +from matplotlib_inline.backend_inline import * # type:ignore[import-untyped] # noqa: F403 # analysis: ignore warnings.warn( "`ipykernel.pylab.backend_inline` is deprecated, directly " diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index f32787283..0048bbf84 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.config import * # type:ignore[import-untyped] # analysis: ignore +from matplotlib_inline.config import * # type:ignore[import-untyped] # noqa: F403 # analysis: ignore warnings.warn( "`ipykernel.pylab.config` is deprecated, directly use `matplotlib_inline.config`", diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py index 616410c81..22ba5396e 100644 --- a/ipykernel/serialize.py +++ b/ipykernel/serialize.py @@ -3,15 +3,8 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import warnings - -warnings.warn( - "ipykernel.serialize is deprecated. It has moved to ipyparallel.serialize", - DeprecationWarning, - stacklevel=2, -) - import pickle +import warnings from itertools import chain try: @@ -41,6 +34,12 @@ from jupyter_client.session import MAX_BYTES, MAX_ITEMS +warnings.warn( + "ipykernel.serialize is deprecated. It has moved to ipyparallel.serialize", + DeprecationWarning, + stacklevel=2, +) + # ----------------------------------------------------------------------------- # Serialization Functions # ----------------------------------------------------------------------------- diff --git a/pyproject.toml b/pyproject.toml index 3d45d68ee..532b8ffbb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,7 +53,7 @@ test = [ "flaky", "ipyparallel", "pre-commit", - "pytest-asyncio==0.23.4", + "pytest-asyncio>=0.23.5", "pytest-timeout" ] cov = [ @@ -209,29 +209,29 @@ omit = [ line-length = 100 [tool.ruff.lint] -select = [ - "B", # flake8-bugbear - "I", # isort - "ARG", # flake8-unused-arguments - "C4", # flake8-comprehensions - "EM", # flake8-errmsg - "ICN", # flake8-import-conventions - "G", # flake8-logging-format - "PGH", # pygrep-hooks - "PIE", # flake8-pie - "PL", # pylint - "PT", # flake8-pytest-style - "PTH", # flake8-use-pathlib - "RET", # flake8-return - "RUF", # Ruff-specific - "SIM", # flake8-simplify - "T20", # flake8-print - "UP", # pyupgrade - "YTT", # flake8-2020 - "EXE", # flake8-executable - "NPY", # NumPy specific rules - "PD", # pandas-vet - "PYI", # flake8-pyi +extend-select = [ + "B", # flake8-bugbear + "I", # isort + "ARG", # flake8-unused-arguments + "C4", # flake8-comprehensions + "EM", # flake8-errmsg + "ICN", # flake8-import-conventions + "G", # flake8-logging-format + "PGH", # pygrep-hooks + "PIE", # flake8-pie + "PL", # pylint + "PT", # flake8-pytest-style + "PTH", # flake8-use-pathlib + "RET", # flake8-return + "RUF", # Ruff-specific + "SIM", # flake8-simplify + "T20", # flake8-print + "UP", # pyupgrade + "YTT", # flake8-2020 + "EXE", # flake8-executable + "NPY", # NumPy specific rules + "PD", # pandas-vet + "PYI", # flake8-pyi ] ignore = [ "PLR", # Design related pylint codes @@ -265,6 +265,9 @@ unfixable = [ "T201", # Don't touch noqa lines "RUF100", + # Don't touch imports + "F401", + "F403" ] [tool.ruff.lint.per-file-ignores] @@ -277,6 +280,7 @@ unfixable = [ # B018 Found useless expression. Either assign it to a variable or remove it. # S603 `subprocess` call: check for execution of untrusted input "tests/*" = ["B011", "C408", "T201", "B007", "EM", "PTH", "PLW", "PLC1901", "B018", "S603", "ARG", "RET", "PGH"] +"*/__init__.py" = ["F401"] [tool.interrogate] ignore-init-module=true From eddd3e666a82ebec287168b0da7cfa03639a3772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Thu, 15 Feb 2024 02:26:07 +0000 Subject: [PATCH 1062/1195] Disable frozen modules by default, add a toggle (#1213) --- ipykernel/kernelspec.py | 57 +++++++++++++++++++++++++++++++--------- tests/test_kernelspec.py | 48 +++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 13 deletions(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index a1d508c38..c0cc9ffe7 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -8,6 +8,7 @@ import errno import json import os +import platform import shutil import stat import sys @@ -19,11 +20,6 @@ from traitlets import Unicode from traitlets.config import Application -try: - from .debugger import _is_debugpy_available -except ImportError: - _is_debugpy_available = False - pjoin = os.path.join KERNEL_NAME = "python%i" % sys.version_info[0] @@ -36,6 +32,7 @@ def make_ipkernel_cmd( mod: str = "ipykernel_launcher", executable: str | None = None, extra_arguments: list[str] | None = None, + python_arguments: list[str] | None = None, ) -> list[str]: """Build Popen command list for launching an IPython kernel. @@ -55,16 +52,18 @@ def make_ipkernel_cmd( if executable is None: executable = sys.executable extra_arguments = extra_arguments or [] - arguments = [executable, "-m", mod, "-f", "{connection_file}"] - arguments.extend(extra_arguments) - - return arguments + python_arguments = python_arguments or [] + return [executable, *python_arguments, "-m", mod, "-f", "{connection_file}", *extra_arguments] -def get_kernel_dict(extra_arguments: list[str] | None = None) -> dict[str, Any]: +def get_kernel_dict( + extra_arguments: list[str] | None = None, python_arguments: list[str] | None = None +) -> dict[str, Any]: """Construct dict for kernel.json""" return { - "argv": make_ipkernel_cmd(extra_arguments=extra_arguments), + "argv": make_ipkernel_cmd( + extra_arguments=extra_arguments, python_arguments=python_arguments + ), "display_name": "Python %i (ipykernel)" % sys.version_info[0], "language": "python", "metadata": {"debugger": True}, @@ -75,6 +74,7 @@ def write_kernel_spec( path: Path | str | None = None, overrides: dict[str, Any] | None = None, extra_arguments: list[str] | None = None, + python_arguments: list[str] | None = None, ) -> str: """Write a kernel spec directory to `path` @@ -95,7 +95,7 @@ def write_kernel_spec( Path(path).chmod(mask | stat.S_IWUSR) # write kernel.json - kernel_dict = get_kernel_dict(extra_arguments) + kernel_dict = get_kernel_dict(extra_arguments, python_arguments) if overrides: kernel_dict.update(overrides) @@ -113,6 +113,7 @@ def install( prefix: str | None = None, profile: str | None = None, env: dict[str, str] | None = None, + frozen_modules: bool = False, ) -> str: """Install the IPython kernelspec for Jupyter @@ -137,6 +138,12 @@ def install( A dictionary of extra environment variables for the kernel. These will be added to the current environment variables before the kernel is started + frozen_modules : bool, optional + Whether to use frozen modules for potentially faster kernel startup. + Using frozen modules prevents debugging inside of some built-in + Python modules, such as io, abc, posixpath, ntpath, or stat. + The frozen modules are used in CPython for faster interpreter startup. + Ignored for cPython <3.11 and for other Python implementations. Returns ------- @@ -145,6 +152,9 @@ def install( if kernel_spec_manager is None: kernel_spec_manager = KernelSpecManager() + if env is None: + env = {} + if (kernel_name != KERNEL_NAME) and (display_name is None): # kernel_name is specified and display_name is not # default display_name to kernel_name @@ -159,9 +169,24 @@ def install( overrides["display_name"] = "Python %i [profile=%s]" % (sys.version_info[0], profile) else: extra_arguments = None + + python_arguments = None + + # addresses the debugger warning from debugpy about frozen modules + if sys.version_info >= (3, 11) and platform.python_implementation() == "CPython": + if not frozen_modules: + # disable frozen modules + python_arguments = ["-Xfrozen_modules=off"] + elif "PYDEVD_DISABLE_FILE_VALIDATION" not in env: + # user opted-in to have frozen modules, and we warned them about + # consequences for the - disable the debugger warning + env["PYDEVD_DISABLE_FILE_VALIDATION"] = "1" + if env: overrides["env"] = env - path = write_kernel_spec(overrides=overrides, extra_arguments=extra_arguments) + path = write_kernel_spec( + overrides=overrides, extra_arguments=extra_arguments, python_arguments=python_arguments + ) dest = kernel_spec_manager.install_kernel_spec( path, kernel_name=kernel_name, user=user, prefix=prefix ) @@ -236,6 +261,12 @@ def start(self) -> None: metavar=("ENV", "VALUE"), help="Set environment variables for the kernel.", ) + parser.add_argument( + "--frozen_modules", + action="store_true", + help="Enable frozen modules for potentially faster startup." + " This has a downside of preventing the debugger from navigating to certain built-in modules.", + ) opts = parser.parse_args(self.argv) if opts.env: opts.env = dict(opts.env) diff --git a/tests/test_kernelspec.py b/tests/test_kernelspec.py index e92109648..c3b62b21a 100644 --- a/tests/test_kernelspec.py +++ b/tests/test_kernelspec.py @@ -3,6 +3,7 @@ import json import os +import platform import shutil import sys import tempfile @@ -22,6 +23,7 @@ ) pjoin = os.path.join +is_cpython = platform.python_implementation() == "CPython" def test_make_ipkernel_cmd(): @@ -144,3 +146,49 @@ def test_install_env(tmp_path, env): assert spec["env"][k] == v else: assert "env" not in spec + + +@pytest.mark.skipif(sys.version_info < (3, 11) or not is_cpython, reason="requires cPython 3.11") +def test_install_frozen_modules_on(): + system_jupyter_dir = tempfile.mkdtemp() + + with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]): + install(frozen_modules=True) + + spec_file = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json") + with open(spec_file) as f: + spec = json.load(f) + assert spec["env"]["PYDEVD_DISABLE_FILE_VALIDATION"] == "1" + assert "-Xfrozen_modules=off" not in spec["argv"] + + +@pytest.mark.skipif(sys.version_info < (3, 11) or not is_cpython, reason="requires cPython 3.11") +def test_install_frozen_modules_off(): + system_jupyter_dir = tempfile.mkdtemp() + + with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]): + install(frozen_modules=False) + + spec_file = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json") + with open(spec_file) as f: + spec = json.load(f) + assert "env" not in spec + assert spec["argv"][1] == "-Xfrozen_modules=off" + + +@pytest.mark.skipif( + sys.version_info >= (3, 11) or is_cpython, + reason="checks versions older than 3.11 and other Python implementations", +) +def test_install_frozen_modules_no_op(): + # ensure we do not add add Xfrozen_modules on older Python versions + # (although cPython does not error out on unknown X options as of 3.8) + system_jupyter_dir = tempfile.mkdtemp() + + with mock.patch("jupyter_client.kernelspec.SYSTEM_JUPYTER_PATH", [system_jupyter_dir]): + install(frozen_modules=False) + + spec_file = os.path.join(system_jupyter_dir, "kernels", KERNEL_NAME, "kernel.json") + with open(spec_file) as f: + spec = json.load(f) + assert "-Xfrozen_modules=off" not in spec["argv"] From 2071a88e7653c8bb2cea6d3aa95b691149319408 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Sat, 24 Feb 2024 19:21:29 -0600 Subject: [PATCH 1063/1195] Fix typings and update project urls (#1214) --- ipykernel/zmqshell.py | 2 +- pyproject.toml | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 4f7e2f5a9..4fa850735 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -612,7 +612,7 @@ def init_magics(self): """Initialize magics.""" super().init_magics() self.register_magics(KernelMagics) - self.magics_manager.register_alias("ed", "edit") # type:ignore[union-attr] + self.magics_manager.register_alias("ed", "edit") def init_virtualenv(self): """Initialize virtual environment.""" diff --git a/pyproject.toml b/pyproject.toml index 532b8ffbb..c2ed3fc48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,6 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", ] -urls = {Homepage = "https://ipython.org"} requires-python = ">=3.8" dependencies = [ "debugpy>=1.6.5", @@ -37,6 +36,13 @@ dependencies = [ "packaging", ] +[project.urls] +Homepage = "https://ipython.org" +Documentation = "https://ipykernel.readthedocs.io" +Funding = "https://numfocus.org/donate" +Source = "https://github.com/ipython/ipykernel" +Tracker = "https://github.com/ipython/ipykernel/issues" + [project.optional-dependencies] docs = [ "sphinx", From de2221ce155668c343084fde37b77fb6b1671dc9 Mon Sep 17 00:00:00 2001 From: Jakub Dranczewski Date: Sun, 25 Feb 2024 18:57:10 +0000 Subject: [PATCH 1064/1195] Eventloop scheduling improvements for stop_on_error_timeout and schedule_next (#1212) Co-authored-by: Steven Silvester --- ipykernel/eventloops.py | 69 +++++++++++++++++++++++++++++------------ ipykernel/kernelbase.py | 26 +++++++++++----- 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index c3ddd301e..853738d9e 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -82,6 +82,11 @@ def _notify_stream_qt(kernel): def enum_helper(name): return operator.attrgetter(name.rpartition(".")[0])(sys.modules[QtCore.__package__]) + def exit_loop(): + """fall back to main loop""" + kernel._qt_notifier.setEnabled(False) + kernel.app.qt_event_loop.quit() + def process_stream_events(): """fall back to main loop when there's a socket event""" # call flush to ensure that the stream doesn't lose events @@ -89,8 +94,7 @@ def process_stream_events(): # flush returns the number of events consumed. # if there were any, wake it up if kernel.shell_stream.flush(limit=1): - kernel._qt_notifier.setEnabled(False) - kernel.app.qt_event_loop.quit() + exit_loop() if not hasattr(kernel, "_qt_notifier"): fd = kernel.shell_stream.getsockopt(zmq.FD) @@ -101,6 +105,23 @@ def process_stream_events(): else: kernel._qt_notifier.setEnabled(True) + # allow for scheduling exits from the loop in case a timeout needs to + # be set from the kernel level + def _schedule_exit(delay): + """schedule fall back to main loop in [delay] seconds""" + # The signatures of QtCore.QTimer.singleShot are inconsistent between PySide and PyQt + # if setting the TimerType, so we create a timer explicitly and store it + # to avoid a memory leak. + # PreciseTimer is needed so we exit after _at least_ the specified delay, not within 5% of it + if not hasattr(kernel, "_qt_timer"): + kernel._qt_timer = QtCore.QTimer(kernel.app) + kernel._qt_timer.setSingleShot(True) + kernel._qt_timer.setTimerType(enum_helper("QtCore.Qt.TimerType").PreciseTimer) + kernel._qt_timer.timeout.connect(exit_loop) + kernel._qt_timer.start(int(1000 * delay)) + + loop_qt._schedule_exit = _schedule_exit + # there may already be unprocessed events waiting. # these events will not wake zmq's edge-triggered FD # since edge-triggered notification only occurs on new i/o activity. @@ -108,11 +129,7 @@ def process_stream_events(): # so we start in a clean state ensuring that any new i/o events will notify. # schedule first call on the eventloop as soon as it's running, # so we don't block here processing events - if not hasattr(kernel, "_qt_timer"): - kernel._qt_timer = QtCore.QTimer(kernel.app) - kernel._qt_timer.setSingleShot(True) - kernel._qt_timer.timeout.connect(process_stream_events) - kernel._qt_timer.start(0) + QtCore.QTimer.singleShot(0, process_stream_events) @register_integration("qt", "qt5", "qt6") @@ -229,23 +246,33 @@ def __init__(self, app): self.app = app self.app.withdraw() - def process_stream_events(stream, *a, **kw): + def exit_loop(): + """fall back to main loop""" + app.tk.deletefilehandler(kernel.shell_stream.getsockopt(zmq.FD)) + app.quit() + app.destroy() + del kernel.app_wrapper + + def process_stream_events(*a, **kw): """fall back to main loop when there's a socket event""" - if stream.flush(limit=1): - app.tk.deletefilehandler(stream.getsockopt(zmq.FD)) - app.quit() - app.destroy() - del kernel.app_wrapper + if kernel.shell_stream.flush(limit=1): + exit_loop() + + # allow for scheduling exits from the loop in case a timeout needs to + # be set from the kernel level + def _schedule_exit(delay): + """schedule fall back to main loop in [delay] seconds""" + app.after(int(1000 * delay), exit_loop) + + loop_tk._schedule_exit = _schedule_exit # For Tkinter, we create a Tk object and call its withdraw method. kernel.app_wrapper = BasicAppWrapper(app) - - notifier = partial(process_stream_events, kernel.shell_stream) - # seems to be needed for tk - notifier.__name__ = "notifier" # type:ignore[attr-defined] - app.tk.createfilehandler(kernel.shell_stream.getsockopt(zmq.FD), READABLE, notifier) + app.tk.createfilehandler( + kernel.shell_stream.getsockopt(zmq.FD), READABLE, process_stream_events + ) # schedule initial call after start - app.after(0, notifier) + app.after(0, process_stream_events) app.mainloop() @@ -560,6 +587,10 @@ def enable_gui(gui, kernel=None): # User wants to turn off integration; clear any evidence if Qt was the last one. if hasattr(kernel, "app"): delattr(kernel, "app") + if hasattr(kernel, "_qt_notifier"): + delattr(kernel, "_qt_notifier") + if hasattr(kernel, "_qt_timer"): + delattr(kernel, "_qt_timer") else: if gui.startswith("qt"): # Prepare the kernel here so any exceptions are displayed in the client. diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 4bf2dee35..a24e32380 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -472,7 +472,7 @@ def enter_eventloop(self): self.log.info("Exiting as there is no eventloop") return - def advance_eventloop(): + async def advance_eventloop(): # check if eventloop changed: if self.eventloop is not eventloop: self.log.info("exiting eventloop %s", eventloop) @@ -494,10 +494,13 @@ def advance_eventloop(): def schedule_next(): """Schedule the next advance of the eventloop""" - # flush the eventloop every so often, - # giving us a chance to handle messages in the meantime + # call_later allows the io_loop to process other events if needed. + # Going through schedule_dispatch ensures all other dispatches on msg_queue + # are processed before we enter the eventloop, even if the previous dispatch was + # already consumed from the queue by process_one and the queue is + # technically empty. self.log.debug("Scheduling eventloop advance") - self.io_loop.call_later(0.001, advance_eventloop) + self.io_loop.call_later(0.001, partial(self.schedule_dispatch, advance_eventloop)) # begin polling the eventloop schedule_next() @@ -1202,9 +1205,18 @@ async def stop_aborting(): # before we reset the flag schedule_stop_aborting = partial(self.schedule_dispatch, stop_aborting) - # if we have a delay, give messages this long to arrive on the queue - # before we stop aborting requests - asyncio.get_event_loop().call_later(self.stop_on_error_timeout, schedule_stop_aborting) + if self.stop_on_error_timeout: + # if we have a delay, give messages this long to arrive on the queue + # before we stop aborting requests + self.io_loop.call_later(self.stop_on_error_timeout, schedule_stop_aborting) + # If we have an eventloop, it may interfere with the call_later above. + # If the loop has a _schedule_exit method, we call that so the loop exits + # after stop_on_error_timeout, returning to the main io_loop and letting + # the call_later fire. + if self.eventloop is not None and hasattr(self.eventloop, "_schedule_exit"): + self.eventloop._schedule_exit(self.stop_on_error_timeout + 0.01) + else: + schedule_stop_aborting() def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" From 56a6372805e19c854a16e73c488f8725215a76d3 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Mon, 26 Feb 2024 20:26:01 +0000 Subject: [PATCH 1065/1195] Publish 6.29.3 SHA256 hashes: ipykernel-6.29.3-py3-none-any.whl: 5aa086a4175b0229d4eca211e181fb473ea78ffd9869af36ba7694c947302a21 ipykernel-6.29.3.tar.gz: e14c250d1f9ea3989490225cc1a542781b095a18a19447fcf2b5eaf7d0ac5bd2 --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce6c1611b..62b5d716e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ +## 6.29.3 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.2...de2221ce155668c343084fde37b77fb6b1671dc9)) + +### Enhancements made + +- Eventloop scheduling improvements for stop_on_error_timeout and schedule_next [#1212](https://github.com/ipython/ipykernel/pull/1212) ([@jdranczewski](https://github.com/jdranczewski)) + +### Bugs fixed + +- Disable frozen modules by default, add a toggle [#1213](https://github.com/ipython/ipykernel/pull/1213) ([@krassowski](https://github.com/krassowski)) + +### Maintenance and upkeep improvements + +- Fix typings and update project urls [#1214](https://github.com/ipython/ipykernel/pull/1214) ([@blink1073](https://github.com/blink1073)) +- Unpin pytest-asyncio and update ruff config [#1209](https://github.com/ipython/ipykernel/pull/1209) ([@blink1073](https://github.com/blink1073)) + +### Documentation improvements + +- Correct spelling mistake [#1208](https://github.com/ipython/ipykernel/pull/1208) ([@joouha](https://github.com/joouha)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-02-07&to=2024-02-26&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2024-02-07..2024-02-26&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2024-02-07..2024-02-26&type=Issues) | [@jdranczewski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajdranczewski+updated%3A2024-02-07..2024-02-26&type=Issues) | [@joouha](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajoouha+updated%3A2024-02-07..2024-02-26&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-02-07..2024-02-26&type=Issues) + + + ## 6.29.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.1...d45fe71990d26c0bd5b7b3b2a4ccd3d1f6609899)) @@ -20,8 +49,6 @@ [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-02-06..2024-02-07&type=Issues) | [@stdll00](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Astdll00+updated%3A2024-02-06..2024-02-07&type=Issues) - - ## 6.29.1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.0...09c9b2ad9c15202c5d1896ba24ec978b726c073b)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 7ee214639..77ccbb0a3 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.29.2" +__version__ = "6.29.3" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 384bdb13256a5eb0bf18a3d78969f3f0dceeaa2c Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 29 Feb 2024 09:25:50 -0600 Subject: [PATCH 1066/1195] Fix side effect import for pickleutil (#1216) --- ipykernel/pickleutil.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index c6a2e4484..6f1565943 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -10,6 +10,10 @@ from types import FunctionType # This registers a hook when it's imported +try: + from ipyparallel.serialize import codeutil # noqa: F401 +except ImportError: + pass from traitlets.log import get_logger from traitlets.utils.importstring import import_item From 1cea5332ffc37f32e8232fd2b8b8ddd91b2bbdcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Wed, 27 Mar 2024 22:21:43 +0000 Subject: [PATCH 1067/1195] Backport PR #1223 on branch 6.x (Do not import debugger/debugpy unless needed) (#1224) --- ipykernel/ipkernel.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 9bea4d56b..39c6c7673 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -22,7 +22,6 @@ from .comm.comm import BaseComm from .comm.manager import CommManager from .compiler import XCachingCompiler -from .debugger import Debugger, _is_debugpy_available from .eventloops import _use_appnope from .iostream import OutStream from .kernelbase import Kernel as KernelBase @@ -81,7 +80,7 @@ class IPythonKernel(KernelBase): help="Set this flag to False to deactivate the use of experimental IPython completion APIs.", ).tag(config=True) - debugpy_stream = Instance(ZMQStream, allow_none=True) if _is_debugpy_available else None + debugpy_stream = Instance(ZMQStream, allow_none=True) user_module = Any() @@ -109,6 +108,8 @@ def __init__(self, **kwargs): """Initialize the kernel.""" super().__init__(**kwargs) + from .debugger import Debugger, _is_debugpy_available + # Initialize the Debugger if _is_debugpy_available: self.debugger = Debugger( @@ -209,6 +210,8 @@ def __init__(self, **kwargs): } def dispatch_debugpy(self, msg): + from .debugger import _is_debugpy_available + if _is_debugpy_available: # The first frame is the socket id, we can drop it frame = msg[1].bytes.decode("utf-8") @@ -524,6 +527,8 @@ def do_complete(self, code, cursor_pos): async def do_debug_request(self, msg): """Handle a debug request.""" + from .debugger import _is_debugpy_available + if _is_debugpy_available: return await self.debugger.process_request(msg) return None From 4dc77d783563b04972711e3b435595d3ddb5c689 Mon Sep 17 00:00:00 2001 From: blink1073 Date: Wed, 27 Mar 2024 22:25:28 +0000 Subject: [PATCH 1068/1195] Publish 6.29.4 SHA256 hashes: ipykernel-6.29.4-py3-none-any.whl: 1181e653d95c6808039c509ef8e67c4126b3b3af7781496c7cbfb5ed938a27da ipykernel-6.29.4.tar.gz: 3d44070060f9475ac2092b760123fadf105d2e2493c24848b6691a7c4f42af5c --- CHANGELOG.md | 22 ++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62b5d716e..94854539f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ +## 6.29.4 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.3...1cea5332ffc37f32e8232fd2b8b8ddd91b2bbdcf)) + +### Bugs fixed + +- Fix side effect import for pickleutil [#1216](https://github.com/ipython/ipykernel/pull/1216) ([@blink1073](https://github.com/blink1073)) + +### Maintenance and upkeep improvements + +- Do not import debugger/debugpy unless needed [#1223](https://github.com/ipython/ipykernel/pull/1223) ([@krassowski](https://github.com/krassowski)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-02-26&to=2024-03-27&type=c)) + +[@agronholm](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aagronholm+updated%3A2024-02-26..2024-03-27&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2024-02-26..2024-03-27&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-02-26..2024-03-27&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-02-26..2024-03-27&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-02-26..2024-03-27&type=Issues) + + + ## 6.29.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.2...de2221ce155668c343084fde37b77fb6b1671dc9)) @@ -29,8 +49,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2024-02-07..2024-02-26&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2024-02-07..2024-02-26&type=Issues) | [@jdranczewski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajdranczewski+updated%3A2024-02-07..2024-02-26&type=Issues) | [@joouha](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajoouha+updated%3A2024-02-07..2024-02-26&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-02-07..2024-02-26&type=Issues) - - ## 6.29.2 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.1...d45fe71990d26c0bd5b7b3b2a4ccd3d1f6609899)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 77ccbb0a3..bf984b4cf 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.29.3" +__version__ = "6.29.4" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 22d4e58542c4fde30bc846fecd3868322457aaa2 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 14 Jun 2024 05:11:53 +0100 Subject: [PATCH 1069/1195] Fix use of "%matplotlib osx" (#1237) --- ipykernel/_eventloop_macos.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/ipykernel/_eventloop_macos.py b/ipykernel/_eventloop_macos.py index 3a6692fce..c55546fe2 100644 --- a/ipykernel/_eventloop_macos.py +++ b/ipykernel/_eventloop_macos.py @@ -17,7 +17,6 @@ objc.objc_getClass.restype = void_p objc.sel_registerName.restype = void_p objc.objc_msgSend.restype = void_p -objc.objc_msgSend.argtypes = [void_p, void_p] msg = objc.objc_msgSend @@ -80,11 +79,25 @@ def C(classname): def _NSApp(): """Return the global NSApplication instance (NSApp)""" + objc.objc_msgSend.argtypes = [void_p, void_p] return msg(C("NSApplication"), n("sharedApplication")) def _wake(NSApp): """Wake the Application""" + objc.objc_msgSend.argtypes = [ + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + void_p, + ] event = msg( C("NSEvent"), n( @@ -101,6 +114,7 @@ def _wake(NSApp): 0, # data1 0, # data2 ) + objc.objc_msgSend.argtypes = [void_p, void_p, void_p, void_p] msg(NSApp, n("postEvent:atStart:"), void_p(event), True) @@ -113,7 +127,9 @@ def stop(timer=None, loop=None): NSApp = _NSApp() # if NSApp is not running, stop CFRunLoop directly, # otherwise stop and wake NSApp + objc.objc_msgSend.argtypes = [void_p, void_p] if msg(NSApp, n("isRunning")): + objc.objc_msgSend.argtypes = [void_p, void_p, void_p] msg(NSApp, n("stop:"), NSApp) _wake(NSApp) else: @@ -148,6 +164,7 @@ def mainloop(duration=1): _triggered.clear() NSApp = _NSApp() _stop_after(duration) + objc.objc_msgSend.argtypes = [void_p, void_p] msg(NSApp, n("run")) if not _triggered.is_set(): # app closed without firing callback, From 1e62d48298e353a9879fae99bc752f9bb48797ef Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Thu, 27 Jun 2024 10:32:45 -0500 Subject: [PATCH 1070/1195] [6.x] Update Release Scripts (#1251) --- .github/workflows/prep-release.yml | 9 ++++++- .github/workflows/publish-changelog.yml | 34 +++++++++++++++++++++++++ .github/workflows/publish-release.yml | 22 ++++++++-------- 3 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/publish-changelog.yml diff --git a/.github/workflows/prep-release.yml b/.github/workflows/prep-release.yml index 7a2a18de7..396330bb9 100644 --- a/.github/workflows/prep-release.yml +++ b/.github/workflows/prep-release.yml @@ -12,6 +12,10 @@ on: post_version_spec: description: "Post Version Specifier" required: false + silent: + description: "Set a placeholder in the changelog and don't publish the release." + required: false + type: boolean since: description: "Use PRs with activity since this date or git reference" required: false @@ -22,6 +26,8 @@ on: jobs: prep_release: runs-on: ubuntu-latest + permissions: + contents: write steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -29,8 +35,9 @@ jobs: id: prep-release uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} version_spec: ${{ github.event.inputs.version_spec }} + silent: ${{ github.event.inputs.silent }} post_version_spec: ${{ github.event.inputs.post_version_spec }} target: ${{ github.event.inputs.target }} branch: ${{ github.event.inputs.branch }} diff --git a/.github/workflows/publish-changelog.yml b/.github/workflows/publish-changelog.yml new file mode 100644 index 000000000..60af4c5f1 --- /dev/null +++ b/.github/workflows/publish-changelog.yml @@ -0,0 +1,34 @@ +name: "Publish Changelog" +on: + release: + types: [published] + + workflow_dispatch: + inputs: + branch: + description: "The branch to target" + required: false + +jobs: + publish_changelog: + runs-on: ubuntu-latest + environment: release + steps: + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Publish changelog + id: publish-changelog + uses: jupyter-server/jupyter_releaser/.github/actions/publish-changelog@v2 + with: + token: ${{ steps.app-token.outputs.token }} + branch: ${{ github.event.inputs.branch }} + + - name: "** Next Step **" + run: | + echo "Merge the changelog update PR: ${{ steps.publish-changelog.outputs.pr_url }}" diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index dbaaeaad2..5295e776b 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -15,30 +15,32 @@ on: jobs: publish_release: runs-on: ubuntu-latest + environment: release + permissions: + id-token: write steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + - name: Populate Release id: populate-release uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} - target: ${{ github.event.inputs.target }} + token: ${{ steps.app-token.outputs.token }} branch: ${{ github.event.inputs.branch }} release_url: ${{ github.event.inputs.release_url }} steps_to_skip: ${{ github.event.inputs.steps_to_skip }} - name: Finalize Release id: finalize-release - env: - PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} - PYPI_TOKEN_MAP: ${{ secrets.PYPI_TOKEN_MAP }} - TWINE_USERNAME: __token__ - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - uses: jupyter-server/jupyter-releaser/.github/actions/finalize-release@v2 + uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2 with: - token: ${{ secrets.ADMIN_GITHUB_TOKEN }} - target: ${{ github.event.inputs.target }} + token: ${{ steps.app-token.outputs.token }} release_url: ${{ steps.populate-release.outputs.release_url }} - name: "** Next Step **" From b1283b14419969e36329c1ae957509690126b057 Mon Sep 17 00:00:00 2001 From: ianthomas23 Date: Mon, 1 Jul 2024 14:07:09 +0000 Subject: [PATCH 1071/1195] Publish 6.29.5 SHA256 hashes: ipykernel-6.29.5-py3-none-any.whl: afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5 ipykernel-6.29.5.tar.gz: f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215 --- CHANGELOG.md | 22 ++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94854539f..84767e41b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ +## 6.29.5 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.4...1e62d48298e353a9879fae99bc752f9bb48797ef)) + +### Bugs fixed + +- Fix use of "%matplotlib osx" [#1237](https://github.com/ipython/ipykernel/pull/1237) ([@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- \[6.x\] Update Release Scripts [#1251](https://github.com/ipython/ipykernel/pull/1251) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-03-27&to=2024-06-29&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2024-03-27..2024-06-29&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-03-27..2024-06-29&type=Issues) + + + ## 6.29.4 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.3...1cea5332ffc37f32e8232fd2b8b8ddd91b2bbdcf)) @@ -20,8 +40,6 @@ [@agronholm](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aagronholm+updated%3A2024-02-26..2024-03-27&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2024-02-26..2024-03-27&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-02-26..2024-03-27&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-02-26..2024-03-27&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-02-26..2024-03-27&type=Issues) - - ## 6.29.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.2...de2221ce155668c343084fde37b77fb6b1671dc9)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index bf984b4cf..5034aec02 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.29.4" +__version__ = "6.29.5" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 5d2fe53d3fa43bf04805030e7bdca87e0d6cd8ad Mon Sep 17 00:00:00 2001 From: "Lumberbot (aka Jack)" <39504233+meeseeksmachine@users.noreply.github.com> Date: Sun, 7 Jul 2024 02:57:04 +0200 Subject: [PATCH 1072/1195] Backport PR #1248 on branch 6.x (Avoid a DeprecationWarning on Python 3.13+) (#1254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Miro Hrončok --- ipykernel/jsonutil.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index 6a463cf1b..e45f06e53 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -26,7 +26,7 @@ # holy crap, strptime is not threadsafe. # Calling it once at import seems to help. -datetime.strptime("1", "%d") +datetime.strptime("2000-01-01", "%Y-%m-%d") # ----------------------------------------------------------------------------- # Classes and functions From 3c96ba25cb3f9a83e49ad7d769905e9fa770e369 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Tue, 22 Apr 2025 14:59:49 +0100 Subject: [PATCH 1073/1195] Backports and extra changes to fix CI on 6.x branch (#1390) Co-authored-by: Steven Silvester Co-authored-by: M Bussonnier Co-authored-by: David Brochart Co-authored-by: Steve Kowalik Co-authored-by: Min RK --- .github/workflows/ci.yml | 38 +++++++-------------- .github/workflows/downstream.yml | 2 +- docs/api/ipykernel.comm.rst | 6 ++-- docs/api/ipykernel.inprocess.rst | 16 ++++----- docs/api/ipykernel.rst | 38 ++++++++++----------- docs/conf.py | 18 +++++----- ipykernel/_version.py | 3 +- ipykernel/debugger.py | 2 +- ipykernel/embed.py | 3 +- ipykernel/heartbeat.py | 2 +- ipykernel/inprocess/channels.py | 4 +-- ipykernel/inprocess/ipkernel.py | 7 ++-- ipykernel/iostream.py | 8 ++--- ipykernel/ipkernel.py | 18 ++++++---- ipykernel/kernelbase.py | 58 +++++++++----------------------- ipykernel/pickleutil.py | 4 +-- pyproject.toml | 30 ++++++++++------- tests/__init__.py | 9 ++--- tests/test_async.py | 9 +++-- tests/test_eventloop.py | 7 ++-- tests/test_ipkernel_direct.py | 12 ------- tests/test_kernel.py | 47 ++++++++++++++++++++++++++ tests/test_message_spec.py | 3 +- tests/test_start_kernel.py | 10 +++++- tests/utils.py | 12 ------- 25 files changed, 183 insertions(+), 183 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6d7e864a..06cc35349 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,22 +22,28 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.8", "3.12"] + python-version: ["3.9", "3.13"] include: - - os: windows-latest - python-version: "3.9" - os: ubuntu-latest python-version: "pypy-3.9" - os: macos-latest python-version: "3.10" - os: ubuntu-latest python-version: "3.11" + - os: ubuntu-latest + python-version: "3.12" steps: - name: Checkout uses: actions/checkout@v4 - - name: Base Setup - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install hatch + run: | + python --version + python -m pip install hatch - name: Run the tests timeout-minutes: 15 @@ -98,7 +104,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} test_docs: - runs-on: windows-latest + runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -150,6 +156,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: dependency_type: minimum + python_version: "3.9" - name: List installed packages run: | @@ -198,22 +205,3 @@ jobs: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 - - tests_check: # This job does nothing and is only used for the branch protection - if: always() - needs: - - coverage - - test_docs - - test_without_debugpy - - test_miniumum_versions - - test_lint - - test_prereleases - - check_release - - link_check - - test_sdist - runs-on: ubuntu-latest - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 68afcbf34..b47c0f06d 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -131,7 +131,7 @@ jobs: - name: Install System Packages run: | sudo apt-get update - sudo apt-get install -y --no-install-recommends libegl1-mesa + sudo apt-get install -y --no-install-recommends libgl1 libglx-mesa0 - name: Install spyder-kernels dependencies shell: bash -l {0} run: | diff --git a/docs/api/ipykernel.comm.rst b/docs/api/ipykernel.comm.rst index 1cf9ee4e7..a2d529ed0 100644 --- a/docs/api/ipykernel.comm.rst +++ b/docs/api/ipykernel.comm.rst @@ -7,19 +7,19 @@ Submodules .. automodule:: ipykernel.comm.comm :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.comm.manager :members: - :undoc-members: :show-inheritance: + :undoc-members: Module contents --------------- .. automodule:: ipykernel.comm :members: - :undoc-members: :show-inheritance: + :undoc-members: diff --git a/docs/api/ipykernel.inprocess.rst b/docs/api/ipykernel.inprocess.rst index c2d6536bc..24d62e7ad 100644 --- a/docs/api/ipykernel.inprocess.rst +++ b/docs/api/ipykernel.inprocess.rst @@ -7,49 +7,49 @@ Submodules .. automodule:: ipykernel.inprocess.blocking :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.channels :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.client :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.constants :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.ipkernel :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.manager :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.socket :members: - :undoc-members: :show-inheritance: + :undoc-members: Module contents --------------- .. automodule:: ipykernel.inprocess :members: - :undoc-members: :show-inheritance: + :undoc-members: diff --git a/docs/api/ipykernel.rst b/docs/api/ipykernel.rst index 2e1cf20d8..1a3286c8a 100644 --- a/docs/api/ipykernel.rst +++ b/docs/api/ipykernel.rst @@ -16,115 +16,115 @@ Submodules .. automodule:: ipykernel.compiler :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.connect :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.control :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.debugger :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.displayhook :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.embed :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.eventloops :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.heartbeat :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.iostream :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.ipkernel :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.jsonutil :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.kernelapp :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.kernelbase :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.kernelspec :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.log :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.parentpoller :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.trio_runner :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.zmqshell :members: - :undoc-members: :show-inheritance: + :undoc-members: Module contents --------------- .. automodule:: ipykernel :members: - :undoc-members: :show-inheritance: + :undoc-members: diff --git a/docs/conf.py b/docs/conf.py index 4bb599327..5a5f8ed5c 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,9 @@ import os import shutil from pathlib import Path -from typing import Any, Dict, List +from typing import Any + +from intersphinx_registry import get_intersphinx_mapping # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -70,7 +72,7 @@ # built documents. # -version_ns: Dict[str, Any] = {} +version_ns: dict[str, Any] = {} here = Path(__file__).parent.resolve() version_py = Path(here) / os.pardir / "ipykernel" / "_version.py" with open(version_py) as f: @@ -159,7 +161,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path: List[str] = [] +html_static_path: list[str] = [] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied @@ -226,7 +228,7 @@ # -- Options for LaTeX output --------------------------------------------- -latex_elements: Dict[str, object] = {} +latex_elements: dict[str, object] = {} # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, @@ -303,11 +305,9 @@ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = { - "python": ("https://docs.python.org/3/", None), - "ipython": ("https://ipython.readthedocs.io/en/latest", None), - "jupyter": ("https://jupyter.readthedocs.io/en/latest", None), -} + + +intersphinx_mapping = get_intersphinx_mapping(packages={"ipython", "python", "jupyter"}) def setup(app): diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 5034aec02..792dff91b 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -2,7 +2,6 @@ store the current version info of the server. """ import re -from typing import List # Version string must appear intact for hatch versioning __version__ = "6.29.5" @@ -11,7 +10,7 @@ pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" match = re.match(pattern, __version__) assert match is not None -parts: List[object] = [int(match[part]) for part in ["major", "minor", "patch"]] +parts: list[object] = [int(match[part]) for part in ["major", "minor", "patch"]] if match["rest"]: parts.append(match["rest"]) version_info = tuple(parts) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index fd192e157..4d4d43dc3 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -128,7 +128,7 @@ def _reset_tcp_pos(self): def _put_message(self, raw_msg): self.log.debug("QUEUE - _put_message:") - msg = t.cast(t.Dict[str, t.Any], jsonapi.loads(raw_msg)) + msg = t.cast(dict[str, t.Any], jsonapi.loads(raw_msg)) if msg["type"] == "event": self.log.debug("QUEUE - received event:") self.log.debug(msg) diff --git a/ipykernel/embed.py b/ipykernel/embed.py index 3e4abd390..d2cbe60b4 100644 --- a/ipykernel/embed.py +++ b/ipykernel/embed.py @@ -49,9 +49,10 @@ def embed_kernel(module=None, local_ns=None, **kwargs): if module is None: module = caller_module if local_ns is None: - local_ns = caller_locals + local_ns = dict(**caller_locals) app.kernel.user_module = module + assert isinstance(local_ns, dict) app.kernel.user_ns = local_ns app.shell.set_completer_frame() # type:ignore[union-attr] app.start() diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index d2890f672..38fea17f9 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -103,7 +103,7 @@ def run(self): while True: try: - zmq.device(zmq.QUEUE, self.socket, self.socket) + zmq.device(zmq.QUEUE, self.socket, self.socket) # type:ignore[attr-defined] except zmq.ZMQError as e: if e.errno == errno.EINTR: # signal interrupt, resume heartbeat diff --git a/ipykernel/inprocess/channels.py b/ipykernel/inprocess/channels.py index 378416dcc..4c01c5bcb 100644 --- a/ipykernel/inprocess/channels.py +++ b/ipykernel/inprocess/channels.py @@ -3,8 +3,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -from typing import List - from jupyter_client.channelsabc import HBChannelABC # ----------------------------------------------------------------------------- @@ -15,7 +13,7 @@ class InProcessChannel: """Base class for in-process channels.""" - proxy_methods: List[object] = [] + proxy_methods: list[object] = [] def __init__(self, client=None): """Initialize the channel.""" diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 873b96d20..789b0bf8a 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -88,9 +88,6 @@ def start(self): def _abort_queues(self): """The in-process kernel doesn't abort requests.""" - async def _flush_control_queue(self): - """No need to flush control queues for in-process""" - def _input_request(self, prompt, ident, parent, password=False): # Flush output before making the request. self.raw_input_str = None @@ -193,11 +190,11 @@ def enable_matplotlib(self, gui=None): gui = self.kernel.gui return super().enable_matplotlib(gui) - def enable_pylab(self, gui=None, import_all=True, welcome_message=False): + def enable_pylab(self, gui=None, import_all=True): """Activate pylab support at runtime.""" if not gui: gui = self.kernel.gui - return super().enable_pylab(gui, import_all, welcome_message) + return super().enable_pylab(gui, import_all) InteractiveShellABC.register(InProcessInteractiveShell) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 257b5c800..9dc55242b 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -16,7 +16,7 @@ from collections import defaultdict, deque from io import StringIO, TextIOBase from threading import local -from typing import Any, Callable, Deque, Dict, Optional +from typing import Any, Callable, Optional import zmq from jupyter_client.session import extract_header @@ -66,8 +66,8 @@ def __init__(self, socket, pipe=False): if pipe: self._setup_pipe_in() self._local = threading.local() - self._events: Deque[Callable[..., Any]] = deque() - self._event_pipes: Dict[threading.Thread, Any] = {} + self._events: deque[Callable[..., Any]] = deque() + self._event_pipes: dict[threading.Thread, Any] = {} self._event_pipe_gc_lock: threading.Lock = threading.Lock() self._event_pipe_gc_seconds: float = 10 self._event_pipe_gc_task: Optional[asyncio.Task[Any]] = None @@ -447,7 +447,7 @@ def __init__( self.pub_thread = pub_thread self.name = name self.topic = b"stream." + name.encode() - self._parent_header: contextvars.ContextVar[Dict[str, Any]] = contextvars.ContextVar( + self._parent_header: contextvars.ContextVar[dict[str, Any]] = contextvars.ContextVar( "parent_header" ) self._parent_header.set({}) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 39c6c7673..ada46eaa2 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -1,5 +1,7 @@ """The IPython kernel implementation""" +from __future__ import annotations + import asyncio import builtins import gc @@ -16,7 +18,7 @@ from IPython.core import release from IPython.utils.tokenutil import line_at_cursor, token_at_cursor from jupyter_client.session import extract_header -from traitlets import Any, Bool, HasTraits, Instance, List, Type, observe, observe_compat +from traitlets import Any, Bool, HasTraits, Instance, List, Type, default, observe, observe_compat from zmq.eventloop.zmqstream import ZMQStream from .comm.comm import BaseComm @@ -52,7 +54,7 @@ def _create_comm(*args, **kwargs): # there can only be one comm manager in a ipykernel process _comm_lock = threading.Lock() -_comm_manager: t.Optional[CommManager] = None +_comm_manager: CommManager | None = None def _get_comm_manager(*args, **kwargs): @@ -90,7 +92,11 @@ def _user_module_changed(self, change): if self.shell is not None: self.shell.user_module = change["new"] - user_ns = Instance(dict, args=None, allow_none=True) + user_ns = Instance("collections.abc.Mapping", allow_none=True) + + @default("user_ns") + def _default_user_ns(self): + return dict() @observe("user_ns") @observe_compat @@ -378,7 +384,7 @@ async def do_execute( self._forward_input(allow_stdin) - reply_content: t.Dict[str, t.Any] = {} + reply_content: dict[str, t.Any] = {} if hasattr(shell, "run_cell_async") and hasattr(shell, "should_run_async"): run_cell = shell.run_cell_async should_run_async = shell.should_run_async @@ -577,7 +583,7 @@ def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): """Handle code inspection.""" name = token_at_cursor(code, cursor_pos) - reply_content: t.Dict[str, t.Any] = {"status": "ok"} + reply_content: dict[str, t.Any] = {"status": "ok"} reply_content["data"] = {} reply_content["metadata"] = {} assert self.shell is not None @@ -773,7 +779,7 @@ def init_closure(self: threading.Thread, *args, **kwargs): threading.Thread.run = run_closure # type:ignore[method-assign] def _clean_thread_parent_frames( - self, phase: t.Literal["start", "stop"], info: t.Dict[str, t.Any] + self, phase: t.Literal["start", "stop"], info: dict[str, t.Any] ): """Clean parent frames of threads which are no longer running. This is meant to be invoked by garbage collector callback hook. diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index a24e32380..01539fd22 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -5,7 +5,6 @@ from __future__ import annotations import asyncio -import concurrent.futures import inspect import itertools import logging @@ -289,49 +288,16 @@ def __init__(self, **kwargs): for msg_type in self.control_msg_types: self.control_handlers[msg_type] = getattr(self, msg_type) - self.control_queue: Queue[t.Any] = Queue() - # Storing the accepted parameters for do_execute, used in execute_request self._do_exec_accepted_params = _accepts_parameters( self.do_execute, ["cell_meta", "cell_id"] ) - def dispatch_control(self, msg): - self.control_queue.put_nowait(msg) - - async def poll_control_queue(self): - while True: - msg = await self.control_queue.get() - # handle tracers from _flush_control_queue - if isinstance(msg, (concurrent.futures.Future, asyncio.Future)): - msg.set_result(None) - continue + async def dispatch_control(self, msg): + # Ensure only one control message is processed at a time + async with asyncio.Lock(): await self.process_control(msg) - async def _flush_control_queue(self): - """Flush the control queue, wait for processing of any pending messages""" - tracer_future: concurrent.futures.Future[object] | asyncio.Future[object] - if self.control_thread: - control_loop = self.control_thread.io_loop - # concurrent.futures.Futures are threadsafe - # and can be used to await across threads - tracer_future = concurrent.futures.Future() - awaitable_future = asyncio.wrap_future(tracer_future) - else: - control_loop = self.io_loop - tracer_future = awaitable_future = asyncio.Future() - - def _flush(): - # control_stream.flush puts messages on the queue - if self.control_stream: - self.control_stream.flush() - # put Future on the queue after all of those, - # so we can wait for all queued messages to be processed - self.control_queue.put(tracer_future) - - control_loop.add_callback(_flush) - return awaitable_future - async def process_control(self, msg): """dispatch control requests""" if not self.session: @@ -387,8 +353,6 @@ async def dispatch_shell(self, msg): """dispatch shell requests""" if not self.session: return - # flush control queue before handling shell requests - await self._flush_control_queue() idents, msg = self.session.feed_identities(msg, copy=False) try: @@ -531,6 +495,19 @@ async def process_one(self, wait=True): t, dispatch, args = self.msg_queue.get_nowait() except (asyncio.QueueEmpty, QueueEmpty): return + + if self.control_thread is None and self.control_stream is not None: + # If there isn't a separate control thread then this main thread handles both shell + # and control messages. Before processing a shell message we need to flush all control + # messages and allow them all to be processed. + await asyncio.sleep(0) + self.control_stream.flush() + + socket = self.control_stream.socket + while socket.poll(1): + await asyncio.sleep(0) + self.control_stream.flush() + await dispatch(*args) async def dispatch_queue(self): @@ -578,9 +555,6 @@ def start(self): if self.control_stream: self.control_stream.on_recv(self.dispatch_control, copy=False) - control_loop = self.control_thread.io_loop if self.control_thread else self.io_loop - - asyncio.run_coroutine_threadsafe(self.poll_control_queue(), control_loop.asyncio_loop) if self.shell_stream: self.shell_stream.on_recv( partial( diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 6f1565943..4ffa5262e 100644 --- a/ipykernel/pickleutil.py +++ b/ipykernel/pickleutil.py @@ -209,7 +209,7 @@ def __init__(self, f): """Initialize the can""" self._check_type(f) self.code = f.__code__ - self.defaults: typing.Optional[typing.List[typing.Any]] + self.defaults: typing.Optional[list[typing.Any]] if f.__defaults__: self.defaults = [can(fd) for fd in f.__defaults__] else: @@ -475,7 +475,7 @@ def uncan_sequence(obj, g=None): if buffer is not memoryview: can_map[buffer] = CannedBuffer -uncan_map: typing.Dict[type, typing.Any] = { +uncan_map: dict[type, typing.Any] = { CannedObject: lambda obj, g: obj.get_object(g), dict: uncan_dict, } diff --git a/pyproject.toml b/pyproject.toml index c2ed3fc48..b11d572c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,26 +14,25 @@ classifiers = [ "Intended Audience :: Developers", "Intended Audience :: System Administrators", "Intended Audience :: Science/Research", - "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 3", ] -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ "debugpy>=1.6.5", "ipython>=7.23.1", "comm>=0.1.1", "traitlets>=5.4.0", - "jupyter_client>=6.1.12", + "jupyter_client>=8.0.0", "jupyter_core>=4.12,!=5.0.*", # For tk event loop support only. - "nest_asyncio", - "tornado>=6.1", + "nest_asyncio>=1.4", + "tornado>=6.2", "matplotlib-inline>=0.1", - 'appnope;platform_system=="Darwin"', - "pyzmq>=24", - "psutil", - "packaging", + 'appnope>=0.1.2;platform_system=="Darwin"', + "pyzmq>=25", + "psutil>=5.7", + "packaging>=22", ] [project.urls] @@ -51,10 +50,11 @@ docs = [ "sphinxcontrib_github_alt", "sphinxcontrib-spelling", "sphinx-autodoc-typehints", + "intersphinx_registry", "trio" ] test = [ - "pytest>=7.0", + "pytest>=7.0,<9", "pytest-cov", "flaky", "ipyparallel", @@ -66,7 +66,6 @@ cov = [ "coverage[toml]", "pytest-cov", "matplotlib", - "curio", "trio", ] pyqt5 = ["pyqt5"] @@ -155,7 +154,6 @@ addopts = [ ] testpaths = [ "tests", - "tests/inprocess" ] asyncio_mode = "auto" timeout = 300 @@ -171,6 +169,8 @@ filterwarnings= [ # IPython warnings "ignore: `Completer.complete` is pending deprecation since IPython 6.0 and will be replaced by `Completer.completions`:PendingDeprecationWarning", + "ignore: backends is deprecated since IPython 8.24, backends are managed in matplotlib and can be externally registered.:DeprecationWarning", + "ignore: backend2gui is deprecated since IPython 8.24, backends are managed in matplotlib and can be externally registered.:DeprecationWarning", # Ignore jupyter_client warnings "ignore:unclosed None: async def test_start(ipkernel: IPythonKernel) -> None: shell_future: asyncio.Future = asyncio.Future() - control_future: asyncio.Future = asyncio.Future() async def fake_dispatch_queue(): shell_future.set_result(None) - async def fake_poll_control_queue(): - control_future.set_result(None) - ipkernel.dispatch_queue = fake_dispatch_queue # type:ignore - ipkernel.poll_control_queue = fake_poll_control_queue # type:ignore ipkernel.start() ipkernel.debugpy_stream = None ipkernel.start() await ipkernel.process_one(False) await shell_future - await control_future async def test_start_no_debugpy(ipkernel: IPythonKernel) -> None: shell_future: asyncio.Future = asyncio.Future() - control_future: asyncio.Future = asyncio.Future() async def fake_dispatch_queue(): shell_future.set_result(None) - async def fake_poll_control_queue(): - control_future.set_result(None) - ipkernel.dispatch_queue = fake_dispatch_queue # type:ignore - ipkernel.poll_control_queue = fake_poll_control_queue # type:ignore ipkernel.debugpy_stream = None ipkernel.start() await shell_future - await control_future def test_create_comm(): diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 313388965..88f02ae9a 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -10,6 +10,7 @@ import subprocess import sys import time +from datetime import datetime, timedelta from subprocess import Popen from tempfile import TemporaryDirectory @@ -597,6 +598,52 @@ def test_control_thread_priority(): assert control_dates[-1] <= shell_dates[0] +def test_sequential_control_messages(): + with new_kernel() as kc: + msg_id = kc.execute("import time") + get_reply(kc, msg_id) + + # Send multiple messages on the control channel. + # Using execute messages to vary duration. + sleeps = [0.6, 0.3, 0.1] + + # Prepare messages + msgs = [ + kc.session.msg("execute_request", {"code": f"time.sleep({sleep})"}) for sleep in sleeps + ] + msg_ids = [msg["header"]["msg_id"] for msg in msgs] + + # Submit messages + for msg in msgs: + kc.control_channel.send(msg) + + # Get replies + replies = [get_reply(kc, msg_id, channel="control") for msg_id in msg_ids] + + def ensure_datetime(arg): + # Support arg which is a datetime or str. + if isinstance(arg, str): + if sys.version_info[:2] < (3, 11) and arg.endswith("Z"): + # Python < 3.11 doesn't support "Z" suffix in datetime.fromisoformat, + # so use alternative timezone format. + # https://github.com/python/cpython/issues/80010 + arg = arg[:-1] + "+00:00" + return datetime.fromisoformat(arg) + return arg + + # Check messages are processed in order, one at a time, and of a sensible duration. + previous_end = None + for reply, sleep in zip(replies, sleeps): + start = ensure_datetime(reply["metadata"]["started"]) + end = ensure_datetime(reply["header"]["date"]) + + if previous_end is not None: + assert start >= previous_end + previous_end = end + + assert end >= start + timedelta(seconds=sleep) + + def _child(): print("in child", os.getpid()) diff --git a/tests/test_message_spec.py b/tests/test_message_spec.py index db6ea7d7f..7dd8ad1e2 100644 --- a/tests/test_message_spec.py +++ b/tests/test_message_spec.py @@ -21,7 +21,8 @@ KC: BlockingKernelClient = None # type:ignore -def setup(): +@pytest.fixture(autouse=True) +def _setup_env(): global KC KC = start_global_kernel() diff --git a/tests/test_start_kernel.py b/tests/test_start_kernel.py index f2a632be0..71f4bdc0a 100644 --- a/tests/test_start_kernel.py +++ b/tests/test_start_kernel.py @@ -14,6 +14,14 @@ @flaky(max_runs=3) def test_ipython_start_kernel_userns(): + import IPython + + if IPython.version_info > (9, 0): # noqa:SIM108 + EXPECTED = "IPythonMainModule" + else: + # not this since https://github.com/ipython/ipython/pull/14754 + EXPECTED = "DummyMod" + cmd = dedent( """ from ipykernel.kernelapp import launch_new_instance @@ -40,7 +48,7 @@ def test_ipython_start_kernel_userns(): content = msg["content"] assert content["found"] text = content["data"]["text/plain"] - assert "DummyMod" in text + assert EXPECTED in text @flaky(max_runs=3) diff --git a/tests/utils.py b/tests/utils.py index b1b4119f0..a0880df13 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -28,12 +28,6 @@ def start_new_kernel(**kwargs): Integrates with our output capturing for tests. """ kwargs["stderr"] = STDOUT - try: - import nose - - kwargs["stdout"] = nose.iptest_stdstreams_fileno() - except (ImportError, AttributeError): - pass return manager.start_new_kernel(startup_timeout=STARTUP_TIMEOUT, **kwargs) @@ -150,12 +144,6 @@ def new_kernel(argv=None): kernel_client: connected KernelClient instance """ kwargs = {"stderr": STDOUT} - try: - import nose - - kwargs["stdout"] = nose.iptest_stdstreams_fileno() - except (ImportError, AttributeError): - pass if argv is not None: kwargs["extra_arguments"] = argv return manager.run_kernel(**kwargs) From 7603443ba95195d25e8735ef3d76b627e6af06b2 Mon Sep 17 00:00:00 2001 From: Boyuan Deng <40481626+dby-tmwctw@users.noreply.github.com> Date: Fri, 16 May 2025 01:38:17 -0700 Subject: [PATCH 1074/1195] [Bugfix] Set shell idle when message skipped by "should_handle" in "dispatch_shell" (#1395) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/kernelbase.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 01539fd22..cb440199e 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -331,10 +331,7 @@ async def process_control(self, msg): sys.stdout.flush() sys.stderr.flush() - self._publish_status("idle", "control") - # flush to ensure reply is sent - if self.control_stream: - self.control_stream.flush(zmq.POLLOUT) + self._publish_status_and_flush("idle", "control", self.control_stream) def should_handle(self, stream, msg, idents): """Check whether a shell-channel message should be handled @@ -370,11 +367,7 @@ async def dispatch_shell(self, msg): # Only abort execute requests if self._aborting and msg_type == "execute_request": self._send_abort_reply(self.shell_stream, msg, idents) - self._publish_status("idle", "shell") - # flush to ensure reply is sent before - # handling the next request - if self.shell_stream: - self.shell_stream.flush(zmq.POLLOUT) + self._publish_status_and_flush("idle", "shell", self.shell_stream) return # Print some info about this message and leave a '--->' marker, so it's @@ -384,6 +377,7 @@ async def dispatch_shell(self, msg): self.log.debug(" Content: %s\n --->\n ", msg["content"]) if not self.should_handle(self.shell_stream, msg, idents): + self._publish_status_and_flush("idle", "shell", self.shell_stream) return handler = self.shell_handlers.get(msg_type, None) @@ -412,11 +406,7 @@ async def dispatch_shell(self, msg): sys.stdout.flush() sys.stderr.flush() - self._publish_status("idle", "shell") - # flush to ensure reply is sent before - # handling the next request - if self.shell_stream: - self.shell_stream.flush(zmq.POLLOUT) + self._publish_status_and_flush("idle", "shell", self.shell_stream) def pre_handler_hook(self): """Hook to execute before calling message handler""" @@ -603,6 +593,12 @@ def _publish_status(self, status, channel, parent=None): ident=self._topic("status"), ) + def _publish_status_and_flush(self, status, channel, stream, parent=None): + """send status on IOPub and flush specified stream to ensure reply is sent before handling the next reply""" + self._publish_status(status, channel, parent) + if stream: + stream.flush(zmq.POLLOUT) + def _publish_debug_event(self, event): if not self.session: return From 5d90d9e10425886deb4b0f1676b14b1701522b3c Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Thu, 5 Jun 2025 16:33:40 +0100 Subject: [PATCH 1075/1195] Subshells implemented using tornado event loops on 6.x branch (#1396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michał Krassowski <5832902+krassowski@users.noreply.github.com> Co-authored-by: M Bussonnier --- .github/workflows/ci.yml | 12 +- .github/workflows/downstream.yml | 2 +- .readthedocs.yaml | 2 +- docs/api/ipykernel.comm.rst | 6 +- docs/api/ipykernel.inprocess.rst | 16 +- docs/api/ipykernel.rst | 66 +++++--- ipykernel/control.py | 27 +--- ipykernel/ipkernel.py | 12 +- ipykernel/kernelapp.py | 21 ++- ipykernel/kernelbase.py | 237 +++++++++++++++++++++++++--- ipykernel/shellchannel.py | 47 ++++++ ipykernel/socket_pair.py | 54 +++++++ ipykernel/subshell.py | 34 ++++ ipykernel/subshell_manager.py | 213 +++++++++++++++++++++++++ ipykernel/thread.py | 32 ++++ ipykernel/zmqshell.py | 37 ++++- pyproject.toml | 7 +- tests/test_connect.py | 1 - tests/test_eventloop.py | 16 +- tests/test_message_spec.py | 44 +++++- tests/test_subshells.py | 260 +++++++++++++++++++++++++++++++ tests/utils.py | 36 ++++- 22 files changed, 1078 insertions(+), 104 deletions(-) create mode 100644 ipykernel/shellchannel.py create mode 100644 ipykernel/socket_pair.py create mode 100644 ipykernel/subshell.py create mode 100644 ipykernel/subshell_manager.py create mode 100644 ipykernel/thread.py create mode 100644 tests/test_subshells.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06cc35349..b15038b09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,19 +49,19 @@ jobs: timeout-minutes: 15 if: ${{ !startsWith( matrix.python-version, 'pypy' ) && !startsWith(matrix.os, 'windows') }} run: | - hatch run cov:test --cov-fail-under 50 || hatch run test:test --lf + hatch run cov:test --cov-fail-under 50 - name: Run the tests on pypy timeout-minutes: 15 if: ${{ startsWith( matrix.python-version, 'pypy' ) }} run: | - hatch run test:nowarn || hatch run test:nowarn --lf + hatch run test:nowarn - name: Run the tests on Windows timeout-minutes: 15 if: ${{ startsWith(matrix.os, 'windows') }} run: | - hatch run cov:nowarn || hatch run test:nowarn --lf + hatch run cov:nowarn - name: Check Launcher run: | @@ -144,7 +144,7 @@ jobs: - name: Run the tests timeout-minutes: 15 - run: pytest -W default -vv || pytest --vv -W default --lf + run: pytest -W default -vv test_miniumum_versions: name: Test Minimum Versions @@ -164,7 +164,7 @@ jobs: - name: Run the unit tests run: | - hatch -v run test:nowarn || hatch run test:nowarn --lf + hatch -v run test:nowarn test_prereleases: name: Test Prereleases @@ -179,7 +179,7 @@ jobs: dependency_type: pre - name: Run the tests run: | - hatch run test:nowarn || hatch run test:nowarn --lf + hatch run test:nowarn make_sdist: name: Make SDist diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index b47c0f06d..42ea51994 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -104,7 +104,7 @@ jobs: shell: bash -l {0} run: | cd ${GITHUB_WORKSPACE}/.. - git clone https://github.com/jupyter/qtconsole.git + git clone https://github.com/spyder-ide/qtconsole.git cd qtconsole ${pythonLocation}/bin/python -m pip install -e ".[test]" ${pythonLocation}/bin/python -m pip install pyqt5 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 7ab2c8bf0..8e17caad0 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,7 +3,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3.13" sphinx: configuration: docs/conf.py diff --git a/docs/api/ipykernel.comm.rst b/docs/api/ipykernel.comm.rst index a2d529ed0..1cf9ee4e7 100644 --- a/docs/api/ipykernel.comm.rst +++ b/docs/api/ipykernel.comm.rst @@ -7,19 +7,19 @@ Submodules .. automodule:: ipykernel.comm.comm :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.comm.manager :members: - :show-inheritance: :undoc-members: + :show-inheritance: Module contents --------------- .. automodule:: ipykernel.comm :members: - :show-inheritance: :undoc-members: + :show-inheritance: diff --git a/docs/api/ipykernel.inprocess.rst b/docs/api/ipykernel.inprocess.rst index 24d62e7ad..c2d6536bc 100644 --- a/docs/api/ipykernel.inprocess.rst +++ b/docs/api/ipykernel.inprocess.rst @@ -7,49 +7,49 @@ Submodules .. automodule:: ipykernel.inprocess.blocking :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.inprocess.channels :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.inprocess.client :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.inprocess.constants :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.inprocess.ipkernel :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.inprocess.manager :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.inprocess.socket :members: - :show-inheritance: :undoc-members: + :show-inheritance: Module contents --------------- .. automodule:: ipykernel.inprocess :members: - :show-inheritance: :undoc-members: + :show-inheritance: diff --git a/docs/api/ipykernel.rst b/docs/api/ipykernel.rst index 1a3286c8a..0f023070d 100644 --- a/docs/api/ipykernel.rst +++ b/docs/api/ipykernel.rst @@ -16,115 +16,145 @@ Submodules .. automodule:: ipykernel.compiler :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.connect :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.control :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.debugger :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.displayhook :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.embed :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.eventloops :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.heartbeat :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.iostream :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.ipkernel :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.jsonutil :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.kernelapp :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.kernelbase :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.kernelspec :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.log :members: - :show-inheritance: :undoc-members: + :show-inheritance: .. automodule:: ipykernel.parentpoller :members: + :undoc-members: :show-inheritance: + + +.. automodule:: ipykernel.shellchannel + :members: :undoc-members: + :show-inheritance: -.. automodule:: ipykernel.trio_runner +.. automodule:: ipykernel.socket_pair :members: + :undoc-members: :show-inheritance: + + +.. automodule:: ipykernel.subshell + :members: :undoc-members: + :show-inheritance: -.. automodule:: ipykernel.zmqshell +.. automodule:: ipykernel.subshell_manager + :members: + :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.thread :members: + :undoc-members: :show-inheritance: + + +.. automodule:: ipykernel.trio_runner + :members: :undoc-members: + :show-inheritance: + + +.. automodule:: ipykernel.zmqshell + :members: + :undoc-members: + :show-inheritance: Module contents --------------- .. automodule:: ipykernel :members: - :show-inheritance: :undoc-members: + :show-inheritance: diff --git a/ipykernel/control.py b/ipykernel/control.py index 0ee0fad05..21d6d9962 100644 --- a/ipykernel/control.py +++ b/ipykernel/control.py @@ -1,32 +1,11 @@ """A thread for a control channel.""" -from threading import Thread -from tornado.ioloop import IOLoop +from .thread import CONTROL_THREAD_NAME, BaseThread -CONTROL_THREAD_NAME = "Control" - -class ControlThread(Thread): +class ControlThread(BaseThread): """A thread for a control channel.""" def __init__(self, **kwargs): """Initialize the thread.""" - Thread.__init__(self, name=CONTROL_THREAD_NAME, **kwargs) - self.io_loop = IOLoop(make_current=False) - self.pydev_do_not_trace = True - self.is_pydev_daemon_thread = True - - def run(self): - """Run the thread.""" - self.name = CONTROL_THREAD_NAME - try: - self.io_loop.start() - finally: - self.io_loop.close() - - def stop(self): - """Stop the thread. - - This method is threadsafe. - """ - self.io_loop.add_callback(self.io_loop.stop) + super().__init__(name=CONTROL_THREAD_NAME, **kwargs) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index ada46eaa2..22ea82880 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -361,6 +361,11 @@ def set_sigint_result(): # restore the previous sigint handler signal.signal(signal.SIGINT, save_sigint) + @contextmanager + def _dummy_context_manager(self, *args): + # Signals only work in main thread, so cannot use _cancel_on_sigint in subshells. + yield + async def execute_request(self, stream, ident, parent): """Override for cell output - cell reconciliation.""" parent_header = extract_header(parent) @@ -439,7 +444,12 @@ async def run_cell(*args, **kwargs): coro_future = asyncio.ensure_future(coro) - with self._cancel_on_sigint(coro_future): + cm = ( + self._cancel_on_sigint + if threading.current_thread() == threading.main_thread() + else self._dummy_context_manager + ) + with cm(coro_future): # type:ignore[operator] res = None try: res = await coro_future diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 097b65aa9..e3136a03c 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -53,6 +53,7 @@ from .iostream import IOPubThread from .ipkernel import IPythonKernel from .parentpoller import ParentPollerUnix, ParentPollerWindows +from .shellchannel import ShellChannelThread from .zmqshell import ZMQInteractiveShell # ----------------------------------------------------------------------------- @@ -143,6 +144,7 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMix iopub_socket = Any() iopub_thread = Any() control_thread = Any() + shell_channel_thread = Any() _ports = Dict() @@ -367,6 +369,11 @@ def init_control(self, context): self.control_socket.router_handover = 1 self.control_thread = ControlThread(daemon=True) + self.shell_channel_thread = ShellChannelThread( + context, + self.shell_socket, + daemon=True, + ) def init_iopub(self, context): """Initialize the iopub channel.""" @@ -406,6 +413,10 @@ def close(self): self.log.debug("Closing control thread") self.control_thread.stop() self.control_thread.join() + if self.shell_channel_thread and self.shell_channel_thread.is_alive(): + self.log.debug("Closing shell channel thread") + self.shell_channel_thread.stop() + self.shell_channel_thread.join() if self.debugpy_socket and not self.debugpy_socket.closed: self.debugpy_socket.close() @@ -546,10 +557,17 @@ def init_signal(self): def init_kernel(self): """Create the Kernel object itself""" - shell_stream = ZMQStream(self.shell_socket) + if self.shell_channel_thread: + shell_stream = ZMQStream(self.shell_socket, self.shell_channel_thread.io_loop) + else: + shell_stream = ZMQStream(self.shell_socket) control_stream = ZMQStream(self.control_socket, self.control_thread.io_loop) debugpy_stream = ZMQStream(self.debugpy_socket, self.control_thread.io_loop) + self.control_thread.start() + if self.shell_channel_thread: + self.shell_channel_thread.start() + kernel_factory = self.kernel_class.instance # type:ignore[attr-defined] kernel = kernel_factory( @@ -560,6 +578,7 @@ def init_kernel(self): debug_shell_socket=self.debug_shell_socket, shell_stream=shell_stream, control_thread=self.control_thread, + shell_channel_thread=self.shell_channel_thread, iopub_thread=self.iopub_thread, iopub_socket=self.iopub_socket, stdin_socket=self.stdin_socket, diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index cb440199e..0c922f2bd 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -20,7 +20,7 @@ from functools import partial from signal import SIGINT, SIGTERM, Signals, default_int_handler, signal -from .control import CONTROL_THREAD_NAME +from .thread import CONTROL_THREAD_NAME if sys.platform != "win32": from signal import SIGKILL @@ -144,6 +144,7 @@ def _shell_streams_changed(self, change): # pragma: no cover debug_shell_socket = Any() control_thread = Any() + shell_channel_thread = Any() iopub_socket = Any() iopub_thread = Any() stdin_socket = Any() @@ -267,6 +268,9 @@ def _parent_header(self): "abort_request", "debug_request", "usage_request", + "create_subshell_request", + "delete_subshell_request", + "list_subshell_request", ] def __init__(self, **kwargs): @@ -346,11 +350,28 @@ def should_handle(self, stream, msg, idents): return False return True - async def dispatch_shell(self, msg): + async def dispatch_shell(self, msg, /, subshell_id: str | None = None): """dispatch shell requests""" + if len(msg) == 1 and msg[0].buffer == b"stop aborting": + # Dummy "stop aborting" message to stop aborting execute requests on this subshell. + # This dummy message implementation allows the subshell to abort messages that are + # already queued in the zmq sockets/streams without having to know any of their + # details in advance. + if subshell_id is None: + self._aborting = False + else: + self.shell_channel_thread.manager.set_subshell_aborting(subshell_id, False) + return + if not self.session: return + if self._supports_kernel_subshells: + assert threading.current_thread() not in ( + self.control_thread, + self.shell_channel_thread, + ) + idents, msg = self.session.feed_identities(msg, copy=False) try: msg = self.session.deserialize(msg, content=True, copy=False) @@ -363,12 +384,25 @@ async def dispatch_shell(self, msg): self._publish_status("busy", "shell") msg_type = msg["header"]["msg_type"] + assert msg["header"].get("subshell_id") == subshell_id + + if self._supports_kernel_subshells: + stream = self.shell_channel_thread.manager.get_subshell_to_shell_channel_socket( + subshell_id + ) + else: + stream = self.shell_stream # Only abort execute requests - if self._aborting and msg_type == "execute_request": - self._send_abort_reply(self.shell_stream, msg, idents) - self._publish_status_and_flush("idle", "shell", self.shell_stream) - return + if msg_type == "execute_request": + if subshell_id is None: + aborting = self._aborting # type:ignore[unreachable] + else: + aborting = self.shell_channel_thread.manager.get_subshell_aborting(subshell_id) + if aborting: + self._send_abort_reply(stream, msg, idents) + self._publish_status_and_flush("idle", "shell", stream) + return # Print some info about this message and leave a '--->' marker, so it's # easier to trace visually the message chain when debugging. Each @@ -376,8 +410,8 @@ async def dispatch_shell(self, msg): self.log.debug("\n*** MESSAGE TYPE:%s***", msg_type) self.log.debug(" Content: %s\n --->\n ", msg["content"]) - if not self.should_handle(self.shell_stream, msg, idents): - self._publish_status_and_flush("idle", "shell", self.shell_stream) + if not self.should_handle(stream, msg, idents): + self._publish_status_and_flush("idle", "shell", stream) return handler = self.shell_handlers.get(msg_type, None) @@ -390,7 +424,7 @@ async def dispatch_shell(self, msg): except Exception: self.log.debug("Unable to signal in pre_handler_hook:", exc_info=True) try: - result = handler(self.shell_stream, idents, msg) + result = handler(stream, idents, msg) if inspect.isawaitable(result): await result except Exception: @@ -406,7 +440,7 @@ async def dispatch_shell(self, msg): sys.stdout.flush() sys.stderr.flush() - self._publish_status_and_flush("idle", "shell", self.shell_stream) + self._publish_status_and_flush("idle", "shell", stream) def pre_handler_hook(self): """Hook to execute before calling message handler""" @@ -540,23 +574,83 @@ def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() self.msg_queue: Queue[t.Any] = Queue() - self.io_loop.add_callback(self.dispatch_queue) + if not self.shell_channel_thread: + self.io_loop.add_callback(self.dispatch_queue) if self.control_stream: self.control_stream.on_recv(self.dispatch_control, copy=False) if self.shell_stream: - self.shell_stream.on_recv( - partial( - self.schedule_dispatch, - self.dispatch_shell, - ), - copy=False, - ) + if self.shell_channel_thread: + self.shell_channel_thread.manager.set_on_recv_callback(self.shell_main) + self.shell_stream.on_recv(self.shell_channel_thread_main, copy=False) + else: + self.shell_stream.on_recv( + partial( + self.schedule_dispatch, + self.dispatch_shell, + ), + copy=False, + ) # publish idle status self._publish_status("starting", "shell") + async def shell_channel_thread_main(self, msg): + """Handler for shell messages received on shell_channel_thread""" + assert threading.current_thread() == self.shell_channel_thread + + if self.session is None: + return + + # deserialize only the header to get subshell_id + # Keep original message to send to subshell_id unmodified. + _, msg2 = self.session.feed_identities(msg, copy=False) + try: + msg3 = self.session.deserialize(msg2, content=False, copy=False) + subshell_id = msg3["header"].get("subshell_id") + + # Find inproc pair socket to use to send message to correct subshell. + subshell_manager = self.shell_channel_thread.manager + socket = subshell_manager.get_shell_channel_to_subshell_socket(subshell_id) + assert socket is not None + socket.send_multipart(msg, copy=False) + except Exception: + self.log.error("Invalid message", exc_info=True) # noqa: G201 + + if self.shell_stream: + self.shell_stream.flush() + + async def shell_main(self, subshell_id: str | None, msg): + """Handler of shell messages for a single subshell""" + if self._supports_kernel_subshells: + if subshell_id is None: + assert threading.current_thread() == threading.main_thread() + else: + assert threading.current_thread() not in ( + self.shell_channel_thread, + threading.main_thread(), + ) + socket_pair = self.shell_channel_thread.manager.get_shell_channel_to_subshell_pair( + subshell_id + ) + else: + assert subshell_id is None + assert threading.current_thread() == threading.main_thread() + socket_pair = None + + try: + # Whilst executing a shell message, do not accept any other shell messages on the + # same subshell, so that cells are run sequentially. Without this we can run multiple + # async cells at the same time which would be a nice feature to have but is an API + # change. + if socket_pair: + socket_pair.pause_on_recv() + await self.dispatch_shell(msg, subshell_id=subshell_id) + finally: + if socket_pair: + socket_pair.resume_on_recv() + def record_ports(self, ports): """Record the ports that this kernel is using. @@ -596,7 +690,7 @@ def _publish_status(self, status, channel, parent=None): def _publish_status_and_flush(self, status, channel, stream, parent=None): """send status on IOPub and flush specified stream to ensure reply is sent before handling the next reply""" self._publish_status(status, channel, parent) - if stream: + if stream and hasattr(stream, "flush"): stream.flush(zmq.POLLOUT) def _publish_debug_event(self, event): @@ -741,6 +835,8 @@ async def execute_request(self, stream, ident, parent): if self._do_exec_accepted_params["cell_id"]: do_execute_args["cell_id"] = cell_id + subshell_id = parent["header"].get("subshell_id") + # Call do_execute with the appropriate arguments reply_content = self.do_execute(**do_execute_args) @@ -772,7 +868,8 @@ async def execute_request(self, stream, ident, parent): self.log.debug("%s", reply_msg) if not silent and reply_msg["content"]["status"] == "error" and stop_on_error: - self._abort_queues() + subshell_id = parent["header"].get("subshell_id") + self._abort_queues(subshell_id) def do_execute( self, @@ -877,14 +974,18 @@ async def connect_request(self, stream, ident, parent): @property def kernel_info(self): - return { + info = { "protocol_version": kernel_protocol_version, "implementation": self.implementation, "implementation_version": self.implementation_version, "language_info": self.language_info, "banner": self.banner, "help_links": self.help_links, + "supported_features": [], } + if self._supports_kernel_subshells: + info["supported_features"] = ["kernel subshells"] + return info async def kernel_info_request(self, stream, ident, parent): """Handle a kernel info request.""" @@ -971,7 +1072,8 @@ async def shutdown_request(self, stream, ident, parent): control_io_loop.add_callback(control_io_loop.stop) self.log.debug("Stopping shell ioloop") - if self.shell_stream: + self.io_loop.add_callback(self.io_loop.stop) + if self.shell_stream and self.shell_stream.io_loop != self.io_loop: shell_io_loop = self.shell_stream.io_loop shell_io_loop.add_callback(shell_io_loop.stop) @@ -1063,6 +1165,63 @@ async def usage_request(self, stream, ident, parent): async def do_debug_request(self, msg): raise NotImplementedError + async def create_subshell_request(self, socket, ident, parent) -> None: + if not self.session: + return + if not self._supports_kernel_subshells: + self.log.error("Subshells are not supported by this kernel") + return + + assert threading.current_thread().name == CONTROL_THREAD_NAME + + # This should only be called in the control thread if it exists. + # Request is passed to shell channel thread to process. + control_socket = self.shell_channel_thread.manager.control_to_shell_channel.from_socket + control_socket.send_json({"type": "create"}) + reply = control_socket.recv_json() + self.session.send(socket, "create_subshell_reply", reply, parent, ident) + + async def delete_subshell_request(self, socket, ident, parent) -> None: + if not self.session: + return + if not self._supports_kernel_subshells: + self.log.error("KERNEL SUBSHELLS NOT SUPPORTED") + return + + assert threading.current_thread().name == CONTROL_THREAD_NAME + + try: + content = parent["content"] + subshell_id = content["subshell_id"] + except Exception: + self.log.error("Got bad msg from parent: %s", parent) + return + + # This should only be called in the control thread if it exists. + # Request is passed to shell channel thread to process. + control_socket = self.shell_channel_thread.manager.control_to_shell_channel.from_socket + control_socket.send_json({"type": "delete", "subshell_id": subshell_id}) + reply = control_socket.recv_json() + + self.session.send(socket, "delete_subshell_reply", reply, parent, ident) + + async def list_subshell_request(self, socket, ident, parent) -> None: + if not self.session: + return + if not self._supports_kernel_subshells: + self.log.error("Subshells are not supported by this kernel") + return + + assert threading.current_thread().name == CONTROL_THREAD_NAME + + # This should only be called in the control thread if it exists. + # Request is passed to shell channel thread to process. + control_socket = self.shell_channel_thread.manager.control_to_shell_channel.from_socket + control_socket.send_json({"type": "list"}) + reply = control_socket.recv_json() + + self.session.send(socket, "list_subshell_reply", reply, parent, ident) + # --------------------------------------------------------------------------- # Engine methods (DEPRECATED) # --------------------------------------------------------------------------- @@ -1116,7 +1275,9 @@ async def abort_request(self, stream, ident, parent): # pragma: no cover if isinstance(msg_ids, str): msg_ids = [msg_ids] if not msg_ids: - self._abort_queues() + subshell_id = parent["header"].get("subshell_id") + self._abort_queues(subshell_id) + for mid in msg_ids: self.aborted.add(str(mid)) @@ -1153,12 +1314,34 @@ def _topic(self, topic): _aborting = Bool(False) - def _abort_queues(self): + def _post_dummy_stop_aborting_message(self, subshell_id: str | None) -> None: + """Post a dummy message to the correct subshell that when handled will unset + the _aborting flag. + """ + subshell_manager = self.shell_channel_thread.manager + socket = subshell_manager.get_shell_channel_to_subshell_socket(subshell_id) + assert socket is not None + + msg = b"stop aborting" # Magic string for dummy message. + socket.send(msg, copy=False) + + def _abort_queues(self, subshell_id: str | None = None): # while this flag is true, # execute requests will be aborted - self._aborting = True + + if subshell_id is None: + self._aborting = True + else: + self.shell_channel_thread.manager.set_subshell_aborting(subshell_id, True) self.log.info("Aborting queue") + if self.shell_channel_thread: + # Only really need to do this if there are messages already queued + self.shell_channel_thread.io_loop.add_callback( + self._post_dummy_stop_aborting_message, subshell_id + ) + return + # flush streams, so all currently waiting messages # are added to the queue if self.shell_stream: @@ -1387,3 +1570,7 @@ async def _at_shutdown(self): self.log.debug("%s", self._shutdown_message) if self.control_stream: self.control_stream.flush(zmq.POLLOUT) + + @property + def _supports_kernel_subshells(self): + return self.shell_channel_thread is not None diff --git a/ipykernel/shellchannel.py b/ipykernel/shellchannel.py new file mode 100644 index 000000000..77a02f11a --- /dev/null +++ b/ipykernel/shellchannel.py @@ -0,0 +1,47 @@ +"""A thread for a shell channel.""" +from __future__ import annotations + +from typing import Any + +import zmq + +from .subshell_manager import SubshellManager +from .thread import SHELL_CHANNEL_THREAD_NAME, BaseThread + + +class ShellChannelThread(BaseThread): + """A thread for a shell channel. + + Communicates with shell/subshell threads via pairs of ZMQ inproc sockets. + """ + + def __init__( + self, + context: zmq.Context[Any], + shell_socket: zmq.Socket[Any], + **kwargs, + ): + """Initialize the thread.""" + super().__init__(name=SHELL_CHANNEL_THREAD_NAME, **kwargs) + self._manager: SubshellManager | None = None + self._context = context + self._shell_socket = shell_socket + + @property + def manager(self) -> SubshellManager: + # Lazy initialisation. + if self._manager is None: + self._manager = SubshellManager( + self._context, + self.io_loop, + self._shell_socket, + ) + return self._manager + + def run(self) -> None: + """Run the thread.""" + try: + super().run() + finally: + if self._manager: + self._manager.close() diff --git a/ipykernel/socket_pair.py b/ipykernel/socket_pair.py new file mode 100644 index 000000000..cbdb2cc7b --- /dev/null +++ b/ipykernel/socket_pair.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from typing import Any + +import zmq +from tornado.ioloop import IOLoop +from zmq.eventloop.zmqstream import ZMQStream + + +class SocketPair: + """Pair of ZMQ inproc sockets for one-direction communication between 2 threads. + + One of the threads is always the shell_channel_thread, the other may be the control + thread, main thread or a subshell thread. + """ + + from_socket: zmq.Socket[Any] + to_socket: zmq.Socket[Any] + to_stream: ZMQStream | None = None + on_recv_callback: Any + on_recv_copy: bool + + def __init__(self, context: zmq.Context[Any], name: str): + self.from_socket = context.socket(zmq.PAIR) + self.to_socket = context.socket(zmq.PAIR) + address = self._address(name) + self.from_socket.bind(address) + self.to_socket.connect(address) # Or do I need to do this in another thread? + + def close(self): + self.from_socket.close() + + if self.to_stream is not None: + self.to_stream.close() + self.to_socket.close() + + def on_recv(self, io_loop: IOLoop, on_recv_callback, copy: bool = False): + # io_loop is that of the 'to' thread. + self.on_recv_callback = on_recv_callback + self.on_recv_copy = copy + if self.to_stream is None: + self.to_stream = ZMQStream(self.to_socket, io_loop) + self.resume_on_recv() + + def pause_on_recv(self): + if self.to_stream is not None: + self.to_stream.stop_on_recv() + + def resume_on_recv(self): + if self.to_stream is not None: + self.to_stream.on_recv(self.on_recv_callback, copy=self.on_recv_copy) + + def _address(self, name) -> str: + return f"inproc://subshell{name}" diff --git a/ipykernel/subshell.py b/ipykernel/subshell.py new file mode 100644 index 000000000..34c0778bd --- /dev/null +++ b/ipykernel/subshell.py @@ -0,0 +1,34 @@ +"""A thread for a subshell.""" + +from typing import Any + +import zmq + +from .socket_pair import SocketPair +from .thread import BaseThread + + +class SubshellThread(BaseThread): + """A thread for a subshell.""" + + def __init__( + self, + subshell_id: str, + context: zmq.Context[Any], + **kwargs, + ): + """Initialize the thread.""" + super().__init__(name=f"subshell-{subshell_id}", **kwargs) + + self.shell_channel_to_subshell = SocketPair(context, subshell_id) + self.subshell_to_shell_channel = SocketPair(context, subshell_id + "-reverse") + + # When aborting flag is set, execute_request messages to this subshell will be aborted. + self.aborting = False + + def run(self) -> None: + try: + super().run() + finally: + self.shell_channel_to_subshell.close() + self.subshell_to_shell_channel.close() diff --git a/ipykernel/subshell_manager.py b/ipykernel/subshell_manager.py new file mode 100644 index 000000000..be9dd758e --- /dev/null +++ b/ipykernel/subshell_manager.py @@ -0,0 +1,213 @@ +"""Manager of subshells in a kernel.""" +from __future__ import annotations + +import json +import typing as t +import uuid +from functools import partial +from threading import Lock, current_thread, main_thread + +import zmq +from tornado.ioloop import IOLoop + +from .socket_pair import SocketPair +from .subshell import SubshellThread +from .thread import SHELL_CHANNEL_THREAD_NAME + + +class SubshellManager: + """A manager of subshells. + + Controls the lifetimes of subshell threads and their associated ZMQ sockets and + streams. Runs mostly in the shell channel thread. + + Care needed with threadsafe access here. All write access to the cache occurs in + the shell channel thread so there is only ever one write access at any one time. + Reading of cache information can be performed by other threads, so all reads are + protected by a lock so that they are atomic. + + Sending reply messages via the shell_socket is wrapped by another lock to protect + against multiple subshells attempting to send at the same time. + """ + + def __init__( + self, + context: zmq.Context[t.Any], + shell_channel_io_loop: IOLoop, + shell_socket: zmq.Socket[t.Any], + ): + assert current_thread() == main_thread() + + self._context: zmq.Context[t.Any] = context + self._shell_channel_io_loop = shell_channel_io_loop + self._shell_socket = shell_socket + self._cache: dict[str, SubshellThread] = {} + self._lock_cache = Lock() + self._lock_shell_socket = Lock() + + # Inproc socket pair for communication from control thread to shell channel thread, + # such as for create_subshell_request messages. Reply messages are returned straight away. + self.control_to_shell_channel = SocketPair(self._context, "control") + self.control_to_shell_channel.on_recv( + self._shell_channel_io_loop, self._process_control_request, copy=True + ) + + # Inproc socket pair for communication from shell channel thread to main thread, + # such as for execute_request messages. + self._shell_channel_to_main = SocketPair(self._context, "main") + + # Inproc socket pair for communication from main thread to shell channel thread. + # such as for execute_reply messages. + self._main_to_shell_channel = SocketPair(self._context, "main-reverse") + self._main_to_shell_channel.on_recv( + self._shell_channel_io_loop, self._send_on_shell_channel + ) + + def close(self) -> None: + """Stop all subshells and close all resources.""" + assert current_thread().name == SHELL_CHANNEL_THREAD_NAME + with self._lock_cache: + while True: + try: + _, subshell_thread = self._cache.popitem() + except KeyError: + break + self._stop_subshell(subshell_thread) + + self.control_to_shell_channel.close() + self._main_to_shell_channel.close() + self._shell_channel_to_main.close() + + def get_shell_channel_to_subshell_pair(self, subshell_id: str | None) -> SocketPair: + if subshell_id is None: + return self._shell_channel_to_main + with self._lock_cache: + return self._cache[subshell_id].shell_channel_to_subshell + + def get_subshell_to_shell_channel_socket(self, subshell_id: str | None) -> zmq.Socket[t.Any]: + if subshell_id is None: + return self._main_to_shell_channel.from_socket + with self._lock_cache: + return self._cache[subshell_id].subshell_to_shell_channel.from_socket + + def get_shell_channel_to_subshell_socket(self, subshell_id: str | None) -> zmq.Socket[t.Any]: + return self.get_shell_channel_to_subshell_pair(subshell_id).from_socket + + def get_subshell_aborting(self, subshell_id: str) -> bool: + """Get the aborting flag of the specified subshell.""" + return self._cache[subshell_id].aborting + + def list_subshell(self) -> list[str]: + """Return list of current subshell ids. + + Can be called by any subshell using %subshell magic. + """ + with self._lock_cache: + return list(self._cache) + + def set_on_recv_callback(self, on_recv_callback): + assert current_thread() == main_thread() + self._on_recv_callback = on_recv_callback + self._shell_channel_to_main.on_recv(IOLoop.current(), partial(self._on_recv_callback, None)) + + def set_subshell_aborting(self, subshell_id: str, aborting: bool) -> None: + """Set the aborting flag of the specified subshell.""" + with self._lock_cache: + self._cache[subshell_id].aborting = aborting + + def subshell_id_from_thread_id(self, thread_id: int) -> str | None: + """Return subshell_id of the specified thread_id. + + Raises RuntimeError if thread_id is not the main shell or a subshell. + + Only used by %subshell magic so does not have to be fast/cached. + """ + with self._lock_cache: + if thread_id == main_thread().ident: + return None + for id, subshell in self._cache.items(): + if subshell.ident == thread_id: + return id + msg = f"Thread id {thread_id!r} does not correspond to a subshell of this kernel" + raise RuntimeError(msg) + + def _create_subshell(self) -> str: + """Create and start a new subshell thread.""" + assert current_thread().name == SHELL_CHANNEL_THREAD_NAME + + subshell_id = str(uuid.uuid4()) + subshell_thread = SubshellThread(subshell_id, self._context) + + with self._lock_cache: + assert subshell_id not in self._cache + self._cache[subshell_id] = subshell_thread + + subshell_thread.shell_channel_to_subshell.on_recv( + subshell_thread.io_loop, + partial(self._on_recv_callback, subshell_id), + ) + + subshell_thread.subshell_to_shell_channel.on_recv( + self._shell_channel_io_loop, self._send_on_shell_channel + ) + + subshell_thread.start() + return subshell_id + + def _delete_subshell(self, subshell_id: str) -> None: + """Delete subshell identified by subshell_id. + + Raises key error if subshell_id not in cache. + """ + assert current_thread().name == SHELL_CHANNEL_THREAD_NAME + + with self._lock_cache: + subshell_threwad = self._cache.pop(subshell_id) + + self._stop_subshell(subshell_threwad) + + def _process_control_request( + self, + request: list[t.Any], + ) -> None: + """Process a control request message received on the control inproc + socket and return the reply. Runs in the shell channel thread. + """ + assert current_thread().name == SHELL_CHANNEL_THREAD_NAME + + try: + decoded = json.loads(request[0]) + type = decoded["type"] + reply: dict[str, t.Any] = {"status": "ok"} + + if type == "create": + reply["subshell_id"] = self._create_subshell() + elif type == "delete": + subshell_id = decoded["subshell_id"] + self._delete_subshell(subshell_id) + elif type == "list": + reply["subshell_id"] = self.list_subshell() + else: + msg = f"Unrecognised message type {type!r}" + raise RuntimeError(msg) + except BaseException as err: + reply = { + "status": "error", + "evalue": str(err), + } + + # Return the reply to the control thread. + self.control_to_shell_channel.to_socket.send_json(reply) + + def _send_on_shell_channel(self, msg) -> None: + assert current_thread().name == SHELL_CHANNEL_THREAD_NAME + with self._lock_shell_socket: + self._shell_socket.send_multipart(msg) + + def _stop_subshell(self, subshell_thread: SubshellThread) -> None: + """Stop a subshell thread and close all of its resources.""" + assert current_thread().name == SHELL_CHANNEL_THREAD_NAME + + if subshell_thread.is_alive(): + subshell_thread.stop() + subshell_thread.join() diff --git a/ipykernel/thread.py b/ipykernel/thread.py new file mode 100644 index 000000000..13eb781b7 --- /dev/null +++ b/ipykernel/thread.py @@ -0,0 +1,32 @@ +"""Base class for threads.""" +from threading import Thread + +from tornado.ioloop import IOLoop + +CONTROL_THREAD_NAME = "Control" +SHELL_CHANNEL_THREAD_NAME = "Shell channel" + + +class BaseThread(Thread): + """Base class for threads.""" + + def __init__(self, **kwargs): + """Initialize the thread.""" + super().__init__(**kwargs) + self.io_loop = IOLoop(make_current=False) + self.pydev_do_not_trace = True + self.is_pydev_daemon_thread = True + + def run(self) -> None: + """Run the thread.""" + try: + self.io_loop.start() + finally: + self.io_loop.close() + + def stop(self) -> None: + """Stop the thread. + + This method is threadsafe. + """ + self.io_loop.add_callback(self.io_loop.stop) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 4fa850735..60682379d 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -16,9 +16,9 @@ import os import sys +import threading import warnings from pathlib import Path -from threading import local from IPython.core import page, payloadpage from IPython.core.autocall import ZMQExitAutocall @@ -69,7 +69,7 @@ def _flush_streams(self): @default("_thread_local") def _default_thread_local(self): """Initialize our thread local storage""" - return local() + return threading.local() @property def _hooks(self): @@ -439,6 +439,39 @@ def autosave(self, arg_s): else: print("Autosave disabled") + @line_magic + def subshell(self, arg_s): + """ + List all current subshells + """ + from ipykernel.kernelapp import IPKernelApp + + if not IPKernelApp.initialized(): + msg = "Not in a running Kernel" + raise RuntimeError(msg) + + app = IPKernelApp.instance() + kernel = app.kernel + + if not getattr(kernel, "_supports_kernel_subshells", False): + print("Kernel does not support subshells") + return + + thread_id = threading.current_thread().ident + manager = kernel.shell_channel_thread.manager + try: + subshell_id = manager.subshell_id_from_thread_id(thread_id) + except RuntimeError: + subshell_id = "unknown" + subshell_id_list = manager.list_subshell() + + print(f"subshell id: {subshell_id}") + print(f"thread id: {thread_id}") + print(f"main thread id: {threading.main_thread().ident}") + print(f"pid: {os.getpid()}") + print(f"thread count: {threading.active_count()}") + print(f"subshell list: {subshell_id_list}") + class ZMQInteractiveShell(InteractiveShell): """A subclass of InteractiveShell for ZMQ.""" diff --git a/pyproject.toml b/pyproject.toml index b11d572c9..39246b492 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,9 @@ Tracker = "https://github.com/ipython/ipykernel/issues" [project.optional-dependencies] docs = [ - "sphinx", + # Sphinx pinned until `sphinx-autodoc-typehints` issue is resolved: + # https://github.com/tox-dev/sphinx-autodoc-typehints/issues/523 + "sphinx<8.2.0", "myst_parser", "pydata_sphinx_theme", "sphinxcontrib_github_alt", @@ -156,7 +158,8 @@ testpaths = [ "tests", ] asyncio_mode = "auto" -timeout = 300 +#timeout = 300 +timeout = 30 # Restore this setting to debug failures #timeout_method = "thread" filterwarnings= [ diff --git a/tests/test_connect.py b/tests/test_connect.py index b4b739d97..118d94dee 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -2,7 +2,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. - import errno import json import os diff --git a/tests/test_eventloop.py b/tests/test_eventloop.py index 69acc0ea2..76cadd7be 100644 --- a/tests/test_eventloop.py +++ b/tests/test_eventloop.py @@ -124,12 +124,16 @@ def test_cocoa_loop(kernel): loop_cocoa(kernel) -@pytest.mark.skipif( - len(qt_guis_avail) == 0, reason="No viable version of PyQt or PySide installed." -) -def test_qt_enable_gui(kernel, capsys): - gui = qt_guis_avail[0] - +@pytest.mark.parametrize("gui", qt_guis_avail) +def test_qt_enable_gui(gui, kernel, capsys): + if os.getenv("GITHUB_ACTIONS", None) == "true" and gui == "qt5": + pytest.skip("Qt5 and GitHub action crash CPython") + if gui == "qt6" and sys.version_info < (3, 10): + pytest.skip( + "qt6 fails on 3.9 with AttributeError: module 'PySide6.QtPrintSupport' has no attribute 'QApplication'" + ) + if sys.platform == "linux" and gui == "qt6" and os.getenv("GITHUB_ACTIONS", None) == "true": + pytest.skip("qt6 fails on github CI with missing libEGL.so.1") enable_gui(gui, kernel) # We store the `QApplication` instance in the kernel. diff --git a/tests/test_message_spec.py b/tests/test_message_spec.py index 7dd8ad1e2..0459b342b 100644 --- a/tests/test_message_spec.py +++ b/tests/test_message_spec.py @@ -238,6 +238,21 @@ class HistoryReply(Reply): history = List(List()) +# Subshell control messages + + +class CreateSubshellReply(Reply): + subshell_id = Unicode() + + +class DeleteSubshellReply(Reply): + pass + + +class ListSubshellReply(Reply): + subshell_id = List(Unicode()) + + references = { "execute_reply": ExecuteReply(), "inspect_reply": InspectReply(), @@ -254,6 +269,9 @@ class HistoryReply(Reply): "stream": Stream(), "display_data": DisplayData(), "header": RHeader(), + "create_subshell_reply": CreateSubshellReply(), + "delete_subshell_reply": DeleteSubshellReply(), + "list_subshell_reply": ListSubshellReply(), } # ----------------------------------------------------------------------------- @@ -365,7 +383,6 @@ def test_execute_stop_on_error(): KC.execute(code='print("Hello")') KC.execute(code='print("world")') reply = KC.get_shell_msg(timeout=TIMEOUT) - print(reply) reply = KC.get_shell_msg(timeout=TIMEOUT) assert reply["content"]["status"] == "aborted" # second message, too @@ -498,6 +515,8 @@ def test_kernel_info_request(): msg_id = KC.kernel_info() reply = get_reply(KC, msg_id, TIMEOUT) validate_message(reply, "kernel_info_reply", msg_id) + assert "supported_features" in reply["content"] + assert "kernel subshells" in reply["content"]["supported_features"] def test_connect_request(): @@ -509,6 +528,29 @@ def test_connect_request(): validate_message(reply, "connect_reply", msg_id) +def test_subshell(): + flush_channels() + + msg = KC.session.msg("create_subshell_request") + KC.control_channel.send(msg) + msg_id = msg["header"]["msg_id"] + reply = get_reply(KC, msg_id, TIMEOUT, channel="control") + validate_message(reply, "create_subshell_reply", msg_id) + subshell_id = reply["content"]["subshell_id"] + + msg = KC.session.msg("list_subshell_request") + KC.control_channel.send(msg) + msg_id = msg["header"]["msg_id"] + reply = get_reply(KC, msg_id, TIMEOUT, channel="control") + validate_message(reply, "list_subshell_reply", msg_id) + + msg = KC.session.msg("delete_subshell_request", {"subshell_id": subshell_id}) + KC.control_channel.send(msg) + msg_id = msg["header"]["msg_id"] + reply = get_reply(KC, msg_id, TIMEOUT, channel="control") + validate_message(reply, "delete_subshell_reply", msg_id) + + @pytest.mark.skipif( version_info < (5, 0), reason="earlier Jupyter Client don't have comm_info", diff --git a/tests/test_subshells.py b/tests/test_subshells.py new file mode 100644 index 000000000..d1affa666 --- /dev/null +++ b/tests/test_subshells.py @@ -0,0 +1,260 @@ +"""Test kernel subshells.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +from __future__ import annotations + +import platform +import time + +import pytest +from jupyter_client.blocking.client import BlockingKernelClient + +from .utils import TIMEOUT, assemble_output, get_replies, get_reply, new_kernel + +# Helpers + + +def create_subshell_helper(kc: BlockingKernelClient): + msg = kc.session.msg("create_subshell_request") + kc.control_channel.send(msg) + msg_id = msg["header"]["msg_id"] + reply = get_reply(kc, msg_id, TIMEOUT, channel="control") + return reply["content"] + + +def delete_subshell_helper(kc: BlockingKernelClient, subshell_id: str): + msg = kc.session.msg("delete_subshell_request", {"subshell_id": subshell_id}) + kc.control_channel.send(msg) + msg_id = msg["header"]["msg_id"] + reply = get_reply(kc, msg_id, TIMEOUT, channel="control") + return reply["content"] + + +def list_subshell_helper(kc: BlockingKernelClient): + msg = kc.session.msg("list_subshell_request") + kc.control_channel.send(msg) + msg_id = msg["header"]["msg_id"] + reply = get_reply(kc, msg_id, TIMEOUT, channel="control") + return reply["content"] + + +def execute_request(kc: BlockingKernelClient, code: str, subshell_id: str | None): + msg = kc.session.msg("execute_request", {"code": code}) + msg["header"]["subshell_id"] = subshell_id + kc.shell_channel.send(msg) + return msg + + +def execute_request_subshell_id( + kc: BlockingKernelClient, code: str, subshell_id: str | None, terminator: str = "\n" +): + msg = execute_request(kc, code, subshell_id) + msg_id = msg["header"]["msg_id"] + stdout, _ = assemble_output(kc.get_iopub_msg, None, msg_id) + return stdout.strip() + + +def execute_thread_count(kc: BlockingKernelClient) -> int: + code = "print(threading.active_count())" + return int(execute_request_subshell_id(kc, code, None)) + + +def execute_thread_ids(kc: BlockingKernelClient, subshell_id: str | None = None) -> tuple[str, str]: + code = "print(threading.get_ident(), threading.main_thread().ident)" + return execute_request_subshell_id(kc, code, subshell_id).split() + + +# Tests + + +def test_no_subshells(): + with new_kernel() as kc: + # Test operation of separate channel thread without using any subshells. + execute_request_subshell_id(kc, "a = 2*3", None) + res = execute_request_subshell_id(kc, "print(a)", None) + assert res == "6" + + +def test_supported(): + with new_kernel() as kc: + msg_id = kc.kernel_info() + reply = get_reply(kc, msg_id, TIMEOUT) + assert "supported_features" in reply["content"] + assert "kernel subshells" in reply["content"]["supported_features"] + + +def test_subshell_id_lifetime(): + with new_kernel() as kc: + assert list_subshell_helper(kc)["subshell_id"] == [] + subshell_id = create_subshell_helper(kc)["subshell_id"] + assert list_subshell_helper(kc)["subshell_id"] == [subshell_id] + delete_subshell_helper(kc, subshell_id) + assert list_subshell_helper(kc)["subshell_id"] == [] + + +def test_thread_counts(): + with new_kernel() as kc: + execute_request_subshell_id(kc, "import threading", None) + nthreads = execute_thread_count(kc) + + subshell_id = create_subshell_helper(kc)["subshell_id"] + nthreads2 = execute_thread_count(kc) + assert nthreads2 > nthreads + + delete_subshell_helper(kc, subshell_id) + nthreads3 = execute_thread_count(kc) + assert nthreads3 == nthreads + + +def test_thread_ids(): + with new_kernel() as kc: + execute_request_subshell_id(kc, "import threading", None) + subshell_id = create_subshell_helper(kc)["subshell_id"] + + thread_id, main_thread_id = execute_thread_ids(kc) + assert thread_id == main_thread_id + + thread_id, main_thread_id = execute_thread_ids(kc, subshell_id) # This is the problem + assert thread_id != main_thread_id + + delete_subshell_helper(kc, subshell_id) + + +@pytest.mark.parametrize("are_subshells", [(False, True), (True, False), (True, True)]) +@pytest.mark.parametrize("overlap", [True, False]) +def test_run_concurrently_sequence(are_subshells, overlap, request): + if request.config.getvalue("--cov"): + pytest.skip("Skip time-sensitive subshell tests if measuring coverage") + + with new_kernel() as kc: + subshell_ids = [ + create_subshell_helper(kc)["subshell_id"] if is_subshell else None + for is_subshell in are_subshells + ] + + # Import time module before running time-sensitive subshell code + # and use threading.Barrier to synchronise start of subshell code. + execute_request_subshell_id( + kc, "import threading as t, time; b=t.Barrier(2); print('ok')", None + ) + + sleep = 0.5 + if overlap: + codes = [ + f"b.wait(); start0=True; end0=False; time.sleep({sleep}); end0=True", + f"b.wait(); time.sleep({sleep / 2}); assert start0; assert not end0; time.sleep({sleep}); assert end0", + ] + else: + codes = [ + f"b.wait(); start0=True; end0=False; time.sleep({sleep}); assert end1", + f"b.wait(); time.sleep({sleep / 2}); assert start0; assert not end0; end1=True", + ] + + msgs = [] + for subshell_id, code in zip(subshell_ids, codes): + msg = kc.session.msg("execute_request", {"code": code}) + msg["header"]["subshell_id"] = subshell_id + kc.shell_channel.send(msg) + msgs.append(msg) + + replies = get_replies(kc, [msg["msg_id"] for msg in msgs], timeout=None) + + for subshell_id in subshell_ids: + if subshell_id: + delete_subshell_helper(kc, subshell_id) + + for reply in replies: + assert reply["content"]["status"] == "ok", reply + + +def test_create_while_execute(): + with new_kernel() as kc: + # Send request to execute code on main subshell. + msg = kc.session.msg("execute_request", {"code": "import time; time.sleep(0.05)"}) + kc.shell_channel.send(msg) + + # Create subshell via control channel. + control_msg = kc.session.msg("create_subshell_request") + kc.control_channel.send(control_msg) + control_reply = get_reply(kc, control_msg["header"]["msg_id"], TIMEOUT, channel="control") + subshell_id = control_reply["content"]["subshell_id"] + control_date = control_reply["header"]["date"] + + # Get result message from main subshell. + shell_date = get_reply(kc, msg["msg_id"])["header"]["date"] + + delete_subshell_helper(kc, subshell_id) + + assert control_date < shell_date + + +@pytest.mark.skipif( + platform.python_implementation() == "PyPy", + reason="does not work on PyPy", +) +def test_shutdown_with_subshell(): + # Based on test_kernel.py::test_shutdown + with new_kernel() as kc: + km = kc.parent + subshell_id = create_subshell_helper(kc)["subshell_id"] + assert list_subshell_helper(kc)["subshell_id"] == [subshell_id] + kc.shutdown() + for _ in range(100): # 10 s timeout + if km.is_alive(): + time.sleep(0.1) + else: + break + assert not km.is_alive() + + +@pytest.mark.parametrize("are_subshells", [(False, True), (True, False), (True, True)]) +def test_execute_stop_on_error(are_subshells): + # Based on test_message_spec.py::test_execute_stop_on_error, testing that exception + # in one subshell aborts execution queue in that subshell but not others. + with new_kernel() as kc: + subshell_ids = [ + create_subshell_helper(kc)["subshell_id"] if is_subshell else None + for is_subshell in are_subshells + ] + + msg_ids = [] + + msg = execute_request( + kc, "import asyncio; await asyncio.sleep(1); raise ValueError()", subshell_ids[0] + ) + msg_ids.append(msg["msg_id"]) + msg = execute_request(kc, "print('hello')", subshell_ids[0]) + msg_ids.append(msg["msg_id"]) + msg = execute_request(kc, "print('goodbye')", subshell_ids[0]) + msg_ids.append(msg["msg_id"]) + + msg = execute_request(kc, "import time; time.sleep(1.5)", subshell_ids[1]) + msg_ids.append(msg["msg_id"]) + msg = execute_request(kc, "print('other')", subshell_ids[1]) + msg_ids.append(msg["msg_id"]) + + replies = get_replies(kc, msg_ids) + + assert replies[0]["parent_header"]["subshell_id"] == subshell_ids[0] + assert replies[1]["parent_header"]["subshell_id"] == subshell_ids[0] + assert replies[2]["parent_header"]["subshell_id"] == subshell_ids[0] + assert replies[3]["parent_header"]["subshell_id"] == subshell_ids[1] + assert replies[4]["parent_header"]["subshell_id"] == subshell_ids[1] + + assert replies[0]["content"]["status"] == "error" + assert replies[1]["content"]["status"] == "aborted" + assert replies[2]["content"]["status"] == "aborted" + assert replies[3]["content"]["status"] == "ok" + assert replies[4]["content"]["status"] == "ok" + + # Check abort is cleared. + msg = execute_request(kc, "print('check')", subshell_ids[0]) + reply = get_reply(kc, msg["msg_id"]) + assert reply["parent_header"]["subshell_id"] == subshell_ids[0] + assert reply["content"]["status"] == "ok" + + # Cleanup + for subshell_id in subshell_ids: + if subshell_id: + delete_subshell_helper(kc, subshell_id) diff --git a/tests/utils.py b/tests/utils.py index a0880df13..772163992 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import atexit import os @@ -62,6 +63,24 @@ def get_reply(kc, msg_id, timeout=TIMEOUT, channel="shell"): return reply +def get_replies(kc, msg_ids: list[str], timeout=TIMEOUT, channel="shell"): + # Get replies which may arrive in any order as they may be running on different subshells. + # Replies are returned in the same order as the msg_ids, not in the order of arrival. + count = 0 + replies = [None] * len(msg_ids) + while count < len(msg_ids): + get_msg = getattr(kc, f"get_{channel}_msg") + reply = get_msg(timeout=timeout) + try: + msg_id = reply["parent_header"]["msg_id"] + replies[msg_ids.index(msg_id)] = reply + count += 1 + except ValueError: + # Allow debugging ignored replies + print(f"Ignoring reply not to any of {msg_ids}: {reply}") + return replies + + def execute(code="", kc=None, **kwargs): """wrapper for doing common steps for validating an execution request""" from .test_message_spec import validate_message @@ -149,14 +168,19 @@ def new_kernel(argv=None): return manager.run_kernel(**kwargs) -def assemble_output(get_msg): +def assemble_output(get_msg, timeout=1, parent_msg_id: str | None = None): """assemble stdout/err from an execution""" stdout = "" stderr = "" while True: - msg = get_msg(timeout=1) + msg = get_msg(timeout=timeout) msg_type = msg["msg_type"] content = msg["content"] + + if parent_msg_id is not None and msg["parent_header"]["msg_id"] != parent_msg_id: + # Ignore message for wrong parent message + continue + if msg_type == "status" and content["execution_state"] == "idle": # idle message signals end of output break @@ -173,12 +197,16 @@ def assemble_output(get_msg): return stdout, stderr -def wait_for_idle(kc): +def wait_for_idle(kc, parent_msg_id: str | None = None): while True: msg = kc.get_iopub_msg(timeout=1) msg_type = msg["msg_type"] content = msg["content"] - if msg_type == "status" and content["execution_state"] == "idle": + if ( + msg_type == "status" + and content["execution_state"] == "idle" + and (parent_msg_id is None or msg["parent_header"]["msg_id"] == parent_msg_id) + ): break From 2320380fef613cd92234a6ec9bfcc747a9afdf63 Mon Sep 17 00:00:00 2001 From: ianthomas23 Date: Thu, 5 Jun 2025 15:41:47 +0000 Subject: [PATCH 1076/1195] Publish 6.30.0a0 SHA256 hashes: ipykernel-6.30.0a0-py3-none-any.whl: 051c75d9cd34260ebe381ae5d4c32b9bc2b6326c2a03d47ad7ea4572d21bc524 ipykernel-6.30.0a0.tar.gz: f86d3d92d9c9c7d64dd9192b44bb720a278063fe76bcd1b9f212c825861fcec3 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 84767e41b..9a289909e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ +## 6.30.0a0 + +Pre-release to allow further testing of subshell implementation. + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.5...5d90d9e10425886deb4b0f1676b14b1701522b3c)) + +### Enhancements made + +- Subshells implemented using tornado event loops on 6.x branch [#1396](https://github.com/ipython/ipykernel/pull/1396) ([@ianthomas23](https://github.com/ianthomas23)) + +### Bugs fixed + +- [Bugfix] Set shell idle when message skipped by "should_handle" in "dispatch_shell" [#1395](https://github.com/ipython/ipykernel/pull/1395) ([@dby-tmwctw](https://github.com/dby-tmwctw)) + +### Maintenance and upkeep improvements + +- Backports and extra changes to fix CI on 6.x branch [#1390](https://github.com/ipython/ipykernel/pull/1390) ([@ianthomas23](https://github.com/ianthomas23)) +- Avoid a DeprecationWarning on Python 3.13+ [#1248](https://github.com/ipython/ipykernel/pull/1248) ([@hroncok](https://github.com/hroncok)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-07-01&to=2025-06-05&type=c)) + +[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2024-07-01..2025-06-05&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2024-07-01..2025-06-05&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-07-01..2025-06-05&type=Issues) | [@dby-tmwctw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adby-tmwctw+updated%3A2024-07-01..2025-06-05&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-07-01..2025-06-05&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2024-07-01..2025-06-05&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2024-07-01..2025-06-05&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-07-01..2025-06-05&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ameeseeksmachine+updated%3A2024-07-01..2025-06-05&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-07-01..2025-06-05&type=Issues) + + + ## 6.29.5 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.4...1e62d48298e353a9879fae99bc752f9bb48797ef)) @@ -20,8 +47,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2024-03-27..2024-06-29&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-03-27..2024-06-29&type=Issues) - - ## 6.29.4 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.3...1cea5332ffc37f32e8232fd2b8b8ddd91b2bbdcf)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 792dff91b..33313973b 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "6.29.5" +__version__ = "6.30.0a0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From c725c1fbd7d6ec90c24b275eaf35e4538af528e4 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 13 Jun 2025 16:24:33 +0100 Subject: [PATCH 1077/1195] Test more python versions on 6.x branch (#1398) Co-authored-by: David Brochart --- .github/workflows/ci.yml | 19 ++++++++----------- CHANGELOG.md | 2 +- ipykernel/socket_pair.py | 2 +- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b15038b09..819eb4910 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,16 +22,13 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.9", "3.13"] - include: - - os: ubuntu-latest - python-version: "pypy-3.9" - - os: macos-latest - python-version: "3.10" - - os: ubuntu-latest - python-version: "3.11" - - os: ubuntu-latest - python-version: "3.12" + python-version: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "pypy-3.10" steps: - name: Checkout uses: actions/checkout@v4 @@ -59,7 +56,7 @@ jobs: - name: Run the tests on Windows timeout-minutes: 15 - if: ${{ startsWith(matrix.os, 'windows') }} + if: ${{ !startsWith( matrix.python-version, 'pypy' ) && ${{ startsWith(matrix.os, 'windows') }} run: | hatch run cov:nowarn diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a289909e..4786fbfc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ Pre-release to allow further testing of subshell implementation. ### Bugs fixed -- [Bugfix] Set shell idle when message skipped by "should_handle" in "dispatch_shell" [#1395](https://github.com/ipython/ipykernel/pull/1395) ([@dby-tmwctw](https://github.com/dby-tmwctw)) +- \[Bugfix\] Set shell idle when message skipped by "should_handle" in "dispatch_shell" [#1395](https://github.com/ipython/ipykernel/pull/1395) ([@dby-tmwctw](https://github.com/dby-tmwctw)) ### Maintenance and upkeep improvements diff --git a/ipykernel/socket_pair.py b/ipykernel/socket_pair.py index cbdb2cc7b..9e6b42234 100644 --- a/ipykernel/socket_pair.py +++ b/ipykernel/socket_pair.py @@ -47,7 +47,7 @@ def pause_on_recv(self): self.to_stream.stop_on_recv() def resume_on_recv(self): - if self.to_stream is not None: + if self.to_stream is not None and not self.to_stream.closed(): self.to_stream.on_recv(self.on_recv_callback, copy=self.on_recv_copy) def _address(self, name) -> str: From 7b89b876a1c694d6294059a632f0cfe1117f1bd4 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 14 Jul 2025 13:18:29 +0100 Subject: [PATCH 1078/1195] Update pre-commit and github actions (#1401) --- .github/workflows/ci.yml | 2 +- .github/workflows/publish-changelog.yml | 2 +- .github/workflows/publish-release.yml | 2 +- .pre-commit-config.yaml | 12 ++--- CHANGELOG.md | 58 ++++++++++++------------- ipykernel/trio_runner.py | 2 +- tests/utils.py | 2 +- 7 files changed, 40 insertions(+), 40 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 819eb4910..f78dc42a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,7 @@ jobs: - name: Run the tests on Windows timeout-minutes: 15 - if: ${{ !startsWith( matrix.python-version, 'pypy' ) && ${{ startsWith(matrix.os, 'windows') }} + if: ${{ !startsWith( matrix.python-version, 'pypy' ) && startsWith(matrix.os, 'windows') }} run: | hatch run cov:nowarn diff --git a/.github/workflows/publish-changelog.yml b/.github/workflows/publish-changelog.yml index 60af4c5f1..c576a5487 100644 --- a/.github/workflows/publish-changelog.yml +++ b/.github/workflows/publish-changelog.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - uses: actions/create-github-app-token@v1 + - uses: actions/create-github-app-token@v2 id: app-token with: app-id: ${{ vars.APP_ID }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 5295e776b..f6743a9bf 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - uses: actions/create-github-app-token@v1 + - uses: actions/create-github-app-token@v2 id: app-token with: app-id: ${{ vars.APP_ID }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc2cfd9d8..e2ae0f758 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: check-case-conflict - id: check-ast @@ -22,12 +22,12 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.4 + rev: 0.33.2 hooks: - id: check-github-workflows - repo: https://github.com/executablebooks/mdformat - rev: 0.7.17 + rev: 0.7.22 hooks: - id: mdformat additional_dependencies: @@ -55,13 +55,13 @@ repos: ] - repo: https://github.com/adamchainz/blacken-docs - rev: "1.16.0" + rev: "1.19.1" hooks: - id: blacken-docs additional_dependencies: [black==23.7.0] - repo: https://github.com/codespell-project/codespell - rev: "v2.2.6" + rev: "v2.4.1" hooks: - id: codespell args: ["-L", "sur,nd"] @@ -83,7 +83,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2024.01.24" + rev: "2025.01.22" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 4786fbfc2..b13da6828 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ Pre-release to allow further testing of subshell implementation. ### Bugs fixed -- \[Bugfix\] Set shell idle when message skipped by "should_handle" in "dispatch_shell" [#1395](https://github.com/ipython/ipykernel/pull/1395) ([@dby-tmwctw](https://github.com/dby-tmwctw)) +- [Bugfix] Set shell idle when message skipped by "should_handle" in "dispatch_shell" [#1395](https://github.com/ipython/ipykernel/pull/1395) ([@dby-tmwctw](https://github.com/dby-tmwctw)) ### Maintenance and upkeep improvements @@ -39,7 +39,7 @@ Pre-release to allow further testing of subshell implementation. ### Maintenance and upkeep improvements -- \[6.x\] Update Release Scripts [#1251](https://github.com/ipython/ipykernel/pull/1251) ([@blink1073](https://github.com/blink1073)) +- [6.x] Update Release Scripts [#1251](https://github.com/ipython/ipykernel/pull/1251) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release @@ -98,7 +98,7 @@ Pre-release to allow further testing of subshell implementation. ### Bugs fixed -- Fix: ipykernel_launcher, delete absolute sys.path\[0\] [#1206](https://github.com/ipython/ipykernel/pull/1206) ([@stdll00](https://github.com/stdll00)) +- Fix: ipykernel_launcher, delete absolute sys.path[0] [#1206](https://github.com/ipython/ipykernel/pull/1206) ([@stdll00](https://github.com/stdll00)) ### Maintenance and upkeep improvements @@ -366,7 +366,7 @@ Pre-release to allow further testing of subshell implementation. ### Enhancements made -- Support control\<>iopub messages to e.g. unblock comm_msg from command execution [#1114](https://github.com/ipython/ipykernel/pull/1114) ([@tkrabel-db](https://github.com/tkrabel-db)) +- Support control\<>iopub messages to e.g. unblock comm_msg from command execution [#1114](https://github.com/ipython/ipykernel/pull/1114) ([@tkrabel-db](https://github.com/tkrabel-db)) - Add outstream hook similar to display publisher [#1110](https://github.com/ipython/ipykernel/pull/1110) ([@maartenbreddels](https://github.com/maartenbreddels)) ### Maintenance and upkeep improvements @@ -776,10 +776,10 @@ Pre-release to allow further testing of subshell implementation. ### Maintenance and upkeep improvements -- \[pre-commit.ci\] pre-commit autoupdate [#989](https://github.com/ipython/ipykernel/pull/989) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#985](https://github.com/ipython/ipykernel/pull/985) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#989](https://github.com/ipython/ipykernel/pull/989) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#985](https://github.com/ipython/ipykernel/pull/985) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Add python logo in svg format [#984](https://github.com/ipython/ipykernel/pull/984) ([@steff456](https://github.com/steff456)) -- \[pre-commit.ci\] pre-commit autoupdate [#982](https://github.com/ipython/ipykernel/pull/982) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#982](https://github.com/ipython/ipykernel/pull/982) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -797,13 +797,13 @@ Pre-release to allow further testing of subshell implementation. ### Maintenance and upkeep improvements -- \[pre-commit.ci\] pre-commit autoupdate [#978](https://github.com/ipython/ipykernel/pull/978) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#977](https://github.com/ipython/ipykernel/pull/977) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#976](https://github.com/ipython/ipykernel/pull/976) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#974](https://github.com/ipython/ipykernel/pull/974) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#971](https://github.com/ipython/ipykernel/pull/971) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#968](https://github.com/ipython/ipykernel/pull/968) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#966](https://github.com/ipython/ipykernel/pull/966) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#978](https://github.com/ipython/ipykernel/pull/978) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#977](https://github.com/ipython/ipykernel/pull/977) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#976](https://github.com/ipython/ipykernel/pull/976) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#974](https://github.com/ipython/ipykernel/pull/974) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#971](https://github.com/ipython/ipykernel/pull/971) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#968](https://github.com/ipython/ipykernel/pull/968) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#966](https://github.com/ipython/ipykernel/pull/966) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -821,9 +821,9 @@ Pre-release to allow further testing of subshell implementation. ### Maintenance and upkeep improvements -- \[pre-commit.ci\] pre-commit autoupdate [#962](https://github.com/ipython/ipykernel/pull/962) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#961](https://github.com/ipython/ipykernel/pull/961) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#960](https://github.com/ipython/ipykernel/pull/960) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#962](https://github.com/ipython/ipykernel/pull/962) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#961](https://github.com/ipython/ipykernel/pull/961) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#960](https://github.com/ipython/ipykernel/pull/960) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -843,7 +843,7 @@ Pre-release to allow further testing of subshell implementation. - Back to top-level tornado IOLoop [#958](https://github.com/ipython/ipykernel/pull/958) ([@minrk](https://github.com/minrk)) - Explicitly require pyzmq >= 17 [#957](https://github.com/ipython/ipykernel/pull/957) ([@minrk](https://github.com/minrk)) -- \[pre-commit.ci\] pre-commit autoupdate [#954](https://github.com/ipython/ipykernel/pull/954) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#954](https://github.com/ipython/ipykernel/pull/954) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -867,7 +867,7 @@ Pre-release to allow further testing of subshell implementation. ### Maintenance and upkeep improvements - Fix sphinx 5.0 support [#951](https://github.com/ipython/ipykernel/pull/951) ([@blink1073](https://github.com/blink1073)) -- \[pre-commit.ci\] pre-commit autoupdate [#950](https://github.com/ipython/ipykernel/pull/950) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#950](https://github.com/ipython/ipykernel/pull/950) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -886,18 +886,18 @@ Pre-release to allow further testing of subshell implementation. ### Maintenance and upkeep improvements -- \[pre-commit.ci\] pre-commit autoupdate [#945](https://github.com/ipython/ipykernel/pull/945) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#945](https://github.com/ipython/ipykernel/pull/945) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Clean up typings [#939](https://github.com/ipython/ipykernel/pull/939) ([@blink1073](https://github.com/blink1073)) -- \[pre-commit.ci\] pre-commit autoupdate [#938](https://github.com/ipython/ipykernel/pull/938) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#938](https://github.com/ipython/ipykernel/pull/938) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Clean up types [#933](https://github.com/ipython/ipykernel/pull/933) ([@blink1073](https://github.com/blink1073)) -- \[pre-commit.ci\] pre-commit autoupdate [#932](https://github.com/ipython/ipykernel/pull/932) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#932](https://github.com/ipython/ipykernel/pull/932) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Switch to hatch backend [#931](https://github.com/ipython/ipykernel/pull/931) ([@blink1073](https://github.com/blink1073)) -- \[pre-commit.ci\] pre-commit autoupdate [#928](https://github.com/ipython/ipykernel/pull/928) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#926](https://github.com/ipython/ipykernel/pull/926) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#928](https://github.com/ipython/ipykernel/pull/928) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#926](https://github.com/ipython/ipykernel/pull/926) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Allow enforce PR label workflow to add labels [#921](https://github.com/ipython/ipykernel/pull/921) ([@blink1073](https://github.com/blink1073)) -- \[pre-commit.ci\] pre-commit autoupdate [#920](https://github.com/ipython/ipykernel/pull/920) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#919](https://github.com/ipython/ipykernel/pull/919) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#917](https://github.com/ipython/ipykernel/pull/917) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#920](https://github.com/ipython/ipykernel/pull/920) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#919](https://github.com/ipython/ipykernel/pull/919) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#917](https://github.com/ipython/ipykernel/pull/917) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -922,7 +922,7 @@ Pre-release to allow further testing of subshell implementation. - Add basic mypy support [#913](https://github.com/ipython/ipykernel/pull/913) ([@blink1073](https://github.com/blink1073)) - Clean up pre-commit [#911](https://github.com/ipython/ipykernel/pull/911) ([@blink1073](https://github.com/blink1073)) - Update setup.py [#909](https://github.com/ipython/ipykernel/pull/909) ([@tlinhart](https://github.com/tlinhart)) -- \[pre-commit.ci\] pre-commit autoupdate [#906](https://github.com/ipython/ipykernel/pull/906) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#906](https://github.com/ipython/ipykernel/pull/906) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -1340,7 +1340,7 @@ Pre-release to allow further testing of subshell implementation. - Add watchfd keyword to InProcessKernel OutStream initialization [#727](https://github.com/ipython/ipykernel/pull/727) ([@rayosborn](https://github.com/rayosborn)) - Fix typo in eventloops.py [#711](https://github.com/ipython/ipykernel/pull/711) ([@selasley](https://github.com/selasley)) -- \[bugfix\] fix in setup.py (comma before appnope) [#709](https://github.com/ipython/ipykernel/pull/709) ([@jstriebel](https://github.com/jstriebel)) +- [bugfix] fix in setup.py (comma before appnope) [#709](https://github.com/ipython/ipykernel/pull/709) ([@jstriebel](https://github.com/jstriebel)) ### Maintenance and upkeep improvements diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index 45f738acb..a641feba8 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -29,7 +29,7 @@ def initialize(self, kernel, io_loop): bg_thread.start() def interrupt(self, signum, frame): - """Interuppt the runner.""" + """Interrupt the runner.""" if self._cell_cancel_scope: self._cell_cancel_scope.cancel() else: diff --git a/tests/utils.py b/tests/utils.py index 772163992..19a7b6be8 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -156,7 +156,7 @@ def stop_global_kernel(): def new_kernel(argv=None): """Context manager for a new kernel in a subprocess - Should only be used for tests where the kernel must not be re-used. + Should only be used for tests where the kernel must not be reused. Returns ------- From 501a1ad95086e1857879402050f91ac0c2428a58 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 14 Jul 2025 17:36:17 +0100 Subject: [PATCH 1079/1195] Backports from `anyio` (old `main`) branch to `main` branch (#1402) Co-authored-by: Gregory Shklover Co-authored-by: bluss Co-authored-by: M Bussonnier Co-authored-by: Min RK Co-authored-by: David Brochart Co-authored-by: Stephen Macke Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/downstream.yml | 20 +- .github/workflows/nightly.yml | 33 ++ .pre-commit-config.yaml | 6 +- examples/embedding/inprocess_qtconsole.py | 35 +- examples/embedding/inprocess_terminal.py | 1 + examples/embedding/ipkernel_wxapp.py | 1 + hatch_build.py | 1 + ipykernel/__main__.py | 1 + ipykernel/_version.py | 1 + ipykernel/comm/comm.py | 2 +- ipykernel/compiler.py | 1 + ipykernel/connect.py | 8 +- ipykernel/datapub.py | 84 ---- ipykernel/debugger.py | 1 + ipykernel/embed.py | 3 +- ipykernel/gui/gtk3embed.py | 3 +- ipykernel/gui/gtkembed.py | 3 +- ipykernel/heartbeat.py | 3 +- ipykernel/inprocess/blocking.py | 10 +- ipykernel/inprocess/client.py | 40 +- ipykernel/inprocess/constants.py | 3 +- ipykernel/inprocess/ipkernel.py | 13 +- ipykernel/inprocess/socket.py | 2 +- ipykernel/iostream.py | 16 +- ipykernel/ipkernel.py | 22 +- ipykernel/kernelapp.py | 22 +- ipykernel/kernelbase.py | 201 ++++----- ipykernel/log.py | 3 +- ipykernel/parentpoller.py | 28 +- ipykernel/pickleutil.py | 485 ---------------------- ipykernel/serialize.py | 203 --------- ipykernel/shellchannel.py | 1 + ipykernel/subshell_manager.py | 1 + ipykernel/thread.py | 1 + ipykernel/trio_runner.py | 1 + ipykernel/zmqshell.py | 81 ++-- pyproject.toml | 42 +- tests/_asyncio_utils.py | 17 - tests/conftest.py | 2 +- tests/test_connect.py | 15 + tests/test_debugger.py | 11 + tests/test_eventloop.py | 28 +- tests/test_kernel.py | 8 +- tests/test_kernel_direct.py | 3 +- tests/test_message_spec.py | 1 - tests/test_parentpoller.py | 18 +- tests/test_pickleutil.py | 78 ---- tests/test_zmq_shell.py | 2 +- 48 files changed, 375 insertions(+), 1190 deletions(-) create mode 100644 .github/workflows/nightly.yml delete mode 100644 ipykernel/datapub.py delete mode 100644 ipykernel/pickleutil.py delete mode 100644 ipykernel/serialize.py delete mode 100644 tests/_asyncio_utils.py delete mode 100644 tests/test_pickleutil.py diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 42ea51994..2aac74ee6 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -38,7 +38,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: ipywidgets - test_command: pytest -vv -raXxs -k \"not deprecation_fa_icons and not tooltip_deprecation and not on_submit_deprecation\" -W default --durations 10 --color=yes + test_command: pytest -vv -raXxs -W default --durations 10 --color=yes jupyter_client: runs-on: ubuntu-latest @@ -56,6 +56,7 @@ jobs: ipyparallel: runs-on: ubuntu-latest + timeout-minutes: 20 steps: - name: Checkout uses: actions/checkout@v4 @@ -147,20 +148,3 @@ jobs: run: | cd ${GITHUB_WORKSPACE}/../spyder-kernels xvfb-run --auto-servernum ${pythonLocation}/bin/python -m pytest -x -vv -s --full-trace --color=yes spyder_kernels - - downstream_check: # This job does nothing and is only used for the branch protection - if: always() - needs: - - nbclient - - ipywidgets - - jupyter_client - - ipyparallel - - jupyter_kernel_test - - spyder_kernels - - qtconsole - runs-on: ubuntu-latest - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 000000000..499f43562 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,33 @@ +name: nightly build and upload +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + +defaults: + run: + shell: bash -eux {0} + +jobs: + build: + runs-on: "ubuntu-latest" + strategy: + fail-fast: false + matrix: + python-version: ["3.12"] + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Base Setup + uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + + - name: Build + run: | + python -m pip install build + python -m build + - name: Upload wheel + uses: scientific-python/upload-nightly-action@82396a2ed4269ba06c6b2988bb4fd568ef3c3d6b # 0.6.1 + with: + artifacts_path: dist + anaconda_nightly_upload_token: ${{secrets.UPLOAD_TOKEN}} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e2ae0f758..6f852f711 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ ci: - autoupdate_schedule: monthly autoupdate_commit_msg: "chore: update pre-commit hooks" + autoupdate_schedule: weekly repos: - repo: https://github.com/pre-commit/pre-commit-hooks @@ -40,7 +40,7 @@ repos: types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.8.0" + rev: "v1.16.1" hooks: - id: mypy files: ipykernel @@ -74,7 +74,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.0 + rev: v0.11.4 hooks: - id: ruff types_or: [python, jupyter] diff --git a/examples/embedding/inprocess_qtconsole.py b/examples/embedding/inprocess_qtconsole.py index 18ce28638..f256ecfed 100644 --- a/examples/embedding/inprocess_qtconsole.py +++ b/examples/embedding/inprocess_qtconsole.py @@ -1,54 +1,25 @@ """An in-process qt console app.""" + import os -import sys import tornado from IPython.lib import guisupport from qtconsole.inprocess import QtInProcessKernelManager from qtconsole.rich_ipython_widget import RichIPythonWidget +assert tornado.version_info >= (6, 1) + def print_process_id(): """Print the process id.""" print("Process ID is:", os.getpid()) -def init_asyncio_patch(): - """set default asyncio policy to be compatible with tornado - Tornado 6 (at least) is not compatible with the default - asyncio implementation on Windows - Pick the older SelectorEventLoopPolicy on Windows - if the known-incompatible default policy is in use. - do this as early as possible to make it a low priority and overridable - ref: https://github.com/tornadoweb/tornado/issues/2608 - FIXME: if/when tornado supports the defaults in asyncio, - remove and bump tornado requirement for py38 - """ - if ( - sys.platform.startswith("win") - and sys.version_info >= (3, 8) - and tornado.version_info < (6, 1) - ): - import asyncio - - try: - from asyncio import WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy - except ImportError: - pass - # not affected - else: - if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: - # WindowsProactorEventLoopPolicy is not compatible with tornado 6 - # fallback to the pre-3.8 default of Selector - asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) - - def main(): """The main entry point.""" # Print the ID of the main process print_process_id() - init_asyncio_patch() app = guisupport.get_app_qt4() # Create an in-process kernel diff --git a/examples/embedding/inprocess_terminal.py b/examples/embedding/inprocess_terminal.py index b644c94af..221a0c473 100644 --- a/examples/embedding/inprocess_terminal.py +++ b/examples/embedding/inprocess_terminal.py @@ -1,4 +1,5 @@ """An in-process terminal example.""" + import os import sys diff --git a/examples/embedding/ipkernel_wxapp.py b/examples/embedding/ipkernel_wxapp.py index f24ed9392..b36fcc312 100755 --- a/examples/embedding/ipkernel_wxapp.py +++ b/examples/embedding/ipkernel_wxapp.py @@ -16,6 +16,7 @@ Ref: Modified from wxPython source code wxPython/samples/simple/simple.py """ + # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- diff --git a/hatch_build.py b/hatch_build.py index 934348050..4dfdd1a22 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -1,4 +1,5 @@ """A custom hatch build hook for ipykernel.""" + import shutil import sys from pathlib import Path diff --git a/ipykernel/__main__.py b/ipykernel/__main__.py index a1050e32e..59c864405 100644 --- a/ipykernel/__main__.py +++ b/ipykernel/__main__.py @@ -1,4 +1,5 @@ """The cli entry point for ipykernel.""" + if __name__ == "__main__": from ipykernel import kernelapp as app diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 33313973b..6b7e6d02a 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -1,6 +1,7 @@ """ store the current version info of the server. """ + import re # Version string must appear intact for hatch versioning diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index 1747d4ce7..a1b659e9a 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -49,7 +49,7 @@ def publish_msg(self, msg_type, data=None, metadata=None, buffers=None, **keys): class Comm(BaseComm, traitlets.config.LoggingConfigurable): """Class for communicating between a Frontend and a Kernel""" - kernel = Instance("ipykernel.kernelbase.Kernel", allow_none=True) # type:ignore[assignment] + kernel = Instance("ipykernel.kernelbase.Kernel", allow_none=True) comm_id = Unicode() primary = Bool(True, help="Am I the primary or secondary Comm?") diff --git a/ipykernel/compiler.py b/ipykernel/compiler.py index e42007ed6..6652e08ae 100644 --- a/ipykernel/compiler.py +++ b/ipykernel/compiler.py @@ -1,4 +1,5 @@ """Compiler helpers for the debugger.""" + import os import sys import tempfile diff --git a/ipykernel/connect.py b/ipykernel/connect.py index 59d36452d..1e1f16cdc 100644 --- a/ipykernel/connect.py +++ b/ipykernel/connect.py @@ -1,5 +1,5 @@ -"""Connection file-related utilities for the kernel -""" +"""Connection file-related utilities for the kernel""" + # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. from __future__ import annotations @@ -133,8 +133,8 @@ def connect_qtconsole( __all__ = [ - "write_connection_file", + "connect_qtconsole", "get_connection_file", "get_connection_info", - "connect_qtconsole", + "write_connection_file", ] diff --git a/ipykernel/datapub.py b/ipykernel/datapub.py deleted file mode 100644 index cc19696db..000000000 --- a/ipykernel/datapub.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -"""Publishing native (typically pickled) objects. -""" - -import warnings - -from traitlets import Any, CBytes, Dict, Instance -from traitlets.config import Configurable - -from ipykernel.jsonutil import json_clean - -try: - # available since ipyparallel 5.0.0 - from ipyparallel.serialize import serialize_object -except ImportError: - # Deprecated since ipykernel 4.3.0 - from ipykernel.serialize import serialize_object - -from jupyter_client.session import Session, extract_header - -warnings.warn( - "ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", - DeprecationWarning, - stacklevel=2, -) - - -class ZMQDataPublisher(Configurable): - """A zmq data publisher.""" - - topic = topic = CBytes(b"datapub") - session = Instance(Session, allow_none=True) - pub_socket = Any(allow_none=True) - parent_header = Dict({}) - - def set_parent(self, parent): - """Set the parent for outbound messages.""" - self.parent_header = extract_header(parent) - - def publish_data(self, data): - """publish a data_message on the IOPub channel - - Parameters - ---------- - data : dict - The data to be published. Think of it as a namespace. - """ - session = self.session - assert session is not None - buffers = serialize_object( - data, - buffer_threshold=session.buffer_threshold, - item_threshold=session.item_threshold, - ) - content = json_clean(dict(keys=list(data.keys()))) - session.send( - self.pub_socket, - "data_message", - content=content, - parent=self.parent_header, - buffers=buffers, - ident=self.topic, - ) - - -def publish_data(data): - """publish a data_message on the IOPub channel - - Parameters - ---------- - data : dict - The data to be published. Think of it as a namespace. - """ - warnings.warn( - "ipykernel.datapub is deprecated. It has moved to ipyparallel.datapub", - DeprecationWarning, - stacklevel=2, - ) - - from ipykernel.zmqshell import ZMQInteractiveShell - - ZMQInteractiveShell.instance().data_pub.publish_data(data) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 4d4d43dc3..64320e631 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -1,4 +1,5 @@ """Debugger implementation for the IPython kernel.""" + import os import re import sys diff --git a/ipykernel/embed.py b/ipykernel/embed.py index d2cbe60b4..5078f8ace 100644 --- a/ipykernel/embed.py +++ b/ipykernel/embed.py @@ -1,5 +1,4 @@ -"""Simple function for embedding an IPython kernel -""" +"""Simple function for embedding an IPython kernel""" # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- diff --git a/ipykernel/gui/gtk3embed.py b/ipykernel/gui/gtk3embed.py index 3317ecfe4..ab4ec2226 100644 --- a/ipykernel/gui/gtk3embed.py +++ b/ipykernel/gui/gtk3embed.py @@ -1,5 +1,4 @@ -"""GUI support for the IPython ZeroMQ kernel - GTK toolkit support. -""" +"""GUI support for the IPython ZeroMQ kernel - GTK toolkit support.""" # ----------------------------------------------------------------------------- # Copyright (C) 2010-2011 The IPython Development Team # diff --git a/ipykernel/gui/gtkembed.py b/ipykernel/gui/gtkembed.py index e87249ead..6f3b6d166 100644 --- a/ipykernel/gui/gtkembed.py +++ b/ipykernel/gui/gtkembed.py @@ -1,5 +1,4 @@ -"""GUI support for the IPython ZeroMQ kernel - GTK toolkit support. -""" +"""GUI support for the IPython ZeroMQ kernel - GTK toolkit support.""" # ----------------------------------------------------------------------------- # Copyright (C) 2010-2011 The IPython Development Team # diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 38fea17f9..3291e0aa6 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -1,5 +1,4 @@ -"""The client and server for a basic ping-pong style heartbeat. -""" +"""The client and server for a basic ping-pong style heartbeat.""" # ----------------------------------------------------------------------------- # Copyright (C) 2008-2011 The IPython Development Team diff --git a/ipykernel/inprocess/blocking.py b/ipykernel/inprocess/blocking.py index c598a44b4..3c2991990 100644 --- a/ipykernel/inprocess/blocking.py +++ b/ipykernel/inprocess/blocking.py @@ -1,7 +1,8 @@ -""" Implements a fully blocking kernel client. +"""Implements a fully blocking kernel client. Useful for test suites and blocking terminal interfaces. """ + import sys # ----------------------------------------------------------------------------- @@ -68,6 +69,7 @@ def call_handlers(self, msg): _raw_input = self.client.kernel._sys_raw_input prompt = msg["content"]["prompt"] print(prompt, end="", file=sys.__stdout__) + assert sys.__stdout__ is not None sys.__stdout__.flush() self.client.input(_raw_input()) @@ -76,9 +78,9 @@ class BlockingInProcessKernelClient(InProcessKernelClient): """A blocking in-process kernel client.""" # The classes to use for the various channels. - shell_channel_class = Type(BlockingInProcessChannel) # type:ignore[arg-type] - iopub_channel_class = Type(BlockingInProcessChannel) # type:ignore[arg-type] - stdin_channel_class = Type(BlockingInProcessStdInChannel) # type:ignore[arg-type] + shell_channel_class = Type(BlockingInProcessChannel) + iopub_channel_class = Type(BlockingInProcessChannel) + stdin_channel_class = Type(BlockingInProcessStdInChannel) def wait_for_ready(self): """Wait for kernel info reply on shell channel.""" diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index 6250302d5..ffe044826 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -10,8 +10,10 @@ # ----------------------------------------------------------------------------- # Imports # ----------------------------------------------------------------------------- +from __future__ import annotations import asyncio +from typing import Any from jupyter_client.client import KernelClient from jupyter_client.clientabc import KernelClientABC @@ -39,11 +41,11 @@ class InProcessKernelClient(KernelClient): """ # The classes to use for the various channels. - shell_channel_class = Type(InProcessChannel) # type:ignore[arg-type] - iopub_channel_class = Type(InProcessChannel) # type:ignore[arg-type] - stdin_channel_class = Type(InProcessChannel) # type:ignore[arg-type] - control_channel_class = Type(InProcessChannel) # type:ignore[arg-type] - hb_channel_class = Type(InProcessHBChannel) # type:ignore[arg-type] + shell_channel_class = Type(InProcessChannel) # type:ignore[assignment] + iopub_channel_class = Type(InProcessChannel) # type:ignore[assignment] + stdin_channel_class = Type(InProcessChannel) # type:ignore[assignment] + control_channel_class = Type(InProcessChannel) # type:ignore[assignment] + hb_channel_class = Type(InProcessHBChannel) # type:ignore[assignment] kernel = Instance("ipykernel.inprocess.ipkernel.InProcessKernel", allow_none=True) @@ -57,9 +59,9 @@ def _default_blocking_class(self): return BlockingInProcessKernelClient - def get_connection_info(self): + def get_connection_info(self, session: bool = False) -> dict[str, int | str | bytes]: """Get the connection info for the client.""" - d = super().get_connection_info() + d = super().get_connection_info(session=session) d["kernel"] = self.kernel # type:ignore[assignment] return d @@ -72,39 +74,45 @@ def start_channels(self, *args, **kwargs): @property def shell_channel(self): if self._shell_channel is None: - self._shell_channel = self.shell_channel_class(self) # type:ignore[abstract,call-arg] + self._shell_channel = self.shell_channel_class(self) return self._shell_channel @property def iopub_channel(self): if self._iopub_channel is None: - self._iopub_channel = self.iopub_channel_class(self) # type:ignore[abstract,call-arg] + self._iopub_channel = self.iopub_channel_class(self) return self._iopub_channel @property def stdin_channel(self): if self._stdin_channel is None: - self._stdin_channel = self.stdin_channel_class(self) # type:ignore[abstract,call-arg] + self._stdin_channel = self.stdin_channel_class(self) return self._stdin_channel @property def control_channel(self): if self._control_channel is None: - self._control_channel = self.control_channel_class(self) # type:ignore[abstract,call-arg] + self._control_channel = self.control_channel_class(self) return self._control_channel @property def hb_channel(self): if self._hb_channel is None: - self._hb_channel = self.hb_channel_class(self) # type:ignore[abstract,call-arg] + self._hb_channel = self.hb_channel_class(self) return self._hb_channel # Methods for sending specific messages # ------------------------------------- def execute( - self, code, silent=False, store_history=True, user_expressions=None, allow_stdin=None - ): + self, + code: str, + silent: bool = False, + store_history: bool = True, + user_expressions: dict[str, Any] | None = None, + allow_stdin: bool | None = None, + stop_on_error: bool = True, + ) -> str: """Execute code on the client.""" if allow_stdin is None: allow_stdin = self.allow_stdin @@ -117,7 +125,9 @@ def execute( ) msg = self.session.msg("execute_request", content) self._dispatch_to_kernel(msg) - return msg["header"]["msg_id"] + res = msg["header"]["msg_id"] + assert isinstance(res, str) + return res def complete(self, code, cursor_pos=None): """Get code completion.""" diff --git a/ipykernel/inprocess/constants.py b/ipykernel/inprocess/constants.py index 6133c757d..16d572083 100644 --- a/ipykernel/inprocess/constants.py +++ b/ipykernel/inprocess/constants.py @@ -1,5 +1,4 @@ -"""Shared constants. -""" +"""Shared constants.""" # Because inprocess communication is not networked, we can use a common Session # key everywhere. This is not just the empty bytestring to avoid tripping diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 789b0bf8a..62c15036b 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -2,6 +2,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +from __future__ import annotations import logging import sys @@ -51,7 +52,7 @@ class InProcessKernel(IPythonKernel): _underlying_iopub_socket = Instance(DummySocket, ()) iopub_thread: IOPubThread = Instance(IOPubThread) # type:ignore[assignment] - shell_stream = Instance(DummySocket, ()) # type:ignore[arg-type] + shell_stream = Instance(DummySocket, ()) # type:ignore[assignment] @default("iopub_thread") def _default_iopub_thread(self): @@ -65,7 +66,7 @@ def _default_iopub_thread(self): def _default_iopub_socket(self): return self.iopub_thread.background_socket - stdin_socket = Instance(DummySocket, ()) # type:ignore[assignment] + stdin_socket = Instance(DummySocket, ()) def __init__(self, **traits): """Initialize the kernel.""" @@ -85,14 +86,16 @@ def start(self): if self.shell: self.shell.exit_now = False - def _abort_queues(self): + def _abort_queues(self, subshell_id: str | None = ...): """The in-process kernel doesn't abort requests.""" def _input_request(self, prompt, ident, parent, password=False): # Flush output before making the request. self.raw_input_str = None - sys.stderr.flush() - sys.stdout.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() # Send the input request. content = json_clean(dict(prompt=prompt, password=password)) diff --git a/ipykernel/inprocess/socket.py b/ipykernel/inprocess/socket.py index 2df72b5e1..2a2866cb5 100644 --- a/ipykernel/inprocess/socket.py +++ b/ipykernel/inprocess/socket.py @@ -1,4 +1,4 @@ -""" Defines a dummy socket implementing (part of) the zmq.Socket interface. """ +"""Defines a dummy socket implementing (part of) the zmq.Socket interface.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 9dc55242b..7e8301149 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -383,6 +383,9 @@ def _watch_pipe_fd(self): """ + if self._fid is None: + return + try: bts = os.read(self._fid, PIPE_BUFFER_SIZE) while bts and self._should_watch: @@ -434,6 +437,7 @@ def __init__( ) # This is necessary for compatibility with Python built-in streams self.session = session + self._fid = None if not isinstance(pub_thread, IOPubThread): # Backward-compat: given socket, not thread. Wrap in a thread. warnings.warn( @@ -466,11 +470,13 @@ def __init__( self._local = local() if ( - watchfd - and ( - (sys.platform.startswith("linux") or sys.platform.startswith("darwin")) - # Pytest set its own capture. Don't redirect from within pytest. - and ("PYTEST_CURRENT_TEST" not in os.environ) + ( + watchfd + and ( + (sys.platform.startswith("linux") or sys.platform.startswith("darwin")) + # Pytest set its own capture. Don't redirect from within pytest. + and ("PYTEST_CURRENT_TEST" not in os.environ) + ) ) # allow forcing watchfd (mainly for tests) or watchfd == "force" diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 22ea82880..3e3927cc5 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -77,6 +77,12 @@ class IPythonKernel(KernelBase): shell = Instance("IPython.core.interactiveshell.InteractiveShellABC", allow_none=True) shell_class = Type(ZMQInteractiveShell) + # use fully-qualified name to ensure lazy import and prevent the issue from + # https://github.com/ipython/ipykernel/issues/1198 + debugger_class = Type("ipykernel.debugger.Debugger") + + compiler_class = Type(XCachingCompiler) + use_experimental_completions = Bool( True, help="Set this flag to False to deactivate the use of experimental IPython completion APIs.", @@ -114,11 +120,11 @@ def __init__(self, **kwargs): """Initialize the kernel.""" super().__init__(**kwargs) - from .debugger import Debugger, _is_debugpy_available + from .debugger import _is_debugpy_available # Initialize the Debugger if _is_debugpy_available: - self.debugger = Debugger( + self.debugger = self.debugger_class( self.log, self.debugpy_stream, self._publish_debug_event, @@ -134,7 +140,7 @@ def __init__(self, **kwargs): user_module=self.user_module, user_ns=self.user_ns, kernel=self, - compiler_class=XCachingCompiler, + compiler_class=self.compiler_class, ) self.shell.displayhook.session = self.session # type:ignore[attr-defined] @@ -225,7 +231,7 @@ def dispatch_debugpy(self, msg): self.debugger.tcp_client.receive_dap_frame(frame) @property - def banner(self): + def banner(self): # type:ignore[override] if self.shell: return self.shell.banner return None @@ -449,7 +455,7 @@ async def run_cell(*args, **kwargs): if threading.current_thread() == threading.main_thread() else self._dummy_context_manager ) - with cm(coro_future): # type:ignore[operator] + with cm(coro_future): res = None try: res = await coro_future @@ -774,9 +780,9 @@ def run_closure(self: threading.Thread): for stream in [stdout, stderr]: if isinstance(stream, OutStream): if parent == kernel_thread_ident: - stream._thread_to_parent_header[ - self.ident - ] = kernel._new_threads_parent_header + stream._thread_to_parent_header[self.ident] = ( + kernel._new_threads_parent_header + ) else: stream._thread_to_parent[self.ident] = parent _threading_Thread_run(self) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index e3136a03c..86b275a82 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -118,9 +118,9 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMix """The IPYKernel application class.""" name = "ipython-kernel" - aliases = Dict(kernel_aliases) # type:ignore[assignment] - flags = Dict(kernel_flags) # type:ignore[assignment] - classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session] + aliases = Dict(kernel_aliases) + flags = Dict(kernel_flags) + classes = [IPythonKernel, ZMQInteractiveShell, ProfileDir, Session] # type:ignore[assignment] # the kernel class, as an importstring kernel_class = Type( "ipykernel.ipkernel.IPythonKernel", @@ -220,7 +220,7 @@ def init_poller(self): # PID 1 (init) is special and will never go away, # only be reassigned. # Parent polling doesn't work if ppid == 1 to start with. - self.poller = ParentPollerUnix() + self.poller = ParentPollerUnix(parent_pid=self.parent_handle) def _try_bind_socket(self, s, port): iface = f"{self.transport}://{self.ip}" @@ -261,7 +261,7 @@ def _bind_socket(self, s, port): raise return None - def write_connection_file(self): + def write_connection_file(self, **kwargs: Any) -> None: """write connection info to JSON file""" cf = self.abs_connection_file connection_info = dict( @@ -331,12 +331,12 @@ def init_sockets(self): self.shell_socket = context.socket(zmq.ROUTER) self.shell_socket.linger = 1000 self.shell_port = self._bind_socket(self.shell_socket, self.shell_port) - self.log.debug("shell ROUTER Channel on port: %i" % self.shell_port) + self.log.debug("shell ROUTER Channel on port: %i", self.shell_port) self.stdin_socket = context.socket(zmq.ROUTER) self.stdin_socket.linger = 1000 self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port) - self.log.debug("stdin ROUTER Channel on port: %i" % self.stdin_port) + self.log.debug("stdin ROUTER Channel on port: %i", self.stdin_port) if hasattr(zmq, "ROUTER_HANDOVER"): # set router-handover to workaround zeromq reconnect problems @@ -352,7 +352,7 @@ def init_control(self, context): self.control_socket = context.socket(zmq.ROUTER) self.control_socket.linger = 1000 self.control_port = self._bind_socket(self.control_socket, self.control_port) - self.log.debug("control ROUTER Channel on port: %i" % self.control_port) + self.log.debug("control ROUTER Channel on port: %i", self.control_port) self.debugpy_socket = context.socket(zmq.STREAM) self.debugpy_socket.linger = 1000 @@ -380,7 +380,7 @@ def init_iopub(self, context): self.iopub_socket = context.socket(zmq.PUB) self.iopub_socket.linger = 1000 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port) - self.log.debug("iopub PUB Channel on port: %i" % self.iopub_port) + self.log.debug("iopub PUB Channel on port: %i", self.iopub_port) self.configure_tornado_logger() self.iopub_thread = IOPubThread(self.iopub_socket, pipe=True) self.iopub_thread.start() @@ -394,7 +394,7 @@ def init_heartbeat(self): hb_ctx = zmq.Context() self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port)) self.hb_port = self.heartbeat.port - self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port) + self.log.debug("Heartbeat REP Channel on port: %i", self.hb_port) self.heartbeat.start() def close(self): @@ -666,7 +666,7 @@ def _init_asyncio_patch(self): where asyncio.ProactorEventLoop supports add_reader and friends. """ - if sys.platform.startswith("win") and sys.version_info >= (3, 8): + if sys.platform.startswith("win"): import asyncio try: diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 0c922f2bd..da16f2a55 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -50,7 +50,6 @@ Instance, Integer, List, - Set, Unicode, default, observe, @@ -62,10 +61,18 @@ from ._version import kernel_protocol_version from .iostream import OutStream +_AWAITABLE_MESSAGE: str = ( + "For consistency across implementations, it is recommended that `{func_name}`" + " either be a coroutine function (`async def`) or return an awaitable object" + " (like an `asyncio.Future`). It might become a requirement in the future." + " Coroutine functions and awaitables have been supported since" + " ipykernel 6.0 (2021). {target} does not seem to return an awaitable" +) + def _accepts_parameters(meth, param_names): parameters = inspect.signature(meth).parameters - accepts = {param: False for param in param_names} + accepts = dict.fromkeys(param_names, False) for param in param_names: param_spec = parameters.get(param) @@ -240,9 +247,6 @@ def _parent_header(self): # by record_ports and used by connect_request. _recorded_ports = Dict() - # set of aborted msg_ids - aborted = Set() - # Track execution count here. For IPython, we override this to use the # execution count we store in the shell. execution_count = 0 @@ -258,14 +262,12 @@ def _parent_header(self): "shutdown_request", "is_complete_request", "interrupt_request", - # deprecated: - "apply_request", ] - # add deprecated ipyparallel control messages + + # control channel accepts all shell messages + # and some of its own control_msg_types = [ *msg_types, - "clear_request", - "abort_request", "debug_request", "usage_request", "create_subshell_request", @@ -333,21 +335,21 @@ async def process_control(self, msg): except Exception: self.log.error("Exception in control handler:", exc_info=True) # noqa: G201 - sys.stdout.flush() - sys.stderr.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() self._publish_status_and_flush("idle", "control", self.control_stream) def should_handle(self, stream, msg, idents): """Check whether a shell-channel message should be handled Allows subclasses to prevent handling of certain messages (e.g. aborted requests). + + .. versionchanged:: 7 + Subclass should_handle _may_ be async. + Base class implementation is not async. """ - msg_id = msg["header"]["msg_id"] - if msg_id in self.aborted: - # is it safe to assume a msg_id will not be resubmitted? - self.aborted.remove(msg_id) - self._send_abort_reply(stream, msg, idents) - return False return True async def dispatch_shell(self, msg, /, subshell_id: str | None = None): @@ -410,8 +412,12 @@ async def dispatch_shell(self, msg, /, subshell_id: str | None = None): self.log.debug("\n*** MESSAGE TYPE:%s***", msg_type) self.log.debug(" Content: %s\n --->\n ", msg["content"]) - if not self.should_handle(stream, msg, idents): + should_handle: bool | t.Awaitable[bool] = self.should_handle(stream, msg, idents) + if inspect.isawaitable(should_handle): + should_handle = await should_handle + if not should_handle: self._publish_status_and_flush("idle", "shell", stream) + self.log.debug("Not handling %s:%s", msg_type, msg["header"].get("msg_id")) return handler = self.shell_handlers.get(msg_type, None) @@ -438,8 +444,10 @@ async def dispatch_shell(self, msg, /, subshell_id: str | None = None): except Exception: self.log.debug("Unable to signal in post_handler_hook:", exc_info=True) - sys.stdout.flush() - sys.stderr.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() self._publish_status_and_flush("idle", "shell", stream) def pre_handler_hook(self): @@ -842,10 +850,18 @@ async def execute_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="do_execute", target=self.do_execute), + PendingDeprecationWarning, + stacklevel=1, + ) # Flush output before sending the reply. - sys.stdout.flush() - sys.stderr.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() # FIXME: on rare occasions, the flush doesn't seem to make it to the # clients... This seems to mitigate the problem, but we definitely need # to better understand what's going on. @@ -896,6 +912,12 @@ async def complete_request(self, stream, ident, parent): matches = self.do_complete(code, cursor_pos) if inspect.isawaitable(matches): matches = await matches + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="do_complete", target=self.do_complete), + PendingDeprecationWarning, + stacklevel=1, + ) matches = json_clean(matches) self.session.send(stream, "complete_reply", matches, parent, ident) @@ -924,6 +946,12 @@ async def inspect_request(self, stream, ident, parent): ) if inspect.isawaitable(reply_content): reply_content = await reply_content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="do_inspect", target=self.do_inspect), + PendingDeprecationWarning, + stacklevel=1, + ) # Before we send this object over, we scrub it for JSON usage reply_content = json_clean(reply_content) @@ -943,6 +971,12 @@ async def history_request(self, stream, ident, parent): reply_content = self.do_history(**content) if inspect.isawaitable(reply_content): reply_content = await reply_content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="do_history", target=self.do_history), + PendingDeprecationWarning, + stacklevel=1, + ) reply_content = json_clean(reply_content) msg = self.session.send(stream, "history_reply", reply_content, parent, ident) @@ -974,18 +1008,23 @@ async def connect_request(self, stream, ident, parent): @property def kernel_info(self): - info = { + from .debugger import _is_debugpy_available + + supported_features: list[str] = [] + if self._supports_kernel_subshells: + supported_features.append("kernel subshells") + if _is_debugpy_available: + supported_features.append("debugger") + + return { "protocol_version": kernel_protocol_version, "implementation": self.implementation, "implementation_version": self.implementation_version, "language_info": self.language_info, "banner": self.banner, "help_links": self.help_links, - "supported_features": [], + "supported_features": supported_features, } - if self._supports_kernel_subshells: - info["supported_features"] = ["kernel subshells"] - return info async def kernel_info_request(self, stream, ident, parent): """Handle a kernel info request.""" @@ -1060,6 +1099,12 @@ async def shutdown_request(self, stream, ident, parent): content = self.do_shutdown(parent["content"]["restart"]) if inspect.isawaitable(content): content = await content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="do_shutdown", target=self.do_shutdown), + PendingDeprecationWarning, + stacklevel=1, + ) self.session.send(stream, "shutdown_reply", content, parent, ident=ident) # same content, but different msg_id for broadcasting on IOPub self._shutdown_message = self.session.msg("shutdown_reply", content, parent) @@ -1093,6 +1138,12 @@ async def is_complete_request(self, stream, ident, parent): reply_content = self.do_is_complete(code) if inspect.isawaitable(reply_content): reply_content = await reply_content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format(func_name="do_is_complete", target=self.do_is_complete), + PendingDeprecationWarning, + stacklevel=1, + ) reply_content = json_clean(reply_content) reply_msg = self.session.send(stream, "is_complete_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) @@ -1109,6 +1160,14 @@ async def debug_request(self, stream, ident, parent): reply_content = self.do_debug_request(content) if inspect.isawaitable(reply_content): reply_content = await reply_content + else: + warnings.warn( + _AWAITABLE_MESSAGE.format( + func_name="do_debug_request", target=self.do_debug_request + ), + PendingDeprecationWarning, + stacklevel=1, + ) reply_content = json_clean(reply_content) reply_msg = self.session.send(stream, "debug_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) @@ -1222,86 +1281,6 @@ async def list_subshell_request(self, socket, ident, parent) -> None: self.session.send(socket, "list_subshell_reply", reply, parent, ident) - # --------------------------------------------------------------------------- - # Engine methods (DEPRECATED) - # --------------------------------------------------------------------------- - - async def apply_request(self, stream, ident, parent): # pragma: no cover - """Handle an apply request.""" - self.log.warning("apply_request is deprecated in kernel_base, moving to ipyparallel.") - try: - content = parent["content"] - bufs = parent["buffers"] - msg_id = parent["header"]["msg_id"] - except Exception: - self.log.error("Got bad msg: %s", parent, exc_info=True) # noqa: G201 - return - - md = self.init_metadata(parent) - - reply_content, result_buf = self.do_apply(content, bufs, msg_id, md) - - # flush i/o - sys.stdout.flush() - sys.stderr.flush() - - md = self.finish_metadata(parent, md, reply_content) - if not self.session: - return - self.session.send( - stream, - "apply_reply", - reply_content, - parent=parent, - ident=ident, - buffers=result_buf, - metadata=md, - ) - - def do_apply(self, content, bufs, msg_id, reply_metadata): - """DEPRECATED""" - raise NotImplementedError - - # --------------------------------------------------------------------------- - # Control messages (DEPRECATED) - # --------------------------------------------------------------------------- - - async def abort_request(self, stream, ident, parent): # pragma: no cover - """abort a specific msg by id""" - self.log.warning( - "abort_request is deprecated in kernel_base. It is only part of IPython parallel" - ) - msg_ids = parent["content"].get("msg_ids", None) - if isinstance(msg_ids, str): - msg_ids = [msg_ids] - if not msg_ids: - subshell_id = parent["header"].get("subshell_id") - self._abort_queues(subshell_id) - - for mid in msg_ids: - self.aborted.add(str(mid)) - - content = dict(status="ok") - if not self.session: - return - reply_msg = self.session.send( - stream, "abort_reply", content=content, parent=parent, ident=ident - ) - self.log.debug("%s", reply_msg) - - async def clear_request(self, stream, idents, parent): # pragma: no cover - """Clear our namespace.""" - self.log.warning( - "clear_request is deprecated in kernel_base. It is only part of IPython parallel" - ) - content = self.do_clear() - if self.session: - self.session.send(stream, "clear_reply", ident=idents, parent=parent, content=content) - - def do_clear(self): - """DEPRECATED since 4.0.3""" - raise NotImplementedError - # --------------------------------------------------------------------------- # Protected interface # --------------------------------------------------------------------------- @@ -1441,8 +1420,10 @@ def raw_input(self, prompt=""): def _input_request(self, prompt, ident, parent, password=False): # Flush output before making the request. - sys.stderr.flush() - sys.stdout.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() # flush the stdin socket, to purge stale replies while True: diff --git a/ipykernel/log.py b/ipykernel/log.py index bbd4c445b..c230065e8 100644 --- a/ipykernel/log.py +++ b/ipykernel/log.py @@ -1,10 +1,11 @@ """A PUB log handler.""" + import warnings from zmq.log.handlers import PUBHandler warnings.warn( - "ipykernel.log is deprecated. It has moved to ipyparallel.engine.log", + "ipykernel.log is deprecated since ipykernel 4.3.0 (2016). It has moved to ipyparallel.engine.log", DeprecationWarning, stacklevel=2, ) diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index a6d9c7538..895a785c7 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -22,9 +22,17 @@ class ParentPollerUnix(Thread): when the parent process no longer exists. """ - def __init__(self): - """Initialize the poller.""" + def __init__(self, parent_pid=0): + """Initialize the poller. + + Parameters + ---------- + parent_handle : int, optional + If provided, the program will terminate immediately when + process parent is no longer this original parent. + """ super().__init__() + self.parent_pid = parent_pid self.daemon = True def run(self): @@ -32,9 +40,23 @@ def run(self): # We cannot use os.waitpid because it works only for child processes. from errno import EINTR + # before start, check if the passed-in parent pid is valid + original_ppid = os.getppid() + if original_ppid != self.parent_pid: + self.parent_pid = 0 + + get_logger().debug( + "%s: poll for parent change with original parent pid=%d", + type(self).__name__, + self.parent_pid, + ) + while True: try: - if os.getppid() == 1: + ppid = os.getppid() + parent_is_init = not self.parent_pid and ppid == 1 + parent_has_changed = self.parent_pid and ppid != self.parent_pid + if parent_is_init or parent_has_changed: get_logger().warning("Parent appears to have exited, shutting down.") os._exit(1) time.sleep(1.0) diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py deleted file mode 100644 index 4ffa5262e..000000000 --- a/ipykernel/pickleutil.py +++ /dev/null @@ -1,485 +0,0 @@ -"""Pickle related utilities. Perhaps this should be called 'can'.""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. -import copy -import pickle -import sys -import typing -import warnings -from types import FunctionType - -# This registers a hook when it's imported -try: - from ipyparallel.serialize import codeutil # noqa: F401 -except ImportError: - pass -from traitlets.log import get_logger -from traitlets.utils.importstring import import_item - -warnings.warn( - "ipykernel.pickleutil is deprecated. It has moved to ipyparallel.", - DeprecationWarning, - stacklevel=2, -) - -buffer = memoryview -class_type = type - -PICKLE_PROTOCOL = pickle.DEFAULT_PROTOCOL - - -def _get_cell_type(a=None): - """the type of a closure cell doesn't seem to be importable, - so just create one - """ - - def inner(): - return a - - return type(inner.__closure__[0]) # type:ignore[index] - - -cell_type = _get_cell_type() - -# ------------------------------------------------------------------------------- -# Functions -# ------------------------------------------------------------------------------- - - -def interactive(f): - """decorator for making functions appear as interactively defined. - This results in the function being linked to the user_ns as globals() - instead of the module globals(). - """ - - # build new FunctionType, so it can have the right globals - # interactive functions never have closures, that's kind of the point - if isinstance(f, FunctionType): - mainmod = __import__("__main__") - f = FunctionType( - f.__code__, - mainmod.__dict__, - f.__name__, - f.__defaults__, - ) - # associate with __main__ for uncanning - f.__module__ = "__main__" - return f - - -def use_dill(): - """use dill to expand serialization support - - adds support for object methods and closures to serialization. - """ - # import dill causes most of the magic - import dill - - # dill doesn't work with cPickle, - # tell the two relevant modules to use plain pickle - - global pickle # noqa: PLW0603 - pickle = dill - - try: - from ipykernel import serialize - except ImportError: - pass - else: - serialize.pickle = dill # type:ignore[attr-defined] - - # disable special function handling, let dill take care of it - can_map.pop(FunctionType, None) - - -def use_cloudpickle(): - """use cloudpickle to expand serialization support - - adds support for object methods and closures to serialization. - """ - import cloudpickle - - global pickle # noqa: PLW0603 - pickle = cloudpickle - - try: - from ipykernel import serialize - except ImportError: - pass - else: - serialize.pickle = cloudpickle # type:ignore[attr-defined] - - # disable special function handling, let cloudpickle take care of it - can_map.pop(FunctionType, None) - - -# ------------------------------------------------------------------------------- -# Classes -# ------------------------------------------------------------------------------- - - -class CannedObject: - """A canned object.""" - - def __init__(self, obj, keys=None, hook=None): - """can an object for safe pickling - - Parameters - ---------- - obj - The object to be canned - keys : list (optional) - list of attribute names that will be explicitly canned / uncanned - hook : callable (optional) - An optional extra callable, - which can do additional processing of the uncanned object. - - Notes - ----- - large data may be offloaded into the buffers list, - used for zero-copy transfers. - """ - self.keys = keys or [] - self.obj = copy.copy(obj) - self.hook = can(hook) - for key in keys: - setattr(self.obj, key, can(getattr(obj, key))) - - self.buffers = [] - - def get_object(self, g=None): - """Get an object.""" - if g is None: - g = {} - obj = self.obj - for key in self.keys: - setattr(obj, key, uncan(getattr(obj, key), g)) - - if self.hook: - self.hook = uncan(self.hook, g) - self.hook(obj, g) - return self.obj - - -class Reference(CannedObject): - """object for wrapping a remote reference by name.""" - - def __init__(self, name): - """Initialize the reference.""" - if not isinstance(name, str): - raise TypeError("illegal name: %r" % name) - self.name = name - self.buffers = [] - - def __repr__(self): - """Get the string repr of the reference.""" - return "" % self.name - - def get_object(self, g=None): - """Get an object in the reference.""" - if g is None: - g = {} - - return eval(self.name, g) - - -class CannedCell(CannedObject): - """Can a closure cell""" - - def __init__(self, cell): - """Initialize the canned cell.""" - self.cell_contents = can(cell.cell_contents) - - def get_object(self, g=None): - """Get an object in the cell.""" - cell_contents = uncan(self.cell_contents, g) - - def inner(): - """Inner function.""" - return cell_contents - - return inner.__closure__[0] # type:ignore[index] - - -class CannedFunction(CannedObject): - """Can a function.""" - - def __init__(self, f): - """Initialize the can""" - self._check_type(f) - self.code = f.__code__ - self.defaults: typing.Optional[list[typing.Any]] - if f.__defaults__: - self.defaults = [can(fd) for fd in f.__defaults__] - else: - self.defaults = None - - self.closure: typing.Any - closure = f.__closure__ - if closure: - self.closure = tuple(can(cell) for cell in closure) - else: - self.closure = None - - self.module = f.__module__ or "__main__" - self.__name__ = f.__name__ - self.buffers = [] - - def _check_type(self, obj): - assert isinstance(obj, FunctionType), "Not a function type" - - def get_object(self, g=None): - """Get an object out of the can.""" - # try to load function back into its module: - if not self.module.startswith("__"): - __import__(self.module) - g = sys.modules[self.module].__dict__ - - if g is None: - g = {} - defaults = tuple(uncan(cfd, g) for cfd in self.defaults) if self.defaults else None - closure = tuple(uncan(cell, g) for cell in self.closure) if self.closure else None - return FunctionType(self.code, g, self.__name__, defaults, closure) - - -class CannedClass(CannedObject): - """A canned class object.""" - - def __init__(self, cls): - """Initialize the can.""" - self._check_type(cls) - self.name = cls.__name__ - self.old_style = not isinstance(cls, type) - self._canned_dict = {} - for k, v in cls.__dict__.items(): - if k not in ("__weakref__", "__dict__"): - self._canned_dict[k] = can(v) - mro = [] if self.old_style else cls.mro() - - self.parents = [can(c) for c in mro[1:]] - self.buffers = [] - - def _check_type(self, obj): - assert isinstance(obj, class_type), "Not a class type" - - def get_object(self, g=None): - """Get an object from the can.""" - parents = tuple(uncan(p, g) for p in self.parents) - return type(self.name, parents, uncan_dict(self._canned_dict, g=g)) - - -class CannedArray(CannedObject): - """A canned numpy array.""" - - def __init__(self, obj): - """Initialize the can.""" - from numpy import ascontiguousarray - - self.shape = obj.shape - self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str - self.pickled = False - if sum(obj.shape) == 0: - self.pickled = True - elif obj.dtype == "O": - # can't handle object dtype with buffer approach - self.pickled = True - elif obj.dtype.fields and any(dt == "O" for dt, sz in obj.dtype.fields.values()): - self.pickled = True - if self.pickled: - # just pickle it - self.buffers = [pickle.dumps(obj, PICKLE_PROTOCOL)] - else: - # ensure contiguous - obj = ascontiguousarray(obj, dtype=None) - self.buffers = [buffer(obj)] - - def get_object(self, g=None): - """Get the object.""" - from numpy import frombuffer - - data = self.buffers[0] - if self.pickled: - # we just pickled it - return pickle.loads(data) - return frombuffer(data, dtype=self.dtype).reshape(self.shape) - - -class CannedBytes(CannedObject): - """A canned bytes object.""" - - @staticmethod - def wrap(buf: typing.Union[memoryview, bytes, typing.SupportsBytes]) -> bytes: - """Cast a buffer or memoryview object to bytes""" - if isinstance(buf, memoryview): - return buf.tobytes() - if not isinstance(buf, bytes): - return bytes(buf) - return buf - - def __init__(self, obj): - """Initialize the can.""" - self.buffers = [obj] - - def get_object(self, g=None): - """Get the canned object.""" - data = self.buffers[0] - return self.wrap(data) - - -class CannedBuffer(CannedBytes): - """A canned buffer.""" - - wrap = buffer # type:ignore[assignment] - - -class CannedMemoryView(CannedBytes): - """A canned memory view.""" - - wrap = memoryview # type:ignore[assignment] - - -# ------------------------------------------------------------------------------- -# Functions -# ------------------------------------------------------------------------------- - - -def _import_mapping(mapping, original=None): - """import any string-keys in a type mapping""" - log = get_logger() - log.debug("Importing canning map") - for key, _ in list(mapping.items()): - if isinstance(key, str): - try: - cls = import_item(key) - except Exception: - if original and key not in original: - # only message on user-added classes - log.error("canning class not importable: %r", key, exc_info=True) # noqa: G201 - mapping.pop(key) - else: - mapping[cls] = mapping.pop(key) - - -def istype(obj, check): - """like isinstance(obj, check), but strict - - This won't catch subclasses. - """ - if isinstance(check, tuple): - return any(type(obj) is cls for cls in check) - return type(obj) is check - - -def can(obj): - """prepare an object for pickling""" - - import_needed = False - - for cls, canner in can_map.items(): - if isinstance(cls, str): - import_needed = True - break - if istype(obj, cls): - return canner(obj) - - if import_needed: - # perform can_map imports, then try again - # this will usually only happen once - _import_mapping(can_map, _original_can_map) - return can(obj) - - return obj - - -def can_class(obj): - """Can a class object.""" - if isinstance(obj, class_type) and obj.__module__ == "__main__": - return CannedClass(obj) - return obj - - -def can_dict(obj): - """can the *values* of a dict""" - if istype(obj, dict): - newobj = {} - for k, v in obj.items(): - newobj[k] = can(v) - return newobj - return obj - - -sequence_types = (list, tuple, set) - - -def can_sequence(obj): - """can the elements of a sequence""" - if istype(obj, sequence_types): - t = type(obj) - return t([can(i) for i in obj]) - return obj - - -def uncan(obj, g=None): - """invert canning""" - - import_needed = False - for cls, uncanner in uncan_map.items(): - if isinstance(cls, str): - import_needed = True - break - if isinstance(obj, cls): - return uncanner(obj, g) - - if import_needed: - # perform uncan_map imports, then try again - # this will usually only happen once - _import_mapping(uncan_map, _original_uncan_map) - return uncan(obj, g) - - return obj - - -def uncan_dict(obj, g=None): - """Uncan a dict object.""" - if istype(obj, dict): - newobj = {} - for k, v in obj.items(): - newobj[k] = uncan(v, g) - return newobj - return obj - - -def uncan_sequence(obj, g=None): - """Uncan a sequence.""" - if istype(obj, sequence_types): - t = type(obj) - return t([uncan(i, g) for i in obj]) - return obj - - -# ------------------------------------------------------------------------------- -# API dictionaries -# ------------------------------------------------------------------------------- - -# These dicts can be extended for custom serialization of new objects - -can_map = { - "numpy.ndarray": CannedArray, - FunctionType: CannedFunction, - bytes: CannedBytes, - memoryview: CannedMemoryView, - cell_type: CannedCell, - class_type: can_class, -} -if buffer is not memoryview: - can_map[buffer] = CannedBuffer - -uncan_map: dict[type, typing.Any] = { - CannedObject: lambda obj, g: obj.get_object(g), - dict: uncan_dict, -} - -# for use in _import_mapping: -_original_can_map = can_map.copy() -_original_uncan_map = uncan_map.copy() diff --git a/ipykernel/serialize.py b/ipykernel/serialize.py deleted file mode 100644 index 22ba5396e..000000000 --- a/ipykernel/serialize.py +++ /dev/null @@ -1,203 +0,0 @@ -"""serialization utilities for apply messages""" - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. - -import pickle -import warnings -from itertools import chain - -try: - # available since ipyparallel 5.0.0 - from ipyparallel.serialize.canning import ( - CannedObject, - can, - can_sequence, - istype, - sequence_types, - uncan, - uncan_sequence, - ) - from ipyparallel.serialize.serialize import PICKLE_PROTOCOL -except ImportError: - # Deprecated since ipykernel 4.3.0 - from ipykernel.pickleutil import ( - PICKLE_PROTOCOL, - CannedObject, - can, - can_sequence, - istype, - sequence_types, - uncan, - uncan_sequence, - ) - -from jupyter_client.session import MAX_BYTES, MAX_ITEMS - -warnings.warn( - "ipykernel.serialize is deprecated. It has moved to ipyparallel.serialize", - DeprecationWarning, - stacklevel=2, -) - -# ----------------------------------------------------------------------------- -# Serialization Functions -# ----------------------------------------------------------------------------- - - -def _extract_buffers(obj, threshold=MAX_BYTES): - """extract buffers larger than a certain threshold""" - buffers = [] - if isinstance(obj, CannedObject) and obj.buffers: - for i, buf in enumerate(obj.buffers): - if len(buf) > threshold: - # buffer larger than threshold, prevent pickling - obj.buffers[i] = None - buffers.append(buf) - # buffer too small for separate send, coerce to bytes - # because pickling buffer objects just results in broken pointers - elif isinstance(buf, memoryview): - obj.buffers[i] = buf.tobytes() - return buffers - - -def _restore_buffers(obj, buffers): - """restore buffers extracted by""" - if isinstance(obj, CannedObject) and obj.buffers: - for i, buf in enumerate(obj.buffers): - if buf is None: - obj.buffers[i] = buffers.pop(0) - - -def serialize_object(obj, buffer_threshold=MAX_BYTES, item_threshold=MAX_ITEMS): - """Serialize an object into a list of sendable buffers. - - Parameters - ---------- - obj : object - The object to be serialized - buffer_threshold : int - The threshold (in bytes) for pulling out data buffers - to avoid pickling them. - item_threshold : int - The maximum number of items over which canning will iterate. - Containers (lists, dicts) larger than this will be pickled without - introspection. - - Returns - ------- - [bufs] : list of buffers representing the serialized object. - """ - buffers = [] - if istype(obj, sequence_types) and len(obj) < item_threshold: - cobj = can_sequence(obj) - for c in cobj: - buffers.extend(_extract_buffers(c, buffer_threshold)) - elif istype(obj, dict) and len(obj) < item_threshold: - cobj = {} - for k in sorted(obj): - c = can(obj[k]) - buffers.extend(_extract_buffers(c, buffer_threshold)) - cobj[k] = c - else: - cobj = can(obj) - buffers.extend(_extract_buffers(cobj, buffer_threshold)) - - buffers.insert(0, pickle.dumps(cobj, PICKLE_PROTOCOL)) - return buffers - - -def deserialize_object(buffers, g=None): - """reconstruct an object serialized by serialize_object from data buffers. - - Parameters - ---------- - buffers : list of buffers/bytes - g : globals to be used when uncanning - - Returns - ------- - (newobj, bufs) : unpacked object, and the list of remaining unused buffers. - """ - bufs = list(buffers) - pobj = bufs.pop(0) - canned = pickle.loads(pobj) - if istype(canned, sequence_types) and len(canned) < MAX_ITEMS: - for c in canned: - _restore_buffers(c, bufs) - newobj = uncan_sequence(canned, g) - elif istype(canned, dict) and len(canned) < MAX_ITEMS: - newobj = {} - for k in sorted(canned): - c = canned[k] - _restore_buffers(c, bufs) - newobj[k] = uncan(c, g) - else: - _restore_buffers(canned, bufs) - newobj = uncan(canned, g) - - return newobj, bufs - - -def pack_apply_message(f, args, kwargs, buffer_threshold=MAX_BYTES, item_threshold=MAX_ITEMS): - """pack up a function, args, and kwargs to be sent over the wire - - Each element of args/kwargs will be canned for special treatment, - but inspection will not go any deeper than that. - - Any object whose data is larger than `threshold` will not have their data copied - (only numpy arrays and bytes/buffers support zero-copy) - - Message will be a list of bytes/buffers of the format: - - [ cf, pinfo, , ] - - With length at least two + len(args) + len(kwargs) - """ - - arg_bufs = list( - chain.from_iterable(serialize_object(arg, buffer_threshold, item_threshold) for arg in args) - ) - - kw_keys = sorted(kwargs.keys()) - kwarg_bufs = list( - chain.from_iterable( - serialize_object(kwargs[key], buffer_threshold, item_threshold) for key in kw_keys - ) - ) - - info = dict(nargs=len(args), narg_bufs=len(arg_bufs), kw_keys=kw_keys) - - msg = [pickle.dumps(can(f), PICKLE_PROTOCOL)] - msg.append(pickle.dumps(info, PICKLE_PROTOCOL)) - msg.extend(arg_bufs) - msg.extend(kwarg_bufs) - - return msg - - -def unpack_apply_message(bufs, g=None, copy=True): - """unpack f,args,kwargs from buffers packed by pack_apply_message() - Returns: original f,args,kwargs""" - bufs = list(bufs) # allow us to pop - assert len(bufs) >= 2, "not enough buffers!" - pf = bufs.pop(0) - f = uncan(pickle.loads(pf), g) - pinfo = bufs.pop(0) - info = pickle.loads(pinfo) - arg_bufs, kwarg_bufs = bufs[: info["narg_bufs"]], bufs[info["narg_bufs"] :] - - args_list = [] - for _ in range(info["nargs"]): - arg, arg_bufs = deserialize_object(arg_bufs, g) - args_list.append(arg) - args = tuple(args_list) - assert not arg_bufs, "Shouldn't be any arg bufs left over" - - kwargs = {} - for key in info["kw_keys"]: - kwarg, kwarg_bufs = deserialize_object(kwarg_bufs, g) - kwargs[key] = kwarg - assert not kwarg_bufs, "Shouldn't be any kwarg bufs left over" - - return f, args, kwargs diff --git a/ipykernel/shellchannel.py b/ipykernel/shellchannel.py index 77a02f11a..12102e870 100644 --- a/ipykernel/shellchannel.py +++ b/ipykernel/shellchannel.py @@ -1,4 +1,5 @@ """A thread for a shell channel.""" + from __future__ import annotations from typing import Any diff --git a/ipykernel/subshell_manager.py b/ipykernel/subshell_manager.py index be9dd758e..21f0a5af8 100644 --- a/ipykernel/subshell_manager.py +++ b/ipykernel/subshell_manager.py @@ -1,4 +1,5 @@ """Manager of subshells in a kernel.""" + from __future__ import annotations import json diff --git a/ipykernel/thread.py b/ipykernel/thread.py index 13eb781b7..3e1d4f07a 100644 --- a/ipykernel/thread.py +++ b/ipykernel/thread.py @@ -1,4 +1,5 @@ """Base class for threads.""" + from threading import Thread from tornado.ioloop import IOLoop diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index a641feba8..6fb44107b 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -1,4 +1,5 @@ """A trio loop runner.""" + import builtins import logging import signal diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 60682379d..4883b376b 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -17,10 +17,11 @@ import os import sys import threading +import typing import warnings from pathlib import Path -from IPython.core import page, payloadpage +from IPython.core import page from IPython.core.autocall import ZMQExitAutocall from IPython.core.displaypub import DisplayPublisher from IPython.core.error import UsageError @@ -78,12 +79,16 @@ def _hooks(self): self._thread_local.hooks = [] return self._thread_local.hooks - def publish( + # Feb: 2025 IPython has a deprecated, `source` parameter, marked for removal that + # triggers typing errors. + def publish( # type:ignore[override] self, data, metadata=None, + *, transient=None, update=False, + **kwargs, ): """Publish a display-data message @@ -125,7 +130,7 @@ def publish( for hook in self._hooks: msg = hook(msg) if msg is None: - return # type:ignore[unreachable] + return self.session.send( self.pub_socket, @@ -153,7 +158,7 @@ def clear_output(self, wait=False): for hook in self._hooks: msg = hook(msg) if msg is None: - return # type:ignore[unreachable] + return self.session.send( self.pub_socket, @@ -398,14 +403,6 @@ def qtconsole(self, arg_s): Useful for connecting a qtconsole to running notebooks, for better debugging. """ - - # %qtconsole should imply bind_kernel for engines: - # FIXME: move to ipyparallel Kernel subclass - if "ipyparallel" in sys.modules: - from ipyparallel import bind_kernel - - bind_kernel() - try: connect_qtconsole(argv=arg_split(arg_s, os.name == "posix")) except Exception as e: @@ -476,9 +473,21 @@ def subshell(self, arg_s): class ZMQInteractiveShell(InteractiveShell): """A subclass of InteractiveShell for ZMQ.""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + # tqdm has an incorrect detection of ZMQInteractiveShell when launch via + # a scheduler that bypass IPKernelApp Think of JupyterHub cluster + # spawners and co. as of end of Feb 2025, the maintainer has been + # unresponsive for 5 months, to our fix, so we implement a workaround. I + # don't like it but we have few other choices. + # See https://github.com/tqdm/tqdm/pull/1628 + if "IPKernelApp" not in self.config: + self.config.IPKernelApp.tqdm = "dummy value for https://github.com/tqdm/tqdm/pull/1628" + displayhook_class = Type(ZMQShellDisplayHook) display_pub_class = Type(ZMQDisplayPublisher) - data_pub_class = Any() # type:ignore[assignment] + data_pub_class = Any() kernel = Any() parent_header = Any() @@ -516,7 +525,7 @@ def _update_exit_now(self, change): # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no # interactive input being read; we provide event loop support in ipkernel - def enable_gui(self, gui): + def enable_gui(self, gui: typing.Any = None) -> None: """Enable a given guil.""" from .eventloops import enable_gui as real_enable_gui @@ -541,10 +550,38 @@ def init_environment(self): env["PAGER"] = "cat" env["GIT_PAGER"] = "cat" + def payloadpage_page(self, strg, start=0, screen_lines=0, pager_cmd=None): + """Print a string, piping through a pager. + + This version ignores the screen_lines and pager_cmd arguments and uses + IPython's payload system instead. + + Parameters + ---------- + strg : str or mime-dict + Text to page, or a mime-type keyed dict of already formatted data. + start : int + Starting line at which to place the display. + """ + + # Some routines may auto-compute start offsets incorrectly and pass a + # negative value. Offset to 0 for robustness. + start = max(0, start) + + data = strg if isinstance(strg, dict) else {"text/plain": strg} + + payload = dict( + source="page", + data=data, + start=start, + ) + assert self.payload_manager is not None + self.payload_manager.write_payload(payload) + def init_hooks(self): """Initialize hooks.""" super().init_hooks() - self.set_hook("show_in_pager", page.as_hook(payloadpage.page), 99) + self.set_hook("show_in_pager", page.as_hook(self.payloadpage_page), 99) def init_data_pub(self): """Delay datapub init until request, for deprecation warnings""" @@ -558,7 +595,7 @@ def data_pub(self): stacklevel=2, ) - self._data_pub = self.data_pub_class(parent=self) # type:ignore[has-type] + self._data_pub = self.data_pub_class(parent=self) self._data_pub.session = self.display_pub.session # type:ignore[attr-defined] self._data_pub.pub_socket = self.display_pub.pub_socket # type:ignore[attr-defined] return self._data_pub @@ -628,14 +665,10 @@ def set_parent(self, parent): self.display_pub.set_parent(parent) # type:ignore[attr-defined] if hasattr(self, "_data_pub"): self.data_pub.set_parent(parent) - try: - sys.stdout.set_parent(parent) # type:ignore[attr-defined] - except AttributeError: - pass - try: - sys.stderr.set_parent(parent) # type:ignore[attr-defined] - except AttributeError: - pass + if hasattr(sys.stdout, "set_parent"): + sys.stdout.set_parent(parent) + if hasattr(sys.stderr, "set_parent"): + sys.stderr.set_parent(parent) def get_parent(self): """Get the parent header.""" diff --git a/pyproject.toml b/pyproject.toml index 39246b492..e6404d973 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,7 +38,6 @@ dependencies = [ [project.urls] Homepage = "https://ipython.org" Documentation = "https://ipykernel.readthedocs.io" -Funding = "https://numfocus.org/donate" Source = "https://github.com/ipython/ipykernel" Tracker = "https://github.com/ipython/ipykernel/issues" @@ -168,7 +167,6 @@ filterwarnings= [ # Ignore our own warnings "ignore:The `stream` parameter of `getpass.getpass` will have no effect:UserWarning", - "ignore:has moved to ipyparallel:DeprecationWarning", # IPython warnings "ignore: `Completer.complete` is pending deprecation since IPython 6.0 and will be replaced by `Completer.completions`:PendingDeprecationWarning", @@ -176,19 +174,14 @@ filterwarnings= [ "ignore: backend2gui is deprecated since IPython 8.24, backends are managed in matplotlib and can be externally registered.:DeprecationWarning", # Ignore jupyter_client warnings - "ignore:unclosed = (3, 8)), + sys.platform == "win32" or (sys.platform == "darwin"), reason="subprocess prints fail on Windows and MacOS Python 3.8+", ) def test_subprocess_print(): @@ -267,7 +269,7 @@ def test_subprocess_noprint(): @flaky(max_runs=3) @pytest.mark.skipif( - sys.platform == "win32" or (sys.platform == "darwin" and sys.version_info >= (3, 8)), + (sys.platform == "win32") or (sys.platform == "darwin"), reason="subprocess prints fail on Windows and MacOS Python 3.8+", ) def test_subprocess_error(): diff --git a/tests/test_kernel_direct.py b/tests/test_kernel_direct.py index dfb8a70fe..c799052be 100644 --- a/tests/test_kernel_direct.py +++ b/tests/test_kernel_direct.py @@ -130,8 +130,7 @@ def __init__(self, bytes): def test_should_handle(kernel): msg = kernel.session.msg("debug_request", {}) - kernel.aborted.add(msg["header"]["msg_id"]) - assert not kernel.should_handle(kernel.control_stream, msg, []) + assert kernel.should_handle(kernel.control_stream, msg, []) is True async def test_dispatch_shell(kernel): diff --git a/tests/test_message_spec.py b/tests/test_message_spec.py index 0459b342b..b35861cb3 100644 --- a/tests/test_message_spec.py +++ b/tests/test_message_spec.py @@ -33,7 +33,6 @@ def _setup_env(): class Reference(HasTraits): - """ Base class for message spec specification testing. diff --git a/tests/test_parentpoller.py b/tests/test_parentpoller.py index 97cd80440..716c9e8f5 100644 --- a/tests/test_parentpoller.py +++ b/tests/test_parentpoller.py @@ -9,7 +9,7 @@ @pytest.mark.skipif(os.name == "nt", reason="only works on posix") -def test_parent_poller_unix(): +def test_parent_poller_unix_to_pid1(): poller = ParentPollerUnix() with mock.patch("os.getppid", lambda: 1): # noqa: PT008 @@ -27,6 +27,22 @@ def mock_getppid(): poller.run() +@pytest.mark.skipif(os.name == "nt", reason="only works on posix") +def test_parent_poller_unix_reparent_not_pid1(): + parent_pid = 221 + parent_pids = iter([parent_pid, parent_pid - 1]) + + poller = ParentPollerUnix(parent_pid=parent_pid) + + with mock.patch("os.getppid", lambda: next(parent_pids)): # noqa: PT008 + + def exit_mock(*args): + sys.exit(1) + + with mock.patch("os._exit", exit_mock), pytest.raises(SystemExit): + poller.run() + + @pytest.mark.skipif(os.name != "nt", reason="only works on windows") def test_parent_poller_windows(): poller = ParentPollerWindows(interrupt_handle=1) diff --git a/tests/test_pickleutil.py b/tests/test_pickleutil.py deleted file mode 100644 index c48eadf77..000000000 --- a/tests/test_pickleutil.py +++ /dev/null @@ -1,78 +0,0 @@ -import pickle -import warnings - -with warnings.catch_warnings(): - warnings.simplefilter("ignore") - from ipykernel.pickleutil import can, uncan - - -def interactive(f): - f.__module__ = "__main__" - return f - - -def dumps(obj): - return pickle.dumps(can(obj)) - - -def loads(obj): - return uncan(pickle.loads(obj)) - - -def test_no_closure(): - @interactive - def foo(): - a = 5 - return a - - pfoo = dumps(foo) - bar = loads(pfoo) - assert foo() == bar() - - -def test_generator_closure(): - # this only creates a closure on Python 3 - @interactive - def foo(): - i = "i" - r = [i for j in (1, 2)] - return r - - pfoo = dumps(foo) - bar = loads(pfoo) - assert foo() == bar() - - -def test_nested_closure(): - @interactive - def foo(): - i = "i" - - def g(): - return i - - return g() - - pfoo = dumps(foo) - bar = loads(pfoo) - assert foo() == bar() - - -def test_closure(): - i = "i" - - @interactive - def foo(): - return i - - pfoo = dumps(foo) - bar = loads(pfoo) - assert foo() == bar() - - -def test_uncan_bytes_buffer(): - data = b"data" - canned = can(data) - canned.buffers = [memoryview(buf) for buf in canned.buffers] - out = uncan(canned) - assert out == data diff --git a/tests/test_zmq_shell.py b/tests/test_zmq_shell.py index dfd22dec0..8a8fe042b 100644 --- a/tests/test_zmq_shell.py +++ b/tests/test_zmq_shell.py @@ -1,4 +1,4 @@ -""" Tests for zmq shell / display publisher. """ +"""Tests for zmq shell / display publisher.""" # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. From 84211d7227fd5faa9d6e485641c1a4df6e7df2b4 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Tue, 15 Jul 2025 09:12:21 +0100 Subject: [PATCH 1080/1195] Add subshell docstrings (#1405) --- ipykernel/kernelbase.py | 14 +++++++++++++- ipykernel/socket_pair.py | 10 ++++++++++ ipykernel/subshell.py | 6 +++++- ipykernel/subshell_manager.py | 16 +++++++++++++++- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index da16f2a55..ec3e7dfe0 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -300,7 +300,7 @@ def __init__(self, **kwargs): ) async def dispatch_control(self, msg): - # Ensure only one control message is processed at a time + """Dispatch a control request, ensuring only one message is processed at a time.""" async with asyncio.Lock(): await self.process_control(msg) @@ -1225,6 +1225,10 @@ async def do_debug_request(self, msg): raise NotImplementedError async def create_subshell_request(self, socket, ident, parent) -> None: + """Handle a create subshell request. + + .. versionadded:: 7 + """ if not self.session: return if not self._supports_kernel_subshells: @@ -1241,6 +1245,10 @@ async def create_subshell_request(self, socket, ident, parent) -> None: self.session.send(socket, "create_subshell_reply", reply, parent, ident) async def delete_subshell_request(self, socket, ident, parent) -> None: + """Handle a delete subshell request. + + .. versionadded:: 7 + """ if not self.session: return if not self._supports_kernel_subshells: @@ -1265,6 +1273,10 @@ async def delete_subshell_request(self, socket, ident, parent) -> None: self.session.send(socket, "delete_subshell_reply", reply, parent, ident) async def list_subshell_request(self, socket, ident, parent) -> None: + """Handle a list subshell request. + + .. versionadded:: 7 + """ if not self.session: return if not self._supports_kernel_subshells: diff --git a/ipykernel/socket_pair.py b/ipykernel/socket_pair.py index 9e6b42234..e2669b8c0 100644 --- a/ipykernel/socket_pair.py +++ b/ipykernel/socket_pair.py @@ -1,3 +1,5 @@ +"""Pair of ZMQ inproc sockets used for communication between threads.""" + from __future__ import annotations from typing import Any @@ -12,6 +14,8 @@ class SocketPair: One of the threads is always the shell_channel_thread, the other may be the control thread, main thread or a subshell thread. + + .. versionadded:: 7 """ from_socket: zmq.Socket[Any] @@ -21,6 +25,7 @@ class SocketPair: on_recv_copy: bool def __init__(self, context: zmq.Context[Any], name: str): + """Initialize the inproc socker pair.""" self.from_socket = context.socket(zmq.PAIR) self.to_socket = context.socket(zmq.PAIR) address = self._address(name) @@ -28,6 +33,7 @@ def __init__(self, context: zmq.Context[Any], name: str): self.to_socket.connect(address) # Or do I need to do this in another thread? def close(self): + """Close the inproc socker pair.""" self.from_socket.close() if self.to_stream is not None: @@ -35,6 +41,7 @@ def close(self): self.to_socket.close() def on_recv(self, io_loop: IOLoop, on_recv_callback, copy: bool = False): + """Set the callback used when a message is received on the to stream.""" # io_loop is that of the 'to' thread. self.on_recv_callback = on_recv_callback self.on_recv_copy = copy @@ -43,12 +50,15 @@ def on_recv(self, io_loop: IOLoop, on_recv_callback, copy: bool = False): self.resume_on_recv() def pause_on_recv(self): + """Pause receiving on the to stream.""" if self.to_stream is not None: self.to_stream.stop_on_recv() def resume_on_recv(self): + """Resume receiving on the to stream.""" if self.to_stream is not None and not self.to_stream.closed(): self.to_stream.on_recv(self.on_recv_callback, copy=self.on_recv_copy) def _address(self, name) -> str: + """Return the address used for this inproc socket pair.""" return f"inproc://subshell{name}" diff --git a/ipykernel/subshell.py b/ipykernel/subshell.py index 34c0778bd..911a9521c 100644 --- a/ipykernel/subshell.py +++ b/ipykernel/subshell.py @@ -9,7 +9,10 @@ class SubshellThread(BaseThread): - """A thread for a subshell.""" + """A thread for a subshell. + + .. versionadded:: 7 + """ def __init__( self, @@ -27,6 +30,7 @@ def __init__( self.aborting = False def run(self) -> None: + """Run the thread.""" try: super().run() finally: diff --git a/ipykernel/subshell_manager.py b/ipykernel/subshell_manager.py index 21f0a5af8..24d683523 100644 --- a/ipykernel/subshell_manager.py +++ b/ipykernel/subshell_manager.py @@ -29,6 +29,8 @@ class SubshellManager: Sending reply messages via the shell_socket is wrapped by another lock to protect against multiple subshells attempting to send at the same time. + + .. versionadded:: 7 """ def __init__( @@ -37,6 +39,7 @@ def __init__( shell_channel_io_loop: IOLoop, shell_socket: zmq.Socket[t.Any], ): + """Initialize the subshell manager.""" assert current_thread() == main_thread() self._context: zmq.Context[t.Any] = context @@ -80,22 +83,30 @@ def close(self) -> None: self._shell_channel_to_main.close() def get_shell_channel_to_subshell_pair(self, subshell_id: str | None) -> SocketPair: + """Return the inproc socket pair used to send messages from the shell channel + to a particular subshell or main shell.""" if subshell_id is None: return self._shell_channel_to_main with self._lock_cache: return self._cache[subshell_id].shell_channel_to_subshell def get_subshell_to_shell_channel_socket(self, subshell_id: str | None) -> zmq.Socket[t.Any]: + """Return the socket used by a particular subshell or main shell to send + messages to the shell channel. + """ if subshell_id is None: return self._main_to_shell_channel.from_socket with self._lock_cache: return self._cache[subshell_id].subshell_to_shell_channel.from_socket def get_shell_channel_to_subshell_socket(self, subshell_id: str | None) -> zmq.Socket[t.Any]: + """Return the socket used by the shell channel to send messages to a particular + subshell or main shell. + """ return self.get_shell_channel_to_subshell_pair(subshell_id).from_socket def get_subshell_aborting(self, subshell_id: str) -> bool: - """Get the aborting flag of the specified subshell.""" + """Get the boolean aborting flag of the specified subshell.""" return self._cache[subshell_id].aborting def list_subshell(self) -> list[str]: @@ -107,6 +118,9 @@ def list_subshell(self) -> list[str]: return list(self._cache) def set_on_recv_callback(self, on_recv_callback): + """Set the callback used by the main shell and all subshells to receive + messages sent from the shell channel thread. + """ assert current_thread() == main_thread() self._on_recv_callback = on_recv_callback self._shell_channel_to_main.on_recv(IOLoop.current(), partial(self._on_recv_callback, None)) From 39a9d368ca1987ffe4f67086f86d80a46f9517bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 10:55:47 +0100 Subject: [PATCH 1081/1195] Bump scientific-python/upload-nightly-action from 0.6.1 to 0.6.2 in the actions group (#1404) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 499f43562..31110223a 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -27,7 +27,7 @@ jobs: python -m pip install build python -m build - name: Upload wheel - uses: scientific-python/upload-nightly-action@82396a2ed4269ba06c6b2988bb4fd568ef3c3d6b # 0.6.1 + uses: scientific-python/upload-nightly-action@b36e8c0c10dbcfd2e05bf95f17ef8c14fd708dbf # 0.6.2 with: artifacts_path: dist anaconda_nightly_upload_token: ${{secrets.UPLOAD_TOKEN}} From 700b9b252eb1688a49211928192e89f4ab420ac7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 22 Jul 2025 12:52:00 +0100 Subject: [PATCH 1082/1195] chore: update pre-commit hooks (#1409) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ian Thomas --- .pre-commit-config.yaml | 6 +++--- pyproject.toml | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6f852f711..3acbeb4a9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -40,7 +40,7 @@ repos: types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.16.1" + rev: "v1.17.0" hooks: - id: mypy files: ipykernel @@ -74,7 +74,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.11.4 + rev: v0.12.4 hooks: - id: ruff types_or: [python, jupyter] @@ -83,7 +83,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2025.01.22" + rev: "2025.05.02" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/pyproject.toml b/pyproject.toml index e6404d973..73007bc0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -256,6 +256,7 @@ ignore = [ # Unused method argument: "ARG002", # `open()` should be replaced by `Path.open()` + "PLC0415", # `import` should be at the top-level of a file "PTH123", "UP007", # use `X | Y` for type annotations, this does not works for dynamic getting type hints on older python "UP031", # Use format specifiers instead of percent format From 4c218005f790057f44a2af0be29d33018572490a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20G=C3=B3rny?= Date: Thu, 24 Jul 2025 09:13:44 +0200 Subject: [PATCH 1083/1195] Replace `@flaky.flaky` decorate with pytest fixture (#1411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- tests/inprocess/test_kernelmanager.py | 3 +-- tests/test_embed_kernel.py | 7 +++---- tests/test_kernel.py | 7 +++---- tests/test_start_kernel.py | 5 ++--- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/tests/inprocess/test_kernelmanager.py b/tests/inprocess/test_kernelmanager.py index d3ee99913..3afc6b8e6 100644 --- a/tests/inprocess/test_kernelmanager.py +++ b/tests/inprocess/test_kernelmanager.py @@ -4,7 +4,6 @@ import unittest import pytest -from flaky import flaky from ipykernel.inprocess.manager import InProcessKernelManager @@ -21,7 +20,7 @@ def tearDown(self): if self.km.has_kernel: self.km.shutdown_kernel() - @flaky + @pytest.mark.flaky def test_interface(self): """Does the in-process kernel manager implement the basic KM interface?""" km = self.km diff --git a/tests/test_embed_kernel.py b/tests/test_embed_kernel.py index ff97edfa5..8e70f8b4c 100644 --- a/tests/test_embed_kernel.py +++ b/tests/test_embed_kernel.py @@ -12,7 +12,6 @@ from subprocess import PIPE, Popen import pytest -from flaky import flaky from jupyter_client.blocking.client import BlockingKernelClient from jupyter_core import paths @@ -91,7 +90,7 @@ def connection_file_ready(connection_file): fid.close() -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_embed_kernel_basic(): """IPython.embed_kernel() is basically functional""" cmd = "\n".join( @@ -127,7 +126,7 @@ def test_embed_kernel_basic(): assert "10" in text -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_embed_kernel_namespace(): """IPython.embed_kernel() inherits calling namespace""" cmd = "\n".join( @@ -166,7 +165,7 @@ def test_embed_kernel_namespace(): assert not content["found"] -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_embed_kernel_reentrant(): """IPython.embed_kernel() can be called multiple times""" cmd = "\n".join( diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 6d7864c32..685748c42 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -17,7 +17,6 @@ import IPython import psutil import pytest -from flaky import flaky from IPython.paths import locate_profile from .utils import ( @@ -212,7 +211,7 @@ def test_sys_path_profile_dir(): assert "" in sys_path -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) @pytest.mark.skipif( sys.platform == "win32" or (sys.platform == "darwin"), reason="subprocess prints fail on Windows and MacOS Python 3.8+", @@ -244,7 +243,7 @@ def test_subprocess_print(): _check_master(kc, expected=True, stream="stderr") -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_subprocess_noprint(): """mp.Process without print doesn't trigger iostream mp_mode""" with kernel() as kc: @@ -267,7 +266,7 @@ def test_subprocess_noprint(): _check_master(kc, expected=True, stream="stderr") -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) @pytest.mark.skipif( (sys.platform == "win32") or (sys.platform == "darwin"), reason="subprocess prints fail on Windows and MacOS Python 3.8+", diff --git a/tests/test_start_kernel.py b/tests/test_start_kernel.py index 71f4bdc0a..b9276e33b 100644 --- a/tests/test_start_kernel.py +++ b/tests/test_start_kernel.py @@ -2,7 +2,6 @@ from textwrap import dedent import pytest -from flaky import flaky from .test_embed_kernel import setup_kernel @@ -12,7 +11,7 @@ pytest.skip("skipping tests on windows", allow_module_level=True) -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_ipython_start_kernel_userns(): import IPython @@ -51,7 +50,7 @@ def test_ipython_start_kernel_userns(): assert EXPECTED in text -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_ipython_start_kernel_no_userns(): # Issue #4188 - user_ns should be passed to shell as None, not {} cmd = dedent( From dc4d9582ee01e21b9145990d48705b6a5cc9ba2c Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 4 Aug 2025 10:14:40 +0100 Subject: [PATCH 1084/1195] Remove links in changelog to github milestones that no longer exist (#1415) --- CHANGELOG.md | 44 -------------------------------------------- 1 file changed, 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b13da6828..886cc9bcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1669,8 +1669,6 @@ failures in nbconvert. 5.1.0 fixes some important regressions in 5.0, especially on Windows. -[5.1.0 on GitHub](https://github.com/ipython/ipykernel/milestones/5.1) - - Fix message-ordering bug that could result in out-of-order executions, especially on Windows [#356](https://github.com/ipython/ipykernel/pull/356) - Fix classifiers to indicate dropped Python 2 support @@ -1683,8 +1681,6 @@ failures in nbconvert. ### 5.0.0 -[5.0.0 on GitHub](https://github.com/ipython/ipykernel/milestones/5.0) - - Drop support for Python 2. `ipykernel` 5.0 requires Python >= 3.4 - Add support for IPython's asynchronous code execution [#323](https://github.com/ipython/ipykernel/pull/323) @@ -1692,8 +1688,6 @@ failures in nbconvert. ## 4.10 -[4.10 on GitHub](https://github.com/ipython/ipykernel/milestones/4.10) - - Fix compatibility with IPython 7.0 [#348](https://github.com/ipython/ipykernel/pull/348) - Fix compatibility in cases where sys.stdout can be None [#344](https://github.com/ipython/ipykernel/pull/344) @@ -1702,8 +1696,6 @@ failures in nbconvert. ### 4.9.0 -[4.9.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.9) - - Python 3.3 is no longer supported [#336](https://github.com/ipython/ipykernel/pull/336) - Flush stdout/stderr in KernelApp before replacing [#314](https://github.com/ipython/ipykernel/pull/314) @@ -1717,15 +1709,11 @@ failures in nbconvert. ### 4.8.2 -[4.8.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8.2) - - Fix compatibility issue with qt eventloop and pyzmq 17 [#307](https://github.com/ipython/ipykernel/pull/307). ### 4.8.1 -[4.8.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8.1) - - set zmq.ROUTER_HANDOVER socket option when available to workaround libzmq reconnect bug [#300](https://github.com/ipython/ipykernel/pull/300). - Fix sdists including absolute paths for kernelspec files, which @@ -1734,8 +1722,6 @@ failures in nbconvert. ### 4.8.0 -[4.8.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8) - - Cleanly shutdown integrated event loops when shutting down the kernel. [#290](https://github.com/ipython/ipykernel/pull/290) - `%gui qt` now uses Qt 5 by default rather than Qt 4, following a @@ -1747,8 +1733,6 @@ failures in nbconvert. ### 4.7.0 -[4.7.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.7) - - Add event loop integration for `asyncio`. - Use the new IPython completer API. - Add support for displaying GIF images (mimetype `image/gif`). @@ -1762,8 +1746,6 @@ failures in nbconvert. ### 4.6.1 -[4.6.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.6.1) - - Fix eventloop-integration bug preventing Qt windows/widgets from displaying with ipykernel 4.6.0 and IPython ≥ 5.2. - Avoid deprecation warnings about naive datetimes when working with @@ -1771,8 +1753,6 @@ failures in nbconvert. ### 4.6.0 -[4.6.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.6) - - Add to API `DisplayPublisher.publish` two new fully backward-compatible keyword-args: @@ -1815,15 +1795,11 @@ failures in nbconvert. ### 4.5.2 -[4.5.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5.2) - - Fix bug when instantiating Comms outside of the IPython kernel (introduced in 4.5.1). ### 4.5.1 -[4.5.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5.1) - - Add missing `stream` parameter to overridden `getpass` - Remove locks from iopub thread, which could cause deadlocks during @@ -1834,8 +1810,6 @@ failures in nbconvert. ### 4.5.0 -[4.5 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5) - - Use figure.dpi instead of savefig.dpi to set DPI for inline figures - Support ipympl matplotlib backend (requires IPython update as well to fully work) @@ -1847,15 +1821,11 @@ failures in nbconvert. ### 4.4.1 -[4.4.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.4.1) - - Fix circular import of matplotlib on Python 2 caused by the inline backend changes in 4.4.0. ### 4.4.0 -[4.4.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.4) - - Use [MPLBACKEND](http://matplotlib.org/devel/coding_guide.html?highlight=mplbackend#developing-a-new-backend) environment variable to tell matplotlib >= 1.5 use use the inline @@ -1889,8 +1859,6 @@ failures in nbconvert. ### 4.3.0 -[4.3.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.3) - - Publish all IO in a thread, via `IOPubThread`. This solves the problem of requiring `sys.stdout.flush` to be called in the notebook to produce output promptly during long-running cells. @@ -1913,22 +1881,16 @@ failures in nbconvert. ### 4.2.2 -[4.2.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2.2) - - Don't show interactive debugging info when kernel crashes - Fix handling of numerical types in json_clean - Testing fixes for output capturing ### 4.2.1 -[4.2.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2.1) - - Fix default display name back to "Python X" instead of "pythonX" ### 4.2.0 -[4.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2) - - Support sending a full message in initial opening of comms (metadata, buffers were not previously allowed) - When using `ipython kernel install --name` to install the IPython @@ -1938,15 +1900,11 @@ failures in nbconvert. ### 4.1.1 -[4.1.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.1.1) - - Fix missing `ipykernel.__version__` on Python 2. - Fix missing `target_name` when opening comms from the frontend. ### 4.1.0 -[4.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.1) - - add `ipython kernel install` entrypoint for installing the IPython kernelspec - provisional implementation of `comm_info` request/reply for msgspec @@ -1954,6 +1912,4 @@ failures in nbconvert. ## 4.0 -[4.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.0) - 4.0 is the first release of ipykernel as a standalone package. From 3c21642a6f7451b818f82a875efe5b9540458e4a Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 4 Aug 2025 17:19:03 +0100 Subject: [PATCH 1085/1195] Forward port from 6.x: Correct use of asyncio.Lock to process a single control message at a time (#1418) --- ipykernel/kernelbase.py | 18 ++++++- tests/test_debugger.py | 111 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 122 insertions(+), 7 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index ec3e7dfe0..d54cc42d5 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -251,6 +251,9 @@ def _parent_header(self): # execution count we store in the shell. execution_count = 0 + # Asyncio lock to ensure only one control queue message is processed at a time. + _control_lock = Instance(asyncio.Lock) + msg_types = [ "execute_request", "complete_request", @@ -301,7 +304,8 @@ def __init__(self, **kwargs): async def dispatch_control(self, msg): """Dispatch a control request, ensuring only one message is processed at a time.""" - async with asyncio.Lock(): + # Ensure only one control message is processed at a time + async with self._control_lock: await self.process_control(msg) async def process_control(self, msg): @@ -578,6 +582,10 @@ def schedule_dispatch(self, dispatch, *args): # ensure the eventloop wakes up self.io_loop.add_callback(lambda: None) + async def _create_control_lock(self): + # This can be removed when minimum python increases to 3.10 + self._control_lock = asyncio.Lock() + def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() @@ -588,6 +596,14 @@ def start(self): if self.control_stream: self.control_stream.on_recv(self.dispatch_control, copy=False) + if self.control_thread and sys.version_info < (3, 10): + # Before Python 3.10 we need to ensure the _control_lock is created in the + # thread that uses it. When our minimum python is 3.10 we can remove this + # and always use the else below, or just assign it where it is declared. + self.control_thread.io_loop.add_callback(self._create_control_lock) + else: + self._control_lock = asyncio.Lock() + if self.shell_stream: if self.shell_channel_thread: self.shell_channel_thread.manager.set_on_recv_callback(self.shell_main) diff --git a/tests/test_debugger.py b/tests/test_debugger.py index 4646fb49d..fe75e28f3 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -2,7 +2,7 @@ import pytest -from .utils import TIMEOUT, get_reply, new_kernel +from .utils import TIMEOUT, get_replies, get_reply, new_kernel seq = 0 @@ -15,11 +15,8 @@ debugpy = None -def wait_for_debug_request(kernel, command, arguments=None, full_reply=False): - """Carry out a debug request and return the reply content. - - It does not check if the request was successful. - """ +def prepare_debug_request(kernel, command, arguments=None): + """Prepare a debug request but do not send it.""" global seq seq += 1 @@ -32,6 +29,15 @@ def wait_for_debug_request(kernel, command, arguments=None, full_reply=False): "arguments": arguments or {}, }, ) + return msg + + +def wait_for_debug_request(kernel, command, arguments=None, full_reply=False): + """Carry out a debug request and return the reply content. + + It does not check if the request was successful. + """ + msg = prepare_debug_request(kernel, command, arguments) kernel.control_channel.send(msg) reply = get_reply(kernel, msg["header"]["msg_id"], channel="control") return reply if full_reply else reply["content"] @@ -459,3 +465,96 @@ def my_test(): # Compare local and global variable assert global_var["value"] == local_var["value"] and global_var["type"] == local_var["type"] # noqa: PT018 + + +def test_debug_requests_sequential(kernel_with_debug): + # Issue https://github.com/ipython/ipykernel/issues/1412 + # Control channel requests should be executed sequentially not concurrently. + code = """def f(a, b): + c = a + b + return c + +f(2, 3)""" + + r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) + if debugpy: + source = r["body"]["sourcePath"] + else: + assert r == {} + source = "some path" + + wait_for_debug_request( + kernel_with_debug, + "setBreakpoints", + { + "breakpoints": [{"line": 2}], + "source": {"path": source}, + "sourceModified": False, + }, + ) + + wait_for_debug_request(kernel_with_debug, "debugInfo") + wait_for_debug_request(kernel_with_debug, "configurationDone") + kernel_with_debug.execute(code) + + if not debugpy: + # Cannot stop on breakpoint if debugpy not installed + return + + # Wait for stop on breakpoint + msg: dict = {"msg_type": "", "content": {}} + while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": + msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) + + stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})["body"][ + "stackFrames" + ] + + scopes = wait_for_debug_request(kernel_with_debug, "scopes", {"frameId": stacks[0]["id"]})[ + "body" + ]["scopes"] + + # Get variablesReference for both Locals and Globals. + locals_ref = next(filter(lambda s: s["name"] == "Locals", scopes))["variablesReference"] + globals_ref = next(filter(lambda s: s["name"] == "Globals", scopes))["variablesReference"] + + msgs = [] + for ref in [locals_ref, globals_ref]: + msgs.append( + prepare_debug_request(kernel_with_debug, "variables", {"variablesReference": ref}) + ) + + # Send messages in quick succession. + for msg in msgs: + kernel_with_debug.control_channel.send(msg) + + replies = get_replies(kernel_with_debug, [msg["msg_id"] for msg in msgs], channel="control") + + # Check debug variable returns are correct. + locals = replies[0]["content"] + assert locals["success"] + variables = locals["body"]["variables"] + var = next(filter(lambda v: v["name"] == "a", variables)) + assert var["type"] == "int" + assert var["value"] == "2" + var = next(filter(lambda v: v["name"] == "b", variables)) + assert var["type"] == "int" + assert var["value"] == "3" + + globals = replies[1]["content"] + assert globals["success"] + variables = globals["body"]["variables"] + + names = [v["name"] for v in variables] + assert "function variables" in names + assert "special variables" in names + + # Check status iopub messages alternate between busy and idle. + execution_states = [] + while len(execution_states) < 8: + msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) + if msg["msg_type"] == "status": + execution_states.append(msg["content"]["execution_state"]) + assert execution_states.count("busy") == 4 + assert execution_states.count("idle") == 4 + assert execution_states == ["busy", "idle"] * 4 From 7daba5bc8ecf2b844bbecacf4efd74495a185fab Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 6 Aug 2025 17:31:21 +0100 Subject: [PATCH 1086/1195] Use correct `__version__` on `main` branch after branch manipulations (#1419) --- ipykernel/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 6b7e6d02a..651d058d5 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "6.30.0a0" +__version__ = "7.0.0a1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From efd53d72406b01550682587381bb5d5d96a2d8db Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 8 Aug 2025 10:56:42 +0100 Subject: [PATCH 1087/1195] Cache separate headers on subshell threads (#1414) --- .github/workflows/downstream.yml | 2 ++ ipykernel/displayhook.py | 32 ++++++++++++++---- ipykernel/kernelbase.py | 31 +++++++++++++---- ipykernel/zmqshell.py | 27 ++++++++++++--- tests/conftest.py | 6 ++-- tests/test_subshells.py | 57 +++++++++++++++++++++++++++++++- tests/test_zmq_shell.py | 2 +- 7 files changed, 134 insertions(+), 23 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 2aac74ee6..5a08f1d94 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -41,6 +41,7 @@ jobs: test_command: pytest -vv -raXxs -W default --durations 10 --color=yes jupyter_client: + if: false runs-on: ubuntu-latest steps: - name: Checkout @@ -55,6 +56,7 @@ jobs: package_name: jupyter_client ipyparallel: + if: false runs-on: ubuntu-latest timeout-minutes: 20 steps: diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index 4dd0bb5eb..5f42a1445 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -7,10 +7,11 @@ import builtins import sys import typing as t +from contextvars import ContextVar from IPython.core.displayhook import DisplayHook from jupyter_client.session import Session, extract_header -from traitlets import Any, Dict, Instance +from traitlets import Any, Instance from ipykernel.jsonutil import encode_images, json_clean @@ -25,7 +26,9 @@ def __init__(self, session, pub_socket): """Initialize the hook.""" self.session = session self.pub_socket = pub_socket - self.parent_header = {} + + self._parent_header: ContextVar[dict[str, Any]] = ContextVar("parent_header") + self._parent_header.set({}) def get_execution_count(self): """This method is replaced in kernelapp""" @@ -45,12 +48,20 @@ def __call__(self, obj): "metadata": {}, } self.session.send( - self.pub_socket, "execute_result", contents, parent=self.parent_header, ident=self.topic + self.pub_socket, + "execute_result", + contents, + parent=self.parent_header, + ident=self.topic, ) + @property + def parent_header(self): + return self._parent_header.get() + def set_parent(self, parent): """Set the parent header.""" - self.parent_header = extract_header(parent) + self._parent_header.set(extract_header(parent)) class ZMQShellDisplayHook(DisplayHook): @@ -62,12 +73,21 @@ class ZMQShellDisplayHook(DisplayHook): session = Instance(Session, allow_none=True) pub_socket = Any(allow_none=True) - parent_header = Dict({}) + _parent_header: ContextVar[dict[str, Any]] msg: dict[str, t.Any] | None + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent_header = ContextVar("parent_header") + self._parent_header.set({}) + + @property + def parent_header(self): + return self._parent_header.get() + def set_parent(self, parent): """Set the parent for outbound messages.""" - self.parent_header = extract_header(parent) + self._parent_header.set(extract_header(parent)) def start_displayhook(self): """Start the display hook.""" diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index d54cc42d5..e75a45c90 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -16,6 +16,7 @@ import typing as t import uuid import warnings +from contextvars import ContextVar from datetime import datetime from functools import partial from signal import SIGINT, SIGTERM, Signals, default_int_handler, signal @@ -194,8 +195,10 @@ def _default_ident(self): # track associations with current request _allow_stdin = Bool(False) - _parents: Dict[str, t.Any] = Dict({"shell": {}, "control": {}}) - _parent_ident = Dict({"shell": b"", "control": b""}) + _control_parent: Dict[str, t.Any] = Dict({}) + _control_parent_ident: bytes = b"" + _shell_parent: ContextVar[dict[str, Any]] + _shell_parent_ident: ContextVar[bytes] @property def _parent_header(self): @@ -302,6 +305,14 @@ def __init__(self, **kwargs): self.do_execute, ["cell_meta", "cell_id"] ) + self._control_parent = {} + self._control_parent_ident = b"" + + self._shell_parent = ContextVar("shell_parent") + self._shell_parent.set({}) + self._shell_parent_ident = ContextVar("shell_parent_ident") + self._shell_parent_ident.set(b"") + async def dispatch_control(self, msg): """Dispatch a control request, ensuring only one message is processed at a time.""" # Ensure only one control message is processed at a time @@ -737,8 +748,12 @@ def set_parent(self, ident, parent, channel="shell"): The parent identity is used to route input_request messages on the stdin channel. """ - self._parent_ident[channel] = ident - self._parents[channel] = parent + if channel == "control": + self._control_parent_ident = ident + self._control_parent = parent + else: + self._shell_parent_ident.set(ident) + self._shell_parent.set(parent) def get_parent(self, channel=None): """Get the parent request associated with a channel. @@ -763,7 +778,9 @@ def get_parent(self, channel=None): else: channel = "shell" - return self._parents.get(channel, {}) + if channel == "control": + return self._control_parent + return self._shell_parent.get() def send_response( self, @@ -1424,7 +1441,7 @@ def getpass(self, prompt="", stream=None): ) return self._input_request( prompt, - self._parent_ident["shell"], + self._shell_parent_ident.get(), self.get_parent("shell"), password=True, ) @@ -1441,7 +1458,7 @@ def raw_input(self, prompt=""): raise StdinNotImplementedError(msg) return self._input_request( str(prompt), - self._parent_ident["shell"], + self._shell_parent_ident.get(), self.get_parent("shell"), password=False, ) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 4883b376b..37575ee2d 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -14,6 +14,7 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. +import contextvars import os import sys import threading @@ -34,7 +35,7 @@ from IPython.utils.process import arg_split, system # type:ignore[attr-defined] from jupyter_client.session import Session, extract_header from jupyter_core.paths import jupyter_runtime_dir -from traitlets import Any, CBool, CBytes, Dict, Instance, Type, default, observe +from traitlets import Any, CBool, CBytes, Instance, Type, default, observe from ipykernel import connect_qtconsole, get_connection_file, get_connection_info from ipykernel.displayhook import ZMQShellDisplayHook @@ -50,7 +51,7 @@ class ZMQDisplayPublisher(DisplayPublisher): session = Instance(Session, allow_none=True) pub_socket = Any(allow_none=True) - parent_header = Dict({}) + _parent_header: contextvars.ContextVar[dict[str, Any]] topic = CBytes(b"display_data") # thread_local: @@ -58,9 +59,18 @@ class ZMQDisplayPublisher(DisplayPublisher): # is processed. See ipykernel Issue 113 for a discussion. _thread_local = Any() + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._parent_header = contextvars.ContextVar("parent_header") + self._parent_header.set({}) + + @property + def parent_header(self): + return self._parent_header.get() + def set_parent(self, parent): """Set the parent for outbound messages.""" - self.parent_header = extract_header(parent) + self._parent_header.set(extract_header(parent)) def _flush_streams(self): """flush IO Streams prior to display""" @@ -485,11 +495,14 @@ def __init__(self, *args, **kwargs): if "IPKernelApp" not in self.config: self.config.IPKernelApp.tqdm = "dummy value for https://github.com/tqdm/tqdm/pull/1628" + self._parent_header = contextvars.ContextVar("parent_header") + self._parent_header.set({}) + displayhook_class = Type(ZMQShellDisplayHook) display_pub_class = Type(ZMQDisplayPublisher) data_pub_class = Any() kernel = Any() - parent_header = Any() + _parent_header: contextvars.ContextVar[dict[str, Any]] @default("banner1") def _default_banner1(self): @@ -658,9 +671,13 @@ def set_next_input(self, text, replace=False): ) self.payload_manager.write_payload(payload) # type:ignore[union-attr] + @property + def parent_header(self): + return self._parent_header.get() + def set_parent(self, parent): """Set the parent header for associating output with its triggering input""" - self.parent_header = parent + self._parent_header.set(parent) self.displayhook.set_parent(parent) # type:ignore[attr-defined] self.display_pub.set_parent(parent) # type:ignore[attr-defined] if hasattr(self, "_data_pub"): diff --git a/tests/conftest.py b/tests/conftest.py index 16a936598..214b32aa8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,7 +4,7 @@ from typing import no_type_check from unittest.mock import MagicMock -import pytest +import pytest_asyncio import zmq from jupyter_client.session import Session from tornado.ioloop import IOLoop @@ -143,7 +143,7 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) -@pytest.fixture() +@pytest_asyncio.fixture() def kernel(): kernel = MockKernel() kernel.io_loop = IOLoop.current() @@ -151,7 +151,7 @@ def kernel(): kernel.destroy() -@pytest.fixture() +@pytest_asyncio.fixture() def ipkernel(): kernel = MockIPyKernel() kernel.io_loop = IOLoop.current() diff --git a/tests/test_subshells.py b/tests/test_subshells.py index d1affa666..62394a0e6 100644 --- a/tests/test_subshells.py +++ b/tests/test_subshells.py @@ -6,11 +6,12 @@ import platform import time +from collections import Counter import pytest from jupyter_client.blocking.client import BlockingKernelClient -from .utils import TIMEOUT, assemble_output, get_replies, get_reply, new_kernel +from .utils import TIMEOUT, assemble_output, get_replies, get_reply, new_kernel, wait_for_idle # Helpers @@ -258,3 +259,57 @@ def test_execute_stop_on_error(are_subshells): for subshell_id in subshell_ids: if subshell_id: delete_subshell_helper(kc, subshell_id) + + +@pytest.mark.parametrize("are_subshells", [(False, True), (True, False), (True, True)]) +def test_idle_message_parent_headers(are_subshells): + with new_kernel() as kc: + # import time module on main shell. + msg = kc.session.msg("execute_request", {"code": "import time"}) + kc.shell_channel.send(msg) + + subshell_ids = [ + create_subshell_helper(kc)["subshell_id"] if is_subshell else None + for is_subshell in are_subshells + ] + + # Wait for all idle status messages to be received. + for _ in range(1 + sum(are_subshells)): + wait_for_idle(kc) + + msg_ids = [] + for subshell_id in subshell_ids: + msg = execute_request(kc, "time.sleep(0.5)", subshell_id) + msg_ids.append(msg["msg_id"]) + + # Expect 4 status messages (2 busy, 2 idle) on iopub channel for the two execute_requests + statuses = [] + timeout = TIMEOUT # Combined timeout to receive all the status messages + t0 = time.time() + while True: + status = kc.get_iopub_msg(timeout=timeout) + if status["msg_type"] != "status" or status["parent_header"]["msg_id"] not in msg_ids: + continue + statuses.append(status) + if len(statuses) == 4: + break + t1 = time.time() + timeout -= t1 - t0 + t0 = t1 + + execution_states = Counter(msg["content"]["execution_state"] for msg in statuses) + assert execution_states["busy"] == 2 + assert execution_states["idle"] == 2 + + parent_msg_ids = Counter(msg["parent_header"]["msg_id"] for msg in statuses) + assert parent_msg_ids[msg_ids[0]] == 2 + assert parent_msg_ids[msg_ids[1]] == 2 + + parent_subshell_ids = Counter(msg["parent_header"].get("subshell_id") for msg in statuses) + assert parent_subshell_ids[subshell_ids[0]] == 2 + assert parent_subshell_ids[subshell_ids[1]] == 2 + + # Cleanup + for subshell_id in subshell_ids: + if subshell_id: + delete_subshell_helper(kc, subshell_id) diff --git a/tests/test_zmq_shell.py b/tests/test_zmq_shell.py index 8a8fe042b..bc5e3f556 100644 --- a/tests/test_zmq_shell.py +++ b/tests/test_zmq_shell.py @@ -245,7 +245,7 @@ def test_zmq_interactive_shell(kernel): shell.data_pub shell.kernel = kernel shell.set_next_input("hi") - assert shell.get_parent() is None + assert shell.get_parent() == {} if os.name == "posix": shell.system_piped("ls") else: From e34aedbed0a84ae9ab728634766a8120ea631647 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 13 Aug 2025 09:59:17 +0100 Subject: [PATCH 1088/1195] Reinstate jupyter_client downstream tests with exclusions (#1425) --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 5a08f1d94..94fa2f8a0 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -41,7 +41,6 @@ jobs: test_command: pytest -vv -raXxs -W default --durations 10 --color=yes jupyter_client: - if: false runs-on: ubuntu-latest steps: - name: Checkout @@ -54,6 +53,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: jupyter_client + test_command: "pytest -vv -raXxs -W default --durations 10 --color=yes -k 'not (test_input_request or signal_kernel_subprocess)'" ipyparallel: if: false From 415c3639a97dcc45c807907c6b68d76aed6d2cc1 Mon Sep 17 00:00:00 2001 From: ianthomas23 Date: Wed, 13 Aug 2025 09:51:46 +0000 Subject: [PATCH 1089/1195] Publish 7.0.0a2 SHA256 hashes: ipykernel-7.0.0a2-py3-none-any.whl: 58336ba0d4c5ad0b9833392d544d882baf520ce8af1b714f89e746197bd7c644 ipykernel-7.0.0a2.tar.gz: b3bd996d94ca09012b6df6654a086ddb3a01a40bcd1ad8cc1f5e80b327e0f120 --- CHANGELOG.md | 128 +++++++++++++++++++++++++++++++++++++++++- ipykernel/_version.py | 2 +- 2 files changed, 127 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 886cc9bcd..ff3126734 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,132 @@ +## 7.0.0a2 + +Pre-release of `ipykernel` with experimental subshell support on top of `tornado`/`asyncio`. The previous version 7 pre-releases used `anyio` but that will no longer be in `ipykernel` 7. + +Note the changelog here is not correct, presumably due to the recent branch renaming. For the full release this will have to be manually curated so that it is accurate. The "Full Changelog" link below is accurate. + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.5...e34aedbed0a84ae9ab728634766a8120ea631647)) + +### Enhancements made + +- Replace BaseThread's add_task with start_soon [#1300](https://github.com/ipython/ipykernel/pull/1300) ([@davidbrochart](https://github.com/davidbrochart)) +- Use supported_features=['debugger'] in kernel info reply [#1296](https://github.com/ipython/ipykernel/pull/1296) ([@ianthomas23](https://github.com/ianthomas23)) +- Use zmq-anyio [#1291](https://github.com/ipython/ipykernel/pull/1291) ([@davidbrochart](https://github.com/davidbrochart)) +- Detect parent change in more cases on unix [#1271](https://github.com/ipython/ipykernel/pull/1271) ([@bluss](https://github.com/bluss)) +- Kernel subshells (JEP91) implementation [#1249](https://github.com/ipython/ipykernel/pull/1249) ([@ianthomas23](https://github.com/ianthomas23)) + +### Bugs fixed + +- Forward port from 6.x: Correct use of asyncio.Lock to process a single control message at a time [#1418](https://github.com/ipython/ipykernel/pull/1418) ([@ianthomas23](https://github.com/ianthomas23)) +- Cache separate headers on subshell threads [#1414](https://github.com/ipython/ipykernel/pull/1414) ([@ianthomas23](https://github.com/ianthomas23)) +- Fix OutStream using \_fid before being defined [#1373](https://github.com/ipython/ipykernel/pull/1373) ([@davidbrochart](https://github.com/davidbrochart)) +- Make kernelbase.\_eventloop_set event thread-safe [#1366](https://github.com/ipython/ipykernel/pull/1366) ([@davidbrochart](https://github.com/davidbrochart)) +- Remove implicit bind_kernel in `%qtconsole` [#1315](https://github.com/ipython/ipykernel/pull/1315) ([@minrk](https://github.com/minrk)) +- Fix ipykernel install [#1310](https://github.com/ipython/ipykernel/pull/1310) ([@davidbrochart](https://github.com/davidbrochart)) +- socket must be None, not shell_socket for default shell [#1281](https://github.com/ipython/ipykernel/pull/1281) ([@minrk](https://github.com/minrk)) +- restore zero-copy recv on shell messages [#1280](https://github.com/ipython/ipykernel/pull/1280) ([@minrk](https://github.com/minrk)) +- Fix eventloop integration with anyio [#1265](https://github.com/ipython/ipykernel/pull/1265) ([@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- Reinstate jupyter_client downstream tests with exclusions [#1425](https://github.com/ipython/ipykernel/pull/1425) ([@ianthomas23](https://github.com/ianthomas23)) +- Use correct `__version__` on `main` branch after branch manipulations [#1419](https://github.com/ipython/ipykernel/pull/1419) ([@ianthomas23](https://github.com/ianthomas23)) +- Remove links in changelog to github milestones that no longer exist [#1415](https://github.com/ipython/ipykernel/pull/1415) ([@ianthomas23](https://github.com/ianthomas23)) +- Replace `@flaky.flaky` decorate with pytest fixture [#1411](https://github.com/ipython/ipykernel/pull/1411) ([@mgorny](https://github.com/mgorny)) +- chore: update pre-commit hooks [#1409](https://github.com/ipython/ipykernel/pull/1409) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Bump scientific-python/upload-nightly-action from 0.6.1 to 0.6.2 in the actions group [#1404](https://github.com/ipython/ipykernel/pull/1404) ([@dependabot](https://github.com/dependabot)) +- Backports from `anyio` (old `main`) branch to `main` branch [#1402](https://github.com/ipython/ipykernel/pull/1402) ([@ianthomas23](https://github.com/ianthomas23)) +- Update pre-commit and github actions [#1401](https://github.com/ipython/ipykernel/pull/1401) ([@ianthomas23](https://github.com/ianthomas23)) +- Add security.md [#1394](https://github.com/ipython/ipykernel/pull/1394) ([@Carreau](https://github.com/Carreau)) +- chore: update pre-commit hooks [#1388](https://github.com/ipython/ipykernel/pull/1388) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- chore: update pre-commit hooks [#1385](https://github.com/ipython/ipykernel/pull/1385) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- chore: update pre-commit hooks [#1383](https://github.com/ipython/ipykernel/pull/1383) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- chore: update pre-commit hooks [#1378](https://github.com/ipython/ipykernel/pull/1378) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- chore: update pre-commit hooks [#1375](https://github.com/ipython/ipykernel/pull/1375) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- chore: update pre-commit hooks [#1372](https://github.com/ipython/ipykernel/pull/1372) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Remove nose import. [#1368](https://github.com/ipython/ipykernel/pull/1368) ([@Carreau](https://github.com/Carreau)) +- TQDM workaround due to unresponsive maintainer [#1363](https://github.com/ipython/ipykernel/pull/1363) ([@Carreau](https://github.com/Carreau)) +- chore: update pre-commit hooks [#1355](https://github.com/ipython/ipykernel/pull/1355) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Fix expected text depending on IPython version. [#1354](https://github.com/ipython/ipykernel/pull/1354) ([@Carreau](https://github.com/Carreau)) +- Another try at tracking down ResourceWarning with tracemalloc. [#1353](https://github.com/ipython/ipykernel/pull/1353) ([@Carreau](https://github.com/Carreau)) +- Remove deprecated modules since 4.3 (2016). [#1352](https://github.com/ipython/ipykernel/pull/1352) ([@Carreau](https://github.com/Carreau)) +- Try to reenable tests from downstream ipywidgets [#1350](https://github.com/ipython/ipykernel/pull/1350) ([@Carreau](https://github.com/Carreau)) +- Disable 3 failing downstream tests, but keep testing the rest. [#1349](https://github.com/ipython/ipykernel/pull/1349) ([@Carreau](https://github.com/Carreau)) +- Licence :: * trove classifers are deprecated [#1348](https://github.com/ipython/ipykernel/pull/1348) ([@Carreau](https://github.com/Carreau)) +- Pin sphinx to resolve docs build failures [#1347](https://github.com/ipython/ipykernel/pull/1347) ([@krassowski](https://github.com/krassowski)) +- Make our own mock kernel methods async [#1346](https://github.com/ipython/ipykernel/pull/1346) ([@Carreau](https://github.com/Carreau)) +- Try to debug non-closed iopub socket [#1345](https://github.com/ipython/ipykernel/pull/1345) ([@Carreau](https://github.com/Carreau)) +- Email is @python.org since 2018 [#1343](https://github.com/ipython/ipykernel/pull/1343) ([@Carreau](https://github.com/Carreau)) +- Remove unused ignores lints. [#1342](https://github.com/ipython/ipykernel/pull/1342) ([@Carreau](https://github.com/Carreau)) +- Enable ruff G002 and fix 6 occurences [#1341](https://github.com/ipython/ipykernel/pull/1341) ([@Carreau](https://github.com/Carreau)) +- Check ignores warnings are still relevant. [#1340](https://github.com/ipython/ipykernel/pull/1340) ([@Carreau](https://github.com/Carreau)) +- Move mypy disablinging error codes on a per-file basis [#1338](https://github.com/ipython/ipykernel/pull/1338) ([@Carreau](https://github.com/Carreau)) +- try to fix spyder kernel install [#1337](https://github.com/ipython/ipykernel/pull/1337) ([@Carreau](https://github.com/Carreau)) +- Remove test_check job [#1335](https://github.com/ipython/ipykernel/pull/1335) ([@Carreau](https://github.com/Carreau)) +- Refine deprecation error messages. [#1334](https://github.com/ipython/ipykernel/pull/1334) ([@Carreau](https://github.com/Carreau)) +- Bump mypy [#1333](https://github.com/ipython/ipykernel/pull/1333) ([@Carreau](https://github.com/Carreau)) +- Remove dead code. [#1332](https://github.com/ipython/ipykernel/pull/1332) ([@Carreau](https://github.com/Carreau)) +- Ignore or fix most of the remaining ruff 0.9.6 errors [#1331](https://github.com/ipython/ipykernel/pull/1331) ([@Carreau](https://github.com/Carreau)) +- minor code reformating valid ruff 0.9.6 [#1330](https://github.com/ipython/ipykernel/pull/1330) ([@Carreau](https://github.com/Carreau)) +- Some formatting changes to prepare bumping ruff pre-commit. [#1329](https://github.com/ipython/ipykernel/pull/1329) ([@Carreau](https://github.com/Carreau)) +- Manually update Codespell and fix new errors. [#1328](https://github.com/ipython/ipykernel/pull/1328) ([@Carreau](https://github.com/Carreau)) +- Manually update mdformat pre-commit and run it. [#1327](https://github.com/ipython/ipykernel/pull/1327) ([@Carreau](https://github.com/Carreau)) +- Manually update pre-commit hooks that do not trigger new errors/fixes. [#1326](https://github.com/ipython/ipykernel/pull/1326) ([@Carreau](https://github.com/Carreau)) +- Try to force precommit-ci to send autoupdate PRs. [#1325](https://github.com/ipython/ipykernel/pull/1325) ([@Carreau](https://github.com/Carreau)) +- Don't rerun test with --lf it hides failures. [#1324](https://github.com/ipython/ipykernel/pull/1324) ([@Carreau](https://github.com/Carreau)) +- Delete always skipped test, fix trio test, fix framelocal has not .clear() [#1322](https://github.com/ipython/ipykernel/pull/1322) ([@Carreau](https://github.com/Carreau)) +- Fix types lints [#1321](https://github.com/ipython/ipykernel/pull/1321) ([@Carreau](https://github.com/Carreau)) +- Remove link to numfocus for funding. [#1320](https://github.com/ipython/ipykernel/pull/1320) ([@Carreau](https://github.com/Carreau)) +- Remove downstream_check [#1318](https://github.com/ipython/ipykernel/pull/1318) ([@Carreau](https://github.com/Carreau)) +- Copy payloadpage.page from IPython [#1317](https://github.com/ipython/ipykernel/pull/1317) ([@Carreau](https://github.com/Carreau)) +- More Informative assert [#1314](https://github.com/ipython/ipykernel/pull/1314) ([@Carreau](https://github.com/Carreau)) +- Fix test_print_to_correct_cell_from_child_thread [#1312](https://github.com/ipython/ipykernel/pull/1312) ([@davidbrochart](https://github.com/davidbrochart)) +- make debugger class configurable [#1307](https://github.com/ipython/ipykernel/pull/1307) ([@smacke](https://github.com/smacke)) +- properly close OutStream and various fixes [#1305](https://github.com/ipython/ipykernel/pull/1305) ([@limwz01](https://github.com/limwz01)) +- Remove base setup [#1299](https://github.com/ipython/ipykernel/pull/1299) ([@davidbrochart](https://github.com/davidbrochart)) +- Suggest to make implementations of some function always return awaitable [#1295](https://github.com/ipython/ipykernel/pull/1295) ([@Carreau](https://github.com/Carreau)) +- Misc type annotations [#1294](https://github.com/ipython/ipykernel/pull/1294) ([@Carreau](https://github.com/Carreau)) +- Rely on intrsphinx_registry to keep intersphinx up to date. [#1290](https://github.com/ipython/ipykernel/pull/1290) ([@Carreau](https://github.com/Carreau)) +- Improve robustness of subshell concurrency tests using Barrier [#1288](https://github.com/ipython/ipykernel/pull/1288) ([@ianthomas23](https://github.com/ianthomas23)) +- Add 20 min timeout dowstream ipyparallel [#1287](https://github.com/ipython/ipykernel/pull/1287) ([@Carreau](https://github.com/Carreau)) +- Improve robustness of subshell concurrency tests [#1285](https://github.com/ipython/ipykernel/pull/1285) ([@ianthomas23](https://github.com/ianthomas23)) +- Drop support for Python 3.8 [#1284](https://github.com/ipython/ipykernel/pull/1284) ([@ianthomas23](https://github.com/ianthomas23)) +- remove deprecated ipyparallel methods now that they are broken anyway [#1282](https://github.com/ipython/ipykernel/pull/1282) ([@minrk](https://github.com/minrk)) +- start testing on 3.13 [#1277](https://github.com/ipython/ipykernel/pull/1277) ([@Carreau](https://github.com/Carreau)) +- Try to add workflow to publish nightlies [#1276](https://github.com/ipython/ipykernel/pull/1276) ([@Carreau](https://github.com/Carreau)) +- fix mixture of sync/async sockets in IOPubThread [#1275](https://github.com/ipython/ipykernel/pull/1275) ([@minrk](https://github.com/minrk)) +- Remove some potential dead-code. [#1273](https://github.com/ipython/ipykernel/pull/1273) ([@Carreau](https://github.com/Carreau)) +- Remove direct use of asyncio [#1266](https://github.com/ipython/ipykernel/pull/1266) ([@davidbrochart](https://github.com/davidbrochart)) +- Specify argtypes when using macos msg [#1264](https://github.com/ipython/ipykernel/pull/1264) ([@ianthomas23](https://github.com/ianthomas23)) +- Forward port changelog for 6.29.4 and 5 to main branch [#1263](https://github.com/ipython/ipykernel/pull/1263) ([@ianthomas23](https://github.com/ianthomas23)) +- Ignore warning from trio [#1262](https://github.com/ipython/ipykernel/pull/1262) ([@ianthomas23](https://github.com/ianthomas23)) +- Build docs on ubuntu [#1257](https://github.com/ipython/ipykernel/pull/1257) ([@blink1073](https://github.com/blink1073)) + +### Documentation improvements + +- Add subshell docstrings [#1405](https://github.com/ipython/ipykernel/pull/1405) ([@ianthomas23](https://github.com/ianthomas23)) +- Forward port changelog for 6.29.4 and 5 to main branch [#1263](https://github.com/ipython/ipykernel/pull/1263) ([@ianthomas23](https://github.com/ianthomas23)) + +### Deprecated features + +- Remove deprecated modules since 4.3 (2016). [#1352](https://github.com/ipython/ipykernel/pull/1352) ([@Carreau](https://github.com/Carreau)) +- Suggest to make implementations of some function always return awaitable [#1295](https://github.com/ipython/ipykernel/pull/1295) ([@Carreau](https://github.com/Carreau)) + +### Other merged PRs + +- Ensure test_start_app takes 1s to stop kernel [#1364](https://github.com/ipython/ipykernel/pull/1364) ([@davidbrochart](https://github.com/davidbrochart)) +- Test more python versions [#1358](https://github.com/ipython/ipykernel/pull/1358) ([@davidbrochart](https://github.com/davidbrochart)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-07-01&to=2025-08-13&type=c)) + +[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2024-07-01..2025-08-13&type=Issues) | [@bluss](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abluss+updated%3A2024-07-01..2025-08-13&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2024-07-01..2025-08-13&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2024-07-01..2025-08-13&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-07-01..2025-08-13&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2024-07-01..2025-08-13&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-07-01..2025-08-13&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2024-07-01..2025-08-13&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2024-07-01..2025-08-13&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-07-01..2025-08-13&type=Issues) | [@limwz01](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Alimwz01+updated%3A2024-07-01..2025-08-13&type=Issues) | [@mgorny](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amgorny+updated%3A2024-07-01..2025-08-13&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-07-01..2025-08-13&type=Issues) | [@nathanmcavoy](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anathanmcavoy+updated%3A2024-07-01..2025-08-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2024-07-01..2025-08-13&type=Issues) | [@smacke](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Asmacke+updated%3A2024-07-01..2025-08-13&type=Issues) + + + ## 6.30.0a0 Pre-release to allow further testing of subshell implementation. @@ -27,8 +153,6 @@ Pre-release to allow further testing of subshell implementation. [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2024-07-01..2025-06-05&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2024-07-01..2025-06-05&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-07-01..2025-06-05&type=Issues) | [@dby-tmwctw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adby-tmwctw+updated%3A2024-07-01..2025-06-05&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-07-01..2025-06-05&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2024-07-01..2025-06-05&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2024-07-01..2025-06-05&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-07-01..2025-06-05&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ameeseeksmachine+updated%3A2024-07-01..2025-06-05&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-07-01..2025-06-05&type=Issues) - - ## 6.29.5 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.4...1e62d48298e353a9879fae99bc752f9bb48797ef)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 651d058d5..5857570f7 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "7.0.0a1" +__version__ = "7.0.0a2" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From acf027b50c7dc3dd2118c6a72c2ee0d506caf605 Mon Sep 17 00:00:00 2001 From: Pankaj Kumar Bind <73558583+pankaj-bind@users.noreply.github.com> Date: Thu, 14 Aug 2025 15:11:44 +0530 Subject: [PATCH 1090/1195] Tests: Add robust tests for silent flag in subshells (#1424) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ian Thomas --- CHANGELOG.md | 8 ++-- tests/test_subshells.py | 94 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff3126734..599f8b04f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,15 +53,15 @@ Note the changelog here is not correct, presumably due to the recent branch rena - Fix expected text depending on IPython version. [#1354](https://github.com/ipython/ipykernel/pull/1354) ([@Carreau](https://github.com/Carreau)) - Another try at tracking down ResourceWarning with tracemalloc. [#1353](https://github.com/ipython/ipykernel/pull/1353) ([@Carreau](https://github.com/Carreau)) - Remove deprecated modules since 4.3 (2016). [#1352](https://github.com/ipython/ipykernel/pull/1352) ([@Carreau](https://github.com/Carreau)) -- Try to reenable tests from downstream ipywidgets [#1350](https://github.com/ipython/ipykernel/pull/1350) ([@Carreau](https://github.com/Carreau)) +- Try to re-enable tests from downstream ipywidgets [#1350](https://github.com/ipython/ipykernel/pull/1350) ([@Carreau](https://github.com/Carreau)) - Disable 3 failing downstream tests, but keep testing the rest. [#1349](https://github.com/ipython/ipykernel/pull/1349) ([@Carreau](https://github.com/Carreau)) -- Licence :: * trove classifers are deprecated [#1348](https://github.com/ipython/ipykernel/pull/1348) ([@Carreau](https://github.com/Carreau)) +- Licence :: * trove classifiers are deprecated [#1348](https://github.com/ipython/ipykernel/pull/1348) ([@Carreau](https://github.com/Carreau)) - Pin sphinx to resolve docs build failures [#1347](https://github.com/ipython/ipykernel/pull/1347) ([@krassowski](https://github.com/krassowski)) - Make our own mock kernel methods async [#1346](https://github.com/ipython/ipykernel/pull/1346) ([@Carreau](https://github.com/Carreau)) - Try to debug non-closed iopub socket [#1345](https://github.com/ipython/ipykernel/pull/1345) ([@Carreau](https://github.com/Carreau)) - Email is @python.org since 2018 [#1343](https://github.com/ipython/ipykernel/pull/1343) ([@Carreau](https://github.com/Carreau)) - Remove unused ignores lints. [#1342](https://github.com/ipython/ipykernel/pull/1342) ([@Carreau](https://github.com/Carreau)) -- Enable ruff G002 and fix 6 occurences [#1341](https://github.com/ipython/ipykernel/pull/1341) ([@Carreau](https://github.com/Carreau)) +- Enable ruff G002 and fix 6 occurrences [#1341](https://github.com/ipython/ipykernel/pull/1341) ([@Carreau](https://github.com/Carreau)) - Check ignores warnings are still relevant. [#1340](https://github.com/ipython/ipykernel/pull/1340) ([@Carreau](https://github.com/Carreau)) - Move mypy disablinging error codes on a per-file basis [#1338](https://github.com/ipython/ipykernel/pull/1338) ([@Carreau](https://github.com/Carreau)) - try to fix spyder kernel install [#1337](https://github.com/ipython/ipykernel/pull/1337) ([@Carreau](https://github.com/Carreau)) @@ -70,7 +70,7 @@ Note the changelog here is not correct, presumably due to the recent branch rena - Bump mypy [#1333](https://github.com/ipython/ipykernel/pull/1333) ([@Carreau](https://github.com/Carreau)) - Remove dead code. [#1332](https://github.com/ipython/ipykernel/pull/1332) ([@Carreau](https://github.com/Carreau)) - Ignore or fix most of the remaining ruff 0.9.6 errors [#1331](https://github.com/ipython/ipykernel/pull/1331) ([@Carreau](https://github.com/Carreau)) -- minor code reformating valid ruff 0.9.6 [#1330](https://github.com/ipython/ipykernel/pull/1330) ([@Carreau](https://github.com/Carreau)) +- minor code reformatting valid ruff 0.9.6 [#1330](https://github.com/ipython/ipykernel/pull/1330) ([@Carreau](https://github.com/Carreau)) - Some formatting changes to prepare bumping ruff pre-commit. [#1329](https://github.com/ipython/ipykernel/pull/1329) ([@Carreau](https://github.com/Carreau)) - Manually update Codespell and fix new errors. [#1328](https://github.com/ipython/ipykernel/pull/1328) ([@Carreau](https://github.com/Carreau)) - Manually update mdformat pre-commit and run it. [#1327](https://github.com/ipython/ipykernel/pull/1327) ([@Carreau](https://github.com/Carreau)) diff --git a/tests/test_subshells.py b/tests/test_subshells.py index 62394a0e6..4d906d44b 100644 --- a/tests/test_subshells.py +++ b/tests/test_subshells.py @@ -7,11 +7,20 @@ import platform import time from collections import Counter +from queue import Empty import pytest from jupyter_client.blocking.client import BlockingKernelClient -from .utils import TIMEOUT, assemble_output, get_replies, get_reply, new_kernel, wait_for_idle +from .utils import ( + TIMEOUT, + assemble_output, + flush_channels, + get_replies, + get_reply, + new_kernel, + wait_for_idle, +) # Helpers @@ -40,8 +49,10 @@ def list_subshell_helper(kc: BlockingKernelClient): return reply["content"] -def execute_request(kc: BlockingKernelClient, code: str, subshell_id: str | None): - msg = kc.session.msg("execute_request", {"code": code}) +def execute_request( + kc: BlockingKernelClient, code: str, subshell_id: str | None, silent: bool = False +): + msg = kc.session.msg("execute_request", {"code": code, "silent": silent}) msg["header"]["subshell_id"] = subshell_id kc.shell_channel.send(msg) return msg @@ -224,16 +235,16 @@ def test_execute_stop_on_error(are_subshells): msg = execute_request( kc, "import asyncio; await asyncio.sleep(1); raise ValueError()", subshell_ids[0] ) - msg_ids.append(msg["msg_id"]) + msg_ids.append(msg["header"]["msg_id"]) msg = execute_request(kc, "print('hello')", subshell_ids[0]) - msg_ids.append(msg["msg_id"]) + msg_ids.append(msg["header"]["msg_id"]) msg = execute_request(kc, "print('goodbye')", subshell_ids[0]) - msg_ids.append(msg["msg_id"]) + msg_ids.append(msg["header"]["msg_id"]) msg = execute_request(kc, "import time; time.sleep(1.5)", subshell_ids[1]) - msg_ids.append(msg["msg_id"]) + msg_ids.append(msg["header"]["msg_id"]) msg = execute_request(kc, "print('other')", subshell_ids[1]) - msg_ids.append(msg["msg_id"]) + msg_ids.append(msg["header"]["msg_id"]) replies = get_replies(kc, msg_ids) @@ -313,3 +324,70 @@ def test_idle_message_parent_headers(are_subshells): for subshell_id in subshell_ids: if subshell_id: delete_subshell_helper(kc, subshell_id) + + +def test_silent_flag_in_subshells(): + """Verifies that the 'silent' flag suppresses output in main and subshell contexts.""" + with new_kernel() as kc: + subshell_id = None + try: + flush_channels(kc) + # Test silent execution in main shell + msg_main_silent = execute_request(kc, "a=1", None, silent=True) + reply_main_silent = get_reply(kc, msg_main_silent["header"]["msg_id"]) + assert reply_main_silent["content"]["status"] == "ok" + + # Test silent execution in subshell + subshell_id = create_subshell_helper(kc)["subshell_id"] + msg_sub_silent = execute_request(kc, "b=2", subshell_id, silent=True) + reply_sub_silent = get_reply(kc, msg_sub_silent["header"]["msg_id"]) + assert reply_sub_silent["content"]["status"] == "ok" + + # Check for no iopub messages (other than status) from the silent requests + for msg_id in [msg_main_silent["header"]["msg_id"], msg_sub_silent["header"]["msg_id"]]: + while True: + try: + msg = kc.get_iopub_msg(timeout=0.2) + if msg["header"]["msg_type"] == "status": + continue + pytest.fail( + f"Silent execution produced unexpected IOPub message: {msg['header']['msg_type']}" + ) + except Empty: + break + + # Test concurrent silent and non-silent execution + msg_silent = execute_request( + kc, "import time; time.sleep(0.5); c=3", subshell_id, silent=True + ) + msg_noisy = execute_request(kc, "print('noisy')", None, silent=False) + + # Wait for both replies + get_replies(kc, [msg_silent["header"]["msg_id"], msg_noisy["header"]["msg_id"]]) + + # Verify that we only receive stream output from the noisy message + stdout, stderr = assemble_output( + kc.get_iopub_msg, parent_msg_id=msg_noisy["header"]["msg_id"] + ) + assert "noisy" in stdout + assert not stderr + + # Verify there is no output from the concurrent silent message + while True: + try: + msg = kc.get_iopub_msg(timeout=0.2) + if ( + msg["header"]["msg_type"] == "status" + and msg["parent_header"].get("msg_id") == msg_silent["header"]["msg_id"] + ): + continue + if msg["parent_header"].get("msg_id") == msg_silent["header"]["msg_id"]: + pytest.fail( + "Silent execution in concurrent setting produced unexpected IOPub message" + ) + except Empty: + break + finally: + # Ensure subshell is always deleted + if subshell_id: + delete_subshell_helper(kc, subshell_id) From ece398fc3686836d64fed1dfc1f7abe2b8ae833e Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 5 Sep 2025 07:09:38 +0100 Subject: [PATCH 1091/1195] Continue to support `Kernel._parent_ident` for backward compatibility (#1427) --- ipykernel/kernelbase.py | 14 ++++++++++++++ ipykernel/utils.py | 26 ++++++++++++++++++++++++++ pyproject.toml | 2 +- tests/test_kernel.py | 41 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 ipykernel/utils.py diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e75a45c90..aceaf3eb9 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -16,6 +16,7 @@ import typing as t import uuid import warnings +from collections.abc import Mapping from contextvars import ContextVar from datetime import datetime from functools import partial @@ -61,6 +62,7 @@ from ._version import kernel_protocol_version from .iostream import OutStream +from .utils import LazyDict _AWAITABLE_MESSAGE: str = ( "For consistency across implementations, it is recommended that `{func_name}`" @@ -199,6 +201,9 @@ def _default_ident(self): _control_parent_ident: bytes = b"" _shell_parent: ContextVar[dict[str, Any]] _shell_parent_ident: ContextVar[bytes] + # Kept for backward-compatibility, accesses _control_parent_ident and _shell_parent_ident, + # see https://github.com/jupyterlab/jupyterlab/issues/17785 + _parent_ident: Mapping[str, bytes] @property def _parent_header(self): @@ -313,6 +318,15 @@ def __init__(self, **kwargs): self._shell_parent_ident = ContextVar("shell_parent_ident") self._shell_parent_ident.set(b"") + # For backward compatibility so that _parent_ident["shell"] and _parent_ident["control"] + # work as they used to for ipykernel >= 7 + self._parent_ident = LazyDict( + { + "control": lambda: self._control_parent_ident, + "shell": lambda: self._shell_parent_ident.get(), + } + ) + async def dispatch_control(self, msg): """Dispatch a control request, ensuring only one message is processed at a time.""" # Ensure only one control message is processed at a time diff --git a/ipykernel/utils.py b/ipykernel/utils.py new file mode 100644 index 000000000..8f6513ce0 --- /dev/null +++ b/ipykernel/utils.py @@ -0,0 +1,26 @@ +"""Utilities""" + +import typing as t +from collections.abc import Mapping + + +class LazyDict(Mapping[str, t.Any]): + """Lazy evaluated read-only dictionary. + + Initialised with a dictionary of key-value pairs where the values are either + constants or callables. Callables are evaluated each time the respective item is + read. + """ + + def __init__(self, dict): + self._dict = dict + + def __getitem__(self, key): + item = self._dict.get(key) + return item() if callable(item) else item + + def __len__(self): + return len(self._dict) + + def __iter__(self): + return iter(self._dict) diff --git a/pyproject.toml b/pyproject.toml index 73007bc0e..f00ea205f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ path = "ipykernel/_version.py" features = ["docs"] [tool.hatch.envs.docs.scripts] build = "make -C docs html SPHINXOPTS='-W'" -api = "sphinx-apidoc -o docs/api -f -E ipykernel tests ipykernel/datapub.py ipykernel/pickleutil.py ipykernel/serialize.py ipykernel/gui ipykernel/pylab" +api = "sphinx-apidoc -o docs/api -f -E ipykernel tests ipykernel/datapub.py ipykernel/pickleutil.py ipykernel/serialize.py ipykernel/gui ipykernel/pylab ipykernel/utils.py" [tool.hatch.envs.test] features = ["test"] diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 685748c42..ae8c621d3 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -717,3 +717,44 @@ def test_shutdown_subprocesses(): child_newpg.terminate() except psutil.NoSuchProcess: pass + + +def test_parent_header_and_ident(): + # Kernel._parent_ident is private but kept for backward compatibility, + # see https://github.com/jupyterlab/jupyterlab/issues/17785 + with kernel() as kc: + # get_parent('shell') + msg_id, _ = execute( + kc=kc, + code="k=get_ipython().kernel; p=k.get_parent('shell'); print(p['header']['msg_id'], p['header']['session'])", + ) + stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id) + check_msg_id, session = stdout.split() + assert check_msg_id == msg_id + assert check_msg_id.startswith(msg_id) + + # _parent_ident['shell'] + msg_id, _ = execute(kc=kc, code="print(k._parent_ident['shell'])") + stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id) + assert stdout == f"[b'{session}']\n" + + # Send a control message + msg = kc.session.msg("kernel_info_request") + kc.control_channel.send(msg) + control_msg_id = msg["header"]["msg_id"] + assemble_output(kc.get_iopub_msg, parent_msg_id=control_msg_id) + + # get_parent('control') + msg_id, _ = execute( + kc=kc, + code="p=k.get_parent('control'); print(p['header']['msg_id'], p['header']['session'])", + ) + stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id) + check_msg_id, session = stdout.split() + assert check_msg_id == control_msg_id + assert check_msg_id.startswith(control_msg_id) + + # _parent_ident['control'] + msg_id, _ = execute(kc=kc, code="print(k._parent_ident['control'])") + stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id) + assert stdout == f"[b'{session}']\n" From 40169949c190cd823826a66282036d25da83672e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 09:04:58 +0100 Subject: [PATCH 1092/1195] Bump the actions group across 1 directory with 2 updates (#1428) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 22 +++++++++++----------- .github/workflows/downstream.yml | 18 +++++++++--------- .github/workflows/nightly.yml | 2 +- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f78dc42a0..d2a39eb1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,9 +31,9 @@ jobs: - "pypy-3.10" steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} @@ -73,7 +73,7 @@ jobs: needs: - build steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: jupyterlab/maintainer-tools/.github/actions/report-coverage@v1 with: fail_under: 80 @@ -82,7 +82,7 @@ jobs: name: Test Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Run Linters run: | @@ -94,7 +94,7 @@ jobs: check_release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 with: @@ -103,7 +103,7 @@ jobs: test_docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Build API docs run: | @@ -125,7 +125,7 @@ jobs: python-version: ["3.9"] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -148,7 +148,7 @@ jobs: timeout-minutes: 20 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: @@ -169,7 +169,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: @@ -183,7 +183,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1 @@ -199,6 +199,6 @@ jobs: link_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 94fa2f8a0..ced8b2d56 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -61,7 +61,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -76,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -93,9 +93,9 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.9" architecture: "x64" @@ -125,9 +125,9 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.9" architecture: "x64" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 31110223a..dc620e9fa 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -17,7 +17,7 @@ jobs: python-version: ["3.12"] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 From 39d4c74a7eda3029b2c1bb3533c724f6b8f8f494 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 10 Sep 2025 09:27:33 +0100 Subject: [PATCH 1093/1195] Forward port of 6.x changelog to main branch (#1429) --- CHANGELOG.md | 57 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 599f8b04f..8d7ff1284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -128,30 +128,73 @@ Note the changelog here is not correct, presumably due to the recent branch rena -## 6.30.0a0 +## 6.30.1 -Pre-release to allow further testing of subshell implementation. +This is a bugfix release to fix a significant bug introduced in 6.30.0 that allowed control messages to be handled concurrently rather than sequentially which broke debugging in JupyterLab and VSCode. -([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.5...5d90d9e10425886deb4b0f1676b14b1701522b3c)) +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.30.0...357c908eab4ae97bb17c5dcabc7ee981df8ecb29)) + +### Bugs fixed + +- Correct use of asyncio.Lock to process a single control message at a time [#1416](https://github.com/ipython/ipykernel/pull/1416) ([@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- Backport: Remove links in changelog to github milestones that no longer exist [#1417](https://github.com/ipython/ipykernel/pull/1417) ([@ianthomas23](https://github.com/ianthomas23)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2025-07-21&to=2025-08-04&type=c)) + +[@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-07-21..2025-08-04&type=Issues) + +## 6.30.0 + +This release fixes three bugs but is primarily a maintenance release bringing support for Python 3.13 and updating dependencies. It does not include subshells which will be in the upcoming 7.0.0 release. Users and downstream libraries that wish to avoid subshells should pin to `ipykernel < 7`. + +([Full Changelog](https://github.com/ipython/ipykernel/compare/b1283b144...d9bd546a4dc49a41c3ad5fcc5d4a61f259973182)) ### Enhancements made -- Subshells implemented using tornado event loops on 6.x branch [#1396](https://github.com/ipython/ipykernel/pull/1396) ([@ianthomas23](https://github.com/ianthomas23)) +- Remove control queue [#1210](https://github.com/ipython/ipykernel/pull/1210) ([@ianthomas23](https://github.com/ianthomas23)) ### Bugs fixed -- [Bugfix] Set shell idle when message skipped by "should_handle" in "dispatch_shell" [#1395](https://github.com/ipython/ipykernel/pull/1395) ([@dby-tmwctw](https://github.com/dby-tmwctw)) +- Set shell idle when message skipped by "should_handle" in "dispatch_shell" [#1395](https://github.com/ipython/ipykernel/pull/1395) ([@dby-tmwctw](https://github.com/dby-tmwctw)) +- Fixed error accessing sys.stdout/sys.stderr when those are None [#1247](https://github.com/ipython/ipykernel/pull/1247) ([@gregory-shklover](https://github.com/gregory-shklover)) +- Allow datetime or str in test_sequential_control_messages [#1219](https://github.com/ipython/ipykernel/pull/1219) ([@ianthomas23](https://github.com/ianthomas23)) ### Maintenance and upkeep improvements +- 6.x backports [#1406](https://github.com/ipython/ipykernel/pull/1406) ([@ianthomas23](https://github.com/ianthomas23)) +- Update pre-commit and github actions [#1401](https://github.com/ipython/ipykernel/pull/1401) ([@ianthomas23](https://github.com/ianthomas23)) +- Test more python versions on 6.x branch [#1398](https://github.com/ipython/ipykernel/pull/1398) ([@davidbrochart](https://github.com/davidbrochart)) - Backports and extra changes to fix CI on 6.x branch [#1390](https://github.com/ipython/ipykernel/pull/1390) ([@ianthomas23](https://github.com/ianthomas23)) +- Remove nose import. [#1368](https://github.com/ipython/ipykernel/pull/1368) ([@Carreau](https://github.com/Carreau)) +- Test more python versions [#1358](https://github.com/ipython/ipykernel/pull/1358) ([@davidbrochart](https://github.com/davidbrochart)) +- Fix expected text depending on IPython version. [#1354](https://github.com/ipython/ipykernel/pull/1354) ([@Carreau](https://github.com/Carreau)) +- Licence :: * trove classifiers are deprecated [#1348](https://github.com/ipython/ipykernel/pull/1348) ([@Carreau](https://github.com/Carreau)) +- Try to fix spyder kernel install [#1337](https://github.com/ipython/ipykernel/pull/1337) ([@Carreau](https://github.com/Carreau)) +- Remove test_check job [#1335](https://github.com/ipython/ipykernel/pull/1335) ([@Carreau](https://github.com/Carreau)) +- Don't rerun test with --lf it hides failures. [#1324](https://github.com/ipython/ipykernel/pull/1324) ([@Carreau](https://github.com/Carreau)) +- Remove link to numfocus for funding. [#1320](https://github.com/ipython/ipykernel/pull/1320) ([@Carreau](https://github.com/Carreau)) +- Remove downstream_check [#1318](https://github.com/ipython/ipykernel/pull/1318) ([@Carreau](https://github.com/Carreau)) +- Copy payloadpage.page from IPython [#1317](https://github.com/ipython/ipykernel/pull/1317) ([@Carreau](https://github.com/Carreau)) +- Remove base setup [#1299](https://github.com/ipython/ipykernel/pull/1299) ([@davidbrochart](https://github.com/davidbrochart)) +- Rely on intrsphinx_registry to keep intersphinx up to date. [#1290](https://github.com/ipython/ipykernel/pull/1290) ([@Carreau](https://github.com/Carreau)) +- Drop support for Python 3.8 [#1284](https://github.com/ipython/ipykernel/pull/1284) ([@ianthomas23](https://github.com/ianthomas23)) +- Start testing on 3.13 [#1277](https://github.com/ipython/ipykernel/pull/1277) ([@Carreau](https://github.com/Carreau)) +- Build docs on ubuntu [#1257](https://github.com/ipython/ipykernel/pull/1257) ([@blink1073](https://github.com/blink1073)) - Avoid a DeprecationWarning on Python 3.13+ [#1248](https://github.com/ipython/ipykernel/pull/1248) ([@hroncok](https://github.com/hroncok)) +- Catch IPython 8.24 DeprecationWarnings [#1242](https://github.com/ipython/ipykernel/pull/1242) ([@s-t-e-v-e-n-k](https://github.com/s-t-e-v-e-n-k)) +- Add compat with pytest 8 [#1231](https://github.com/ipython/ipykernel/pull/1231) ([@blink1073](https://github.com/blink1073)) +- Set all min deps [#1229](https://github.com/ipython/ipykernel/pull/1229) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release -([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-07-01&to=2025-06-05&type=c)) +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-07-01&to=2025-07-21&type=c)) -[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2024-07-01..2025-06-05&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2024-07-01..2025-06-05&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-07-01..2025-06-05&type=Issues) | [@dby-tmwctw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adby-tmwctw+updated%3A2024-07-01..2025-06-05&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-07-01..2025-06-05&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2024-07-01..2025-06-05&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2024-07-01..2025-06-05&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-07-01..2025-06-05&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ameeseeksmachine+updated%3A2024-07-01..2025-06-05&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-07-01..2025-06-05&type=Issues) +[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2024-07-01..2025-07-21&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2024-07-01..2025-07-21&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-07-01..2025-07-21&type=Issues) | [@dby-tmwctw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adby-tmwctw+updated%3A2024-07-01..2025-07-21&type=Issues) | [@gregory-shklover](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Agregory-shklover+updated%3A2024-07-01..2025-07-21&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-07-01..2025-07-21&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2024-07-01..2025-07-21&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2024-07-01..2025-07-21&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-07-01..2025-07-21&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ameeseeksmachine+updated%3A2024-07-01..2025-07-21&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-07-01..2025-07-21&type=Issues) | [@nathanmcavoy](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anathanmcavoy+updated%3A2024-07-01..2025-07-21&type=Issues) | [@s-t-e-v-e-n-k](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3As-t-e-v-e-n-k+updated%3A2024-07-01..2025-07-21&type=Issues) ## 6.29.5 From 5b8ac292dabd824de99f36a6c902fb43fd53681c Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 17 Sep 2025 12:47:26 +0100 Subject: [PATCH 1094/1195] Drop support for Python 3.9 (#1431) --- .github/workflows/ci.yml | 5 ++--- .github/workflows/downstream.yml | 6 ++++-- ipykernel/iostream.py | 9 +++++---- ipykernel/jsonutil.py | 2 +- pyproject.toml | 2 +- tests/test_kernel.py | 2 +- tests/test_subshells.py | 2 +- 7 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d2a39eb1c..4debd4891 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,6 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: - - "3.9" - "3.10" - "3.11" - "3.12" @@ -122,7 +121,7 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - python-version: ["3.9"] + python-version: ["3.10"] steps: - name: Checkout uses: actions/checkout@v5 @@ -153,7 +152,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: dependency_type: minimum - python_version: "3.9" + python_version: "3.10" - name: List installed packages run: | diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index ced8b2d56..aaa6b1913 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -90,6 +90,7 @@ jobs: qtconsole: runs-on: ubuntu-latest + if: false timeout-minutes: 20 steps: - name: Checkout @@ -97,7 +98,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: "3.9" + python-version: "3.10" architecture: "x64" - name: Install System Packages run: | @@ -122,6 +123,7 @@ jobs: spyder_kernels: runs-on: ubuntu-latest + if: false timeout-minutes: 20 steps: - name: Checkout @@ -129,7 +131,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v6 with: - python-version: "3.9" + python-version: "3.10" architecture: "x64" - name: Install System Packages run: | diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 7e8301149..d9c90af4f 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -14,9 +14,10 @@ import warnings from binascii import b2a_hex from collections import defaultdict, deque +from collections.abc import Callable from io import StringIO, TextIOBase from threading import local -from typing import Any, Callable, Optional +from typing import Any import zmq from jupyter_client.session import extract_header @@ -70,7 +71,7 @@ def __init__(self, socket, pipe=False): self._event_pipes: dict[threading.Thread, Any] = {} self._event_pipe_gc_lock: threading.Lock = threading.Lock() self._event_pipe_gc_seconds: float = 10 - self._event_pipe_gc_task: Optional[asyncio.Task[Any]] = None + self._event_pipe_gc_task: asyncio.Task[Any] | None = None self._setup_event_pipe() self.thread = threading.Thread(target=self._thread_main, name="IOPub") self.thread.daemon = True @@ -359,7 +360,7 @@ class OutStream(TextIOBase): flush_interval = 0.2 topic = None encoding = "UTF-8" - _exc: Optional[Any] = None + _exc: Any | None = None def fileno(self): """ @@ -658,7 +659,7 @@ def _flush(self): ident=self.topic, ) - def write(self, string: str) -> Optional[int]: # type:ignore[override] + def write(self, string: str) -> int | None: # type:ignore[override] """Write to current stream after encoding if necessary Returns diff --git a/ipykernel/jsonutil.py b/ipykernel/jsonutil.py index e45f06e53..d40661c74 100644 --- a/ipykernel/jsonutil.py +++ b/ipykernel/jsonutil.py @@ -156,7 +156,7 @@ def json_clean(obj): # pragma: no cover for k, v in obj.items(): out[str(k)] = json_clean(v) return out - if isinstance(obj, (datetime, date)): + if isinstance(obj, datetime | date): return obj.strftime(ISO8601) # we don't understand it, it's probably an unserializable object diff --git a/pyproject.toml b/pyproject.toml index f00ea205f..57d937d44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", ] -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "debugpy>=1.6.5", "ipython>=7.23.1", diff --git a/tests/test_kernel.py b/tests/test_kernel.py index ae8c621d3..1e3722630 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -634,7 +634,7 @@ def ensure_datetime(arg): # Check messages are processed in order, one at a time, and of a sensible duration. previous_end = None - for reply, sleep in zip(replies, sleeps): + for reply, sleep in zip(replies, sleeps, strict=False): start = ensure_datetime(reply["metadata"]["started"]) end = ensure_datetime(reply["header"]["date"]) diff --git a/tests/test_subshells.py b/tests/test_subshells.py index 4d906d44b..419c54dbf 100644 --- a/tests/test_subshells.py +++ b/tests/test_subshells.py @@ -164,7 +164,7 @@ def test_run_concurrently_sequence(are_subshells, overlap, request): ] msgs = [] - for subshell_id, code in zip(subshell_ids, codes): + for subshell_id, code in zip(subshell_ids, codes, strict=False): msg = kc.session.msg("execute_request", {"code": code}) msg["header"]["subshell_id"] = subshell_id kc.shell_channel.send(msg) From 7b41bc6bfca1e00b903f386e76dce1eda97b0907 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 29 Sep 2025 09:41:10 +0100 Subject: [PATCH 1095/1195] Use asyncio.Lock around subshell message handling (#1430) --- .github/workflows/downstream.yml | 1 - ipykernel/kernelbase.py | 74 ++++++++++++++++---------------- ipykernel/shellchannel.py | 3 ++ ipykernel/socket_pair.py | 16 +------ ipykernel/subshell.py | 3 ++ ipykernel/subshell_manager.py | 15 ++++--- 6 files changed, 54 insertions(+), 58 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index aaa6b1913..63501c6a8 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -90,7 +90,6 @@ jobs: qtconsole: runs-on: ubuntu-latest - if: false timeout-minutes: 20 steps: - name: Checkout diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index aceaf3eb9..b815b934b 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -205,6 +205,9 @@ def _default_ident(self): # see https://github.com/jupyterlab/jupyterlab/issues/17785 _parent_ident: Mapping[str, bytes] + # Asyncio lock for main shell thread. + _main_asyncio_lock: asyncio.Lock + @property def _parent_header(self): warnings.warn( @@ -327,6 +330,8 @@ def __init__(self, **kwargs): } ) + self._main_asyncio_lock = asyncio.Lock() + async def dispatch_control(self, msg): """Dispatch a control request, ensuring only one message is processed at a time.""" # Ensure only one control message is processed at a time @@ -539,7 +544,7 @@ async def do_one_iteration(self): This is now a coroutine """ # flush messages off of shell stream into the message queue - if self.shell_stream: + if self.shell_stream and not self._supports_kernel_subshells: self.shell_stream.flush() # process at most one shell message per iteration await self.process_one(wait=False) @@ -649,56 +654,51 @@ async def shell_channel_thread_main(self, msg): """Handler for shell messages received on shell_channel_thread""" assert threading.current_thread() == self.shell_channel_thread - if self.session is None: - return - - # deserialize only the header to get subshell_id - # Keep original message to send to subshell_id unmodified. - _, msg2 = self.session.feed_identities(msg, copy=False) - try: - msg3 = self.session.deserialize(msg2, content=False, copy=False) - subshell_id = msg3["header"].get("subshell_id") - - # Find inproc pair socket to use to send message to correct subshell. - subshell_manager = self.shell_channel_thread.manager - socket = subshell_manager.get_shell_channel_to_subshell_socket(subshell_id) - assert socket is not None - socket.send_multipart(msg, copy=False) - except Exception: - self.log.error("Invalid message", exc_info=True) # noqa: G201 + async with self.shell_channel_thread.asyncio_lock: + if self.session is None: + return - if self.shell_stream: - self.shell_stream.flush() + # deserialize only the header to get subshell_id + # Keep original message to send to subshell_id unmodified. + _, msg2 = self.session.feed_identities(msg, copy=False) + try: + msg3 = self.session.deserialize(msg2, content=False, copy=False) + subshell_id = msg3["header"].get("subshell_id") + + # Find inproc pair socket to use to send message to correct subshell. + subshell_manager = self.shell_channel_thread.manager + socket = subshell_manager.get_shell_channel_to_subshell_socket(subshell_id) + assert socket is not None + socket.send_multipart(msg, copy=False) + except Exception: + self.log.error("Invalid message", exc_info=True) # noqa: G201 async def shell_main(self, subshell_id: str | None, msg): """Handler of shell messages for a single subshell""" if self._supports_kernel_subshells: if subshell_id is None: assert threading.current_thread() == threading.main_thread() + asyncio_lock = self._main_asyncio_lock else: assert threading.current_thread() not in ( self.shell_channel_thread, threading.main_thread(), ) - socket_pair = self.shell_channel_thread.manager.get_shell_channel_to_subshell_pair( - subshell_id - ) + asyncio_lock = self.shell_channel_thread.manager.get_subshell_asyncio_lock( + subshell_id + ) else: assert subshell_id is None assert threading.current_thread() == threading.main_thread() - socket_pair = None - - try: - # Whilst executing a shell message, do not accept any other shell messages on the - # same subshell, so that cells are run sequentially. Without this we can run multiple - # async cells at the same time which would be a nice feature to have but is an API - # change. - if socket_pair: - socket_pair.pause_on_recv() + asyncio_lock = self._main_asyncio_lock + + # Whilst executing a shell message, do not accept any other shell messages on the + # same subshell, so that cells are run sequentially. Without this we can run multiple + # async cells at the same time which would be a nice feature to have but is an API + # change. + assert asyncio_lock is not None + async with asyncio_lock: await self.dispatch_shell(msg, subshell_id=subshell_id) - finally: - if socket_pair: - socket_pair.resume_on_recv() def record_ports(self, ports): """Record the ports that this kernel is using. @@ -739,7 +739,7 @@ def _publish_status(self, status, channel, parent=None): def _publish_status_and_flush(self, status, channel, stream, parent=None): """send status on IOPub and flush specified stream to ensure reply is sent before handling the next reply""" self._publish_status(status, channel, parent) - if stream and hasattr(stream, "flush"): + if stream and hasattr(stream, "flush") and not self._supports_kernel_subshells: stream.flush(zmq.POLLOUT) def _publish_debug_event(self, event): @@ -1382,7 +1382,7 @@ def _abort_queues(self, subshell_id: str | None = None): # flush streams, so all currently waiting messages # are added to the queue - if self.shell_stream: + if self.shell_stream and not self._supports_kernel_subshells: self.shell_stream.flush() # Callback to signal that we are done aborting diff --git a/ipykernel/shellchannel.py b/ipykernel/shellchannel.py index 12102e870..32f5f7399 100644 --- a/ipykernel/shellchannel.py +++ b/ipykernel/shellchannel.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio from typing import Any import zmq @@ -28,6 +29,8 @@ def __init__( self._context = context self._shell_socket = shell_socket + self.asyncio_lock = asyncio.Lock() + @property def manager(self) -> SubshellManager: # Lazy initialisation. diff --git a/ipykernel/socket_pair.py b/ipykernel/socket_pair.py index e2669b8c0..f31dd3b92 100644 --- a/ipykernel/socket_pair.py +++ b/ipykernel/socket_pair.py @@ -21,8 +21,6 @@ class SocketPair: from_socket: zmq.Socket[Any] to_socket: zmq.Socket[Any] to_stream: ZMQStream | None = None - on_recv_callback: Any - on_recv_copy: bool def __init__(self, context: zmq.Context[Any], name: str): """Initialize the inproc socker pair.""" @@ -43,21 +41,9 @@ def close(self): def on_recv(self, io_loop: IOLoop, on_recv_callback, copy: bool = False): """Set the callback used when a message is received on the to stream.""" # io_loop is that of the 'to' thread. - self.on_recv_callback = on_recv_callback - self.on_recv_copy = copy if self.to_stream is None: self.to_stream = ZMQStream(self.to_socket, io_loop) - self.resume_on_recv() - - def pause_on_recv(self): - """Pause receiving on the to stream.""" - if self.to_stream is not None: - self.to_stream.stop_on_recv() - - def resume_on_recv(self): - """Resume receiving on the to stream.""" - if self.to_stream is not None and not self.to_stream.closed(): - self.to_stream.on_recv(self.on_recv_callback, copy=self.on_recv_copy) + self.to_stream.on_recv(on_recv_callback, copy=copy) def _address(self, name) -> str: """Return the address used for this inproc socket pair.""" diff --git a/ipykernel/subshell.py b/ipykernel/subshell.py index 911a9521c..ec1c7cc88 100644 --- a/ipykernel/subshell.py +++ b/ipykernel/subshell.py @@ -1,5 +1,6 @@ """A thread for a subshell.""" +import asyncio from typing import Any import zmq @@ -29,6 +30,8 @@ def __init__( # When aborting flag is set, execute_request messages to this subshell will be aborted. self.aborting = False + self.asyncio_lock = asyncio.Lock() + def run(self) -> None: """Run the thread.""" try: diff --git a/ipykernel/subshell_manager.py b/ipykernel/subshell_manager.py index 24d683523..7f65183dd 100644 --- a/ipykernel/subshell_manager.py +++ b/ipykernel/subshell_manager.py @@ -2,6 +2,7 @@ from __future__ import annotations +import asyncio import json import typing as t import uuid @@ -46,8 +47,7 @@ def __init__( self._shell_channel_io_loop = shell_channel_io_loop self._shell_socket = shell_socket self._cache: dict[str, SubshellThread] = {} - self._lock_cache = Lock() - self._lock_shell_socket = Lock() + self._lock_cache = Lock() # Sync lock across threads when accessing cache. # Inproc socket pair for communication from control thread to shell channel thread, # such as for create_subshell_request messages. Reply messages are returned straight away. @@ -107,7 +107,13 @@ def get_shell_channel_to_subshell_socket(self, subshell_id: str | None) -> zmq.S def get_subshell_aborting(self, subshell_id: str) -> bool: """Get the boolean aborting flag of the specified subshell.""" - return self._cache[subshell_id].aborting + with self._lock_cache: + return self._cache[subshell_id].aborting + + def get_subshell_asyncio_lock(self, subshell_id: str) -> asyncio.Lock: + """Return the asyncio lock belonging to the specified subshell.""" + with self._lock_cache: + return self._cache[subshell_id].asyncio_lock def list_subshell(self) -> list[str]: """Return list of current subshell ids. @@ -216,8 +222,7 @@ def _process_control_request( def _send_on_shell_channel(self, msg) -> None: assert current_thread().name == SHELL_CHANNEL_THREAD_NAME - with self._lock_shell_socket: - self._shell_socket.send_multipart(msg) + self._shell_socket.send_multipart(msg) def _stop_subshell(self, subshell_thread: SubshellThread) -> None: """Stop a subshell thread and close all of its resources.""" From 8a574d6cf60148b84ab0bc0f2d9e19dc5e1b68b7 Mon Sep 17 00:00:00 2001 From: ianthomas23 Date: Fri, 3 Oct 2025 12:11:38 +0000 Subject: [PATCH 1096/1195] Publish 7.0.0a3 SHA256 hashes: ipykernel-7.0.0a3-py3-none-any.whl: 997c7a09d2340d09cd7dc1dfc7a5fd5926be09266906954864bc6e236464be9f ipykernel-7.0.0a3.tar.gz: 70da018600db0bb8d03a5ade7b49979829c6515efb5e98b739f6ece585e503d6 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d7ff1284..55e2c0aef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ +## 7.0.0a3 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.0.0a2...7b41bc6bfca1e00b903f386e76dce1eda97b0907)) + +### Enhancements made + +- Use asyncio.Lock around subshell message handling [#1430](https://github.com/ipython/ipykernel/pull/1430) ([@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- Drop support for Python 3.9 [#1431](https://github.com/ipython/ipykernel/pull/1431) ([@ianthomas23](https://github.com/ianthomas23)) +- Bump the actions group across 1 directory with 2 updates [#1428](https://github.com/ipython/ipykernel/pull/1428) ([@dependabot](https://github.com/dependabot)) +- Continue to support `Kernel._parent_ident` for backward compatibility [#1427](https://github.com/ipython/ipykernel/pull/1427) ([@ianthomas23](https://github.com/ianthomas23)) +- Tests: Add robust tests for silent flag in subshells [#1424](https://github.com/ipython/ipykernel/pull/1424) ([@pankaj-bind](https://github.com/pankaj-bind)) + +### Documentation improvements + +- Forward port of 6.x changelog to main branch [#1429](https://github.com/ipython/ipykernel/pull/1429) ([@ianthomas23](https://github.com/ianthomas23)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2025-08-13&to=2025-10-03&type=c)) + +[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-08-13..2025-10-03&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2025-08-13..2025-10-03&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2025-08-13..2025-10-03&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2025-08-13..2025-10-03&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-08-13..2025-10-03&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2025-08-13..2025-10-03&type=Issues) | [@pankaj-bind](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apankaj-bind+updated%3A2025-08-13..2025-10-03&type=Issues) + + + ## 7.0.0a2 Pre-release of `ipykernel` with experimental subshell support on top of `tornado`/`asyncio`. The previous version 7 pre-releases used `anyio` but that will no longer be in `ipykernel` 7. @@ -126,8 +153,6 @@ Note the changelog here is not correct, presumably due to the recent branch rena [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2024-07-01..2025-08-13&type=Issues) | [@bluss](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abluss+updated%3A2024-07-01..2025-08-13&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2024-07-01..2025-08-13&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2024-07-01..2025-08-13&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-07-01..2025-08-13&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2024-07-01..2025-08-13&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-07-01..2025-08-13&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2024-07-01..2025-08-13&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2024-07-01..2025-08-13&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-07-01..2025-08-13&type=Issues) | [@limwz01](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Alimwz01+updated%3A2024-07-01..2025-08-13&type=Issues) | [@mgorny](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amgorny+updated%3A2024-07-01..2025-08-13&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-07-01..2025-08-13&type=Issues) | [@nathanmcavoy](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anathanmcavoy+updated%3A2024-07-01..2025-08-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2024-07-01..2025-08-13&type=Issues) | [@smacke](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Asmacke+updated%3A2024-07-01..2025-08-13&type=Issues) - - ## 6.30.1 This is a bugfix release to fix a significant bug introduced in 6.30.0 that allowed control messages to be handled concurrently rather than sequentially which broke debugging in JupyterLab and VSCode. diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 5857570f7..75109f679 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "7.0.0a2" +__version__ = "7.0.0a3" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From a21e3de4702c8d97dae2b2d0d01c916a6fbbb2c2 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 10 Oct 2025 11:04:58 +0100 Subject: [PATCH 1097/1195] Reinstate ipyparallel downstream tests (#1426) --- .github/workflows/downstream.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 63501c6a8..e02ba68e3 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -56,7 +56,6 @@ jobs: test_command: "pytest -vv -raXxs -W default --durations 10 --color=yes -k 'not (test_input_request or signal_kernel_subprocess)'" ipyparallel: - if: false runs-on: ubuntu-latest timeout-minutes: 20 steps: From 6a14889a5fe8af989b033aa2cbe6476299020d75 Mon Sep 17 00:00:00 2001 From: ianthomas23 Date: Mon, 13 Oct 2025 11:23:38 +0000 Subject: [PATCH 1098/1195] Publish 7.0.0 SHA256 hashes: ipykernel-7.0.0-py3-none-any.whl: 28793cecaa6a669e3be80eb6d24803202388b6a955929b0a4e13404d8c92062b ipykernel-7.0.0.tar.gz: 06aef83f27adbce00b23345aa70f749f907dc4ac6f4a41fe7bf5f780dc506225 --- CHANGELOG.md | 52 +++++++++++++++++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55e2c0aef..e13fc7753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,56 @@ +## 7.0.0 + +IPykernel 7.0.0 is a major release containing experimental support for [kernel subshells](https://github.com/jupyter/enhancement-proposals/pull/91). + +If not using subshells it is intended to be backward compatible with the 6.x branch, but there are some architectural changes which may break assumptions made in downstream libraries and hence this is identified as a major releases as it is potentially backwards incompatible. If you experience problems then please report them, and you can pin `ipykernel < 7` if necessary. + +For further information and to report problems please see [ipykernel 7.0.0 release with subshells (issue 1438)](https://github.com/ipython/ipykernel/issues/1438). + +([Full Changelog](https://github.com/ipython/ipykernel/compare/7603443ba9...a21e3de4702c8d97dae2b2d0d01c916a6fbbb2c2)) + +### Enhancements made + +- Use asyncio.Lock around subshell message handling [#1430](https://github.com/ipython/ipykernel/pull/1430) ([@ianthomas23](https://github.com/ianthomas23)) +- Subshells implemented using tornado event loops on 6.x branch [#1396](https://github.com/ipython/ipykernel/pull/1396) ([@ianthomas23](https://github.com/ianthomas23)) + +### Bugs fixed + +- Forward port from 6.x: Correct use of asyncio.Lock to process a single control message at a time [#1418](https://github.com/ipython/ipykernel/pull/1418) ([@ianthomas23](https://github.com/ianthomas23)) +- Cache separate headers on subshell threads [#1414](https://github.com/ipython/ipykernel/pull/1414) ([@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- Drop support for Python 3.9 [#1431](https://github.com/ipython/ipykernel/pull/1431) ([@ianthomas23](https://github.com/ianthomas23)) +- Bump the actions group across 1 directory with 2 updates [#1428](https://github.com/ipython/ipykernel/pull/1428) ([@dependabot](https://github.com/dependabot)) +- Continue to support `Kernel._parent_ident` for backward compatibility [#1427](https://github.com/ipython/ipykernel/pull/1427) ([@ianthomas23](https://github.com/ianthomas23)) +- Reinstate ipyparallel downstream tests [#1426](https://github.com/ipython/ipykernel/pull/1426) ([@ianthomas23](https://github.com/ianthomas23)) +- Reinstate jupyter_client downstream tests with exclusions [#1425](https://github.com/ipython/ipykernel/pull/1425) ([@ianthomas23](https://github.com/ianthomas23)) +- Tests: Add robust tests for silent flag in subshells [#1424](https://github.com/ipython/ipykernel/pull/1424) ([@pankaj-bind](https://github.com/pankaj-bind)) +- Use correct `__version__` on `main` branch after branch manipulations [#1419](https://github.com/ipython/ipykernel/pull/1419) ([@ianthomas23](https://github.com/ianthomas23)) +- Remove links in changelog to github milestones that no longer exist [#1415](https://github.com/ipython/ipykernel/pull/1415) ([@ianthomas23](https://github.com/ianthomas23)) +- Replace `@flaky.flaky` decorate with pytest fixture [#1411](https://github.com/ipython/ipykernel/pull/1411) ([@mgorny](https://github.com/mgorny)) +- chore: update pre-commit hooks [#1409](https://github.com/ipython/ipykernel/pull/1409) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- Bump scientific-python/upload-nightly-action from 0.6.1 to 0.6.2 in the actions group [#1404](https://github.com/ipython/ipykernel/pull/1404) ([@dependabot](https://github.com/dependabot)) +- Backports from `anyio` (old `main`) branch to `main` branch [#1402](https://github.com/ipython/ipykernel/pull/1402) ([@ianthomas23](https://github.com/ianthomas23)) +- Update pre-commit and github actions [#1401](https://github.com/ipython/ipykernel/pull/1401) ([@ianthomas23](https://github.com/ianthomas23)) +- Test more python versions on 6.x branch [#1398](https://github.com/ipython/ipykernel/pull/1398) ([@davidbrochart](https://github.com/davidbrochart)) + +### Documentation improvements + +- Forward port of 6.x changelog to main branch [#1429](https://github.com/ipython/ipykernel/pull/1429) ([@ianthomas23](https://github.com/ianthomas23)) +- Add subshell docstrings [#1405](https://github.com/ipython/ipykernel/pull/1405) ([@ianthomas23](https://github.com/ianthomas23)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2025-05-16&to=2025-10-13&type=c)) + +[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-05-16..2025-10-13&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2025-05-16..2025-10-13&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2025-05-16..2025-10-13&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2025-05-16..2025-10-13&type=Issues) | [@fleming79](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afleming79+updated%3A2025-05-16..2025-10-13&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-05-16..2025-10-13&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2025-05-16..2025-10-13&type=Issues) | [@mgorny](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amgorny+updated%3A2025-05-16..2025-10-13&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2025-05-16..2025-10-13&type=Issues) | [@pankaj-bind](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apankaj-bind+updated%3A2025-05-16..2025-10-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2025-05-16..2025-10-13&type=Issues) + + + ## 7.0.0a3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.0.0a2...7b41bc6bfca1e00b903f386e76dce1eda97b0907)) @@ -27,8 +77,6 @@ [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-08-13..2025-10-03&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2025-08-13..2025-10-03&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2025-08-13..2025-10-03&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2025-08-13..2025-10-03&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-08-13..2025-10-03&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2025-08-13..2025-10-03&type=Issues) | [@pankaj-bind](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apankaj-bind+updated%3A2025-08-13..2025-10-03&type=Issues) - - ## 7.0.0a2 Pre-release of `ipykernel` with experimental subshell support on top of `tornado`/`asyncio`. The previous version 7 pre-releases used `anyio` but that will no longer be in `ipykernel` 7. diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 75109f679..d78f3018c 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "7.0.0a3" +__version__ = "7.0.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 60f74b1b717ec27af6e28e93c0fcb97fb9f0ad72 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 13 Oct 2025 12:56:09 +0100 Subject: [PATCH 1099/1195] Clean up changelog following 7.0.0 release (#1439) --- CHANGELOG.md | 149 --------------------------------------------------- 1 file changed, 149 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e13fc7753..c98a2caa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,155 +52,6 @@ For further information and to report problems please see [ipykernel 7.0.0 relea -## 7.0.0a3 - -([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.0.0a2...7b41bc6bfca1e00b903f386e76dce1eda97b0907)) - -### Enhancements made - -- Use asyncio.Lock around subshell message handling [#1430](https://github.com/ipython/ipykernel/pull/1430) ([@ianthomas23](https://github.com/ianthomas23)) - -### Maintenance and upkeep improvements - -- Drop support for Python 3.9 [#1431](https://github.com/ipython/ipykernel/pull/1431) ([@ianthomas23](https://github.com/ianthomas23)) -- Bump the actions group across 1 directory with 2 updates [#1428](https://github.com/ipython/ipykernel/pull/1428) ([@dependabot](https://github.com/dependabot)) -- Continue to support `Kernel._parent_ident` for backward compatibility [#1427](https://github.com/ipython/ipykernel/pull/1427) ([@ianthomas23](https://github.com/ianthomas23)) -- Tests: Add robust tests for silent flag in subshells [#1424](https://github.com/ipython/ipykernel/pull/1424) ([@pankaj-bind](https://github.com/pankaj-bind)) - -### Documentation improvements - -- Forward port of 6.x changelog to main branch [#1429](https://github.com/ipython/ipykernel/pull/1429) ([@ianthomas23](https://github.com/ianthomas23)) - -### Contributors to this release - -([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2025-08-13&to=2025-10-03&type=c)) - -[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-08-13..2025-10-03&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2025-08-13..2025-10-03&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2025-08-13..2025-10-03&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2025-08-13..2025-10-03&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-08-13..2025-10-03&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2025-08-13..2025-10-03&type=Issues) | [@pankaj-bind](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apankaj-bind+updated%3A2025-08-13..2025-10-03&type=Issues) - -## 7.0.0a2 - -Pre-release of `ipykernel` with experimental subshell support on top of `tornado`/`asyncio`. The previous version 7 pre-releases used `anyio` but that will no longer be in `ipykernel` 7. - -Note the changelog here is not correct, presumably due to the recent branch renaming. For the full release this will have to be manually curated so that it is accurate. The "Full Changelog" link below is accurate. - -([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.5...e34aedbed0a84ae9ab728634766a8120ea631647)) - -### Enhancements made - -- Replace BaseThread's add_task with start_soon [#1300](https://github.com/ipython/ipykernel/pull/1300) ([@davidbrochart](https://github.com/davidbrochart)) -- Use supported_features=['debugger'] in kernel info reply [#1296](https://github.com/ipython/ipykernel/pull/1296) ([@ianthomas23](https://github.com/ianthomas23)) -- Use zmq-anyio [#1291](https://github.com/ipython/ipykernel/pull/1291) ([@davidbrochart](https://github.com/davidbrochart)) -- Detect parent change in more cases on unix [#1271](https://github.com/ipython/ipykernel/pull/1271) ([@bluss](https://github.com/bluss)) -- Kernel subshells (JEP91) implementation [#1249](https://github.com/ipython/ipykernel/pull/1249) ([@ianthomas23](https://github.com/ianthomas23)) - -### Bugs fixed - -- Forward port from 6.x: Correct use of asyncio.Lock to process a single control message at a time [#1418](https://github.com/ipython/ipykernel/pull/1418) ([@ianthomas23](https://github.com/ianthomas23)) -- Cache separate headers on subshell threads [#1414](https://github.com/ipython/ipykernel/pull/1414) ([@ianthomas23](https://github.com/ianthomas23)) -- Fix OutStream using \_fid before being defined [#1373](https://github.com/ipython/ipykernel/pull/1373) ([@davidbrochart](https://github.com/davidbrochart)) -- Make kernelbase.\_eventloop_set event thread-safe [#1366](https://github.com/ipython/ipykernel/pull/1366) ([@davidbrochart](https://github.com/davidbrochart)) -- Remove implicit bind_kernel in `%qtconsole` [#1315](https://github.com/ipython/ipykernel/pull/1315) ([@minrk](https://github.com/minrk)) -- Fix ipykernel install [#1310](https://github.com/ipython/ipykernel/pull/1310) ([@davidbrochart](https://github.com/davidbrochart)) -- socket must be None, not shell_socket for default shell [#1281](https://github.com/ipython/ipykernel/pull/1281) ([@minrk](https://github.com/minrk)) -- restore zero-copy recv on shell messages [#1280](https://github.com/ipython/ipykernel/pull/1280) ([@minrk](https://github.com/minrk)) -- Fix eventloop integration with anyio [#1265](https://github.com/ipython/ipykernel/pull/1265) ([@ianthomas23](https://github.com/ianthomas23)) - -### Maintenance and upkeep improvements - -- Reinstate jupyter_client downstream tests with exclusions [#1425](https://github.com/ipython/ipykernel/pull/1425) ([@ianthomas23](https://github.com/ianthomas23)) -- Use correct `__version__` on `main` branch after branch manipulations [#1419](https://github.com/ipython/ipykernel/pull/1419) ([@ianthomas23](https://github.com/ianthomas23)) -- Remove links in changelog to github milestones that no longer exist [#1415](https://github.com/ipython/ipykernel/pull/1415) ([@ianthomas23](https://github.com/ianthomas23)) -- Replace `@flaky.flaky` decorate with pytest fixture [#1411](https://github.com/ipython/ipykernel/pull/1411) ([@mgorny](https://github.com/mgorny)) -- chore: update pre-commit hooks [#1409](https://github.com/ipython/ipykernel/pull/1409) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- Bump scientific-python/upload-nightly-action from 0.6.1 to 0.6.2 in the actions group [#1404](https://github.com/ipython/ipykernel/pull/1404) ([@dependabot](https://github.com/dependabot)) -- Backports from `anyio` (old `main`) branch to `main` branch [#1402](https://github.com/ipython/ipykernel/pull/1402) ([@ianthomas23](https://github.com/ianthomas23)) -- Update pre-commit and github actions [#1401](https://github.com/ipython/ipykernel/pull/1401) ([@ianthomas23](https://github.com/ianthomas23)) -- Add security.md [#1394](https://github.com/ipython/ipykernel/pull/1394) ([@Carreau](https://github.com/Carreau)) -- chore: update pre-commit hooks [#1388](https://github.com/ipython/ipykernel/pull/1388) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- chore: update pre-commit hooks [#1385](https://github.com/ipython/ipykernel/pull/1385) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- chore: update pre-commit hooks [#1383](https://github.com/ipython/ipykernel/pull/1383) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- chore: update pre-commit hooks [#1378](https://github.com/ipython/ipykernel/pull/1378) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- chore: update pre-commit hooks [#1375](https://github.com/ipython/ipykernel/pull/1375) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- chore: update pre-commit hooks [#1372](https://github.com/ipython/ipykernel/pull/1372) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- Remove nose import. [#1368](https://github.com/ipython/ipykernel/pull/1368) ([@Carreau](https://github.com/Carreau)) -- TQDM workaround due to unresponsive maintainer [#1363](https://github.com/ipython/ipykernel/pull/1363) ([@Carreau](https://github.com/Carreau)) -- chore: update pre-commit hooks [#1355](https://github.com/ipython/ipykernel/pull/1355) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- Fix expected text depending on IPython version. [#1354](https://github.com/ipython/ipykernel/pull/1354) ([@Carreau](https://github.com/Carreau)) -- Another try at tracking down ResourceWarning with tracemalloc. [#1353](https://github.com/ipython/ipykernel/pull/1353) ([@Carreau](https://github.com/Carreau)) -- Remove deprecated modules since 4.3 (2016). [#1352](https://github.com/ipython/ipykernel/pull/1352) ([@Carreau](https://github.com/Carreau)) -- Try to re-enable tests from downstream ipywidgets [#1350](https://github.com/ipython/ipykernel/pull/1350) ([@Carreau](https://github.com/Carreau)) -- Disable 3 failing downstream tests, but keep testing the rest. [#1349](https://github.com/ipython/ipykernel/pull/1349) ([@Carreau](https://github.com/Carreau)) -- Licence :: * trove classifiers are deprecated [#1348](https://github.com/ipython/ipykernel/pull/1348) ([@Carreau](https://github.com/Carreau)) -- Pin sphinx to resolve docs build failures [#1347](https://github.com/ipython/ipykernel/pull/1347) ([@krassowski](https://github.com/krassowski)) -- Make our own mock kernel methods async [#1346](https://github.com/ipython/ipykernel/pull/1346) ([@Carreau](https://github.com/Carreau)) -- Try to debug non-closed iopub socket [#1345](https://github.com/ipython/ipykernel/pull/1345) ([@Carreau](https://github.com/Carreau)) -- Email is @python.org since 2018 [#1343](https://github.com/ipython/ipykernel/pull/1343) ([@Carreau](https://github.com/Carreau)) -- Remove unused ignores lints. [#1342](https://github.com/ipython/ipykernel/pull/1342) ([@Carreau](https://github.com/Carreau)) -- Enable ruff G002 and fix 6 occurrences [#1341](https://github.com/ipython/ipykernel/pull/1341) ([@Carreau](https://github.com/Carreau)) -- Check ignores warnings are still relevant. [#1340](https://github.com/ipython/ipykernel/pull/1340) ([@Carreau](https://github.com/Carreau)) -- Move mypy disablinging error codes on a per-file basis [#1338](https://github.com/ipython/ipykernel/pull/1338) ([@Carreau](https://github.com/Carreau)) -- try to fix spyder kernel install [#1337](https://github.com/ipython/ipykernel/pull/1337) ([@Carreau](https://github.com/Carreau)) -- Remove test_check job [#1335](https://github.com/ipython/ipykernel/pull/1335) ([@Carreau](https://github.com/Carreau)) -- Refine deprecation error messages. [#1334](https://github.com/ipython/ipykernel/pull/1334) ([@Carreau](https://github.com/Carreau)) -- Bump mypy [#1333](https://github.com/ipython/ipykernel/pull/1333) ([@Carreau](https://github.com/Carreau)) -- Remove dead code. [#1332](https://github.com/ipython/ipykernel/pull/1332) ([@Carreau](https://github.com/Carreau)) -- Ignore or fix most of the remaining ruff 0.9.6 errors [#1331](https://github.com/ipython/ipykernel/pull/1331) ([@Carreau](https://github.com/Carreau)) -- minor code reformatting valid ruff 0.9.6 [#1330](https://github.com/ipython/ipykernel/pull/1330) ([@Carreau](https://github.com/Carreau)) -- Some formatting changes to prepare bumping ruff pre-commit. [#1329](https://github.com/ipython/ipykernel/pull/1329) ([@Carreau](https://github.com/Carreau)) -- Manually update Codespell and fix new errors. [#1328](https://github.com/ipython/ipykernel/pull/1328) ([@Carreau](https://github.com/Carreau)) -- Manually update mdformat pre-commit and run it. [#1327](https://github.com/ipython/ipykernel/pull/1327) ([@Carreau](https://github.com/Carreau)) -- Manually update pre-commit hooks that do not trigger new errors/fixes. [#1326](https://github.com/ipython/ipykernel/pull/1326) ([@Carreau](https://github.com/Carreau)) -- Try to force precommit-ci to send autoupdate PRs. [#1325](https://github.com/ipython/ipykernel/pull/1325) ([@Carreau](https://github.com/Carreau)) -- Don't rerun test with --lf it hides failures. [#1324](https://github.com/ipython/ipykernel/pull/1324) ([@Carreau](https://github.com/Carreau)) -- Delete always skipped test, fix trio test, fix framelocal has not .clear() [#1322](https://github.com/ipython/ipykernel/pull/1322) ([@Carreau](https://github.com/Carreau)) -- Fix types lints [#1321](https://github.com/ipython/ipykernel/pull/1321) ([@Carreau](https://github.com/Carreau)) -- Remove link to numfocus for funding. [#1320](https://github.com/ipython/ipykernel/pull/1320) ([@Carreau](https://github.com/Carreau)) -- Remove downstream_check [#1318](https://github.com/ipython/ipykernel/pull/1318) ([@Carreau](https://github.com/Carreau)) -- Copy payloadpage.page from IPython [#1317](https://github.com/ipython/ipykernel/pull/1317) ([@Carreau](https://github.com/Carreau)) -- More Informative assert [#1314](https://github.com/ipython/ipykernel/pull/1314) ([@Carreau](https://github.com/Carreau)) -- Fix test_print_to_correct_cell_from_child_thread [#1312](https://github.com/ipython/ipykernel/pull/1312) ([@davidbrochart](https://github.com/davidbrochart)) -- make debugger class configurable [#1307](https://github.com/ipython/ipykernel/pull/1307) ([@smacke](https://github.com/smacke)) -- properly close OutStream and various fixes [#1305](https://github.com/ipython/ipykernel/pull/1305) ([@limwz01](https://github.com/limwz01)) -- Remove base setup [#1299](https://github.com/ipython/ipykernel/pull/1299) ([@davidbrochart](https://github.com/davidbrochart)) -- Suggest to make implementations of some function always return awaitable [#1295](https://github.com/ipython/ipykernel/pull/1295) ([@Carreau](https://github.com/Carreau)) -- Misc type annotations [#1294](https://github.com/ipython/ipykernel/pull/1294) ([@Carreau](https://github.com/Carreau)) -- Rely on intrsphinx_registry to keep intersphinx up to date. [#1290](https://github.com/ipython/ipykernel/pull/1290) ([@Carreau](https://github.com/Carreau)) -- Improve robustness of subshell concurrency tests using Barrier [#1288](https://github.com/ipython/ipykernel/pull/1288) ([@ianthomas23](https://github.com/ianthomas23)) -- Add 20 min timeout dowstream ipyparallel [#1287](https://github.com/ipython/ipykernel/pull/1287) ([@Carreau](https://github.com/Carreau)) -- Improve robustness of subshell concurrency tests [#1285](https://github.com/ipython/ipykernel/pull/1285) ([@ianthomas23](https://github.com/ianthomas23)) -- Drop support for Python 3.8 [#1284](https://github.com/ipython/ipykernel/pull/1284) ([@ianthomas23](https://github.com/ianthomas23)) -- remove deprecated ipyparallel methods now that they are broken anyway [#1282](https://github.com/ipython/ipykernel/pull/1282) ([@minrk](https://github.com/minrk)) -- start testing on 3.13 [#1277](https://github.com/ipython/ipykernel/pull/1277) ([@Carreau](https://github.com/Carreau)) -- Try to add workflow to publish nightlies [#1276](https://github.com/ipython/ipykernel/pull/1276) ([@Carreau](https://github.com/Carreau)) -- fix mixture of sync/async sockets in IOPubThread [#1275](https://github.com/ipython/ipykernel/pull/1275) ([@minrk](https://github.com/minrk)) -- Remove some potential dead-code. [#1273](https://github.com/ipython/ipykernel/pull/1273) ([@Carreau](https://github.com/Carreau)) -- Remove direct use of asyncio [#1266](https://github.com/ipython/ipykernel/pull/1266) ([@davidbrochart](https://github.com/davidbrochart)) -- Specify argtypes when using macos msg [#1264](https://github.com/ipython/ipykernel/pull/1264) ([@ianthomas23](https://github.com/ianthomas23)) -- Forward port changelog for 6.29.4 and 5 to main branch [#1263](https://github.com/ipython/ipykernel/pull/1263) ([@ianthomas23](https://github.com/ianthomas23)) -- Ignore warning from trio [#1262](https://github.com/ipython/ipykernel/pull/1262) ([@ianthomas23](https://github.com/ianthomas23)) -- Build docs on ubuntu [#1257](https://github.com/ipython/ipykernel/pull/1257) ([@blink1073](https://github.com/blink1073)) - -### Documentation improvements - -- Add subshell docstrings [#1405](https://github.com/ipython/ipykernel/pull/1405) ([@ianthomas23](https://github.com/ianthomas23)) -- Forward port changelog for 6.29.4 and 5 to main branch [#1263](https://github.com/ipython/ipykernel/pull/1263) ([@ianthomas23](https://github.com/ianthomas23)) - -### Deprecated features - -- Remove deprecated modules since 4.3 (2016). [#1352](https://github.com/ipython/ipykernel/pull/1352) ([@Carreau](https://github.com/Carreau)) -- Suggest to make implementations of some function always return awaitable [#1295](https://github.com/ipython/ipykernel/pull/1295) ([@Carreau](https://github.com/Carreau)) - -### Other merged PRs - -- Ensure test_start_app takes 1s to stop kernel [#1364](https://github.com/ipython/ipykernel/pull/1364) ([@davidbrochart](https://github.com/davidbrochart)) -- Test more python versions [#1358](https://github.com/ipython/ipykernel/pull/1358) ([@davidbrochart](https://github.com/davidbrochart)) - -### Contributors to this release - -([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-07-01&to=2025-08-13&type=c)) - -[@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2024-07-01..2025-08-13&type=Issues) | [@bluss](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abluss+updated%3A2024-07-01..2025-08-13&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2024-07-01..2025-08-13&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2024-07-01..2025-08-13&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-07-01..2025-08-13&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2024-07-01..2025-08-13&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-07-01..2025-08-13&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2024-07-01..2025-08-13&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2024-07-01..2025-08-13&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-07-01..2025-08-13&type=Issues) | [@limwz01](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Alimwz01+updated%3A2024-07-01..2025-08-13&type=Issues) | [@mgorny](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amgorny+updated%3A2024-07-01..2025-08-13&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-07-01..2025-08-13&type=Issues) | [@nathanmcavoy](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anathanmcavoy+updated%3A2024-07-01..2025-08-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2024-07-01..2025-08-13&type=Issues) | [@smacke](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Asmacke+updated%3A2024-07-01..2025-08-13&type=Issues) - ## 6.30.1 This is a bugfix release to fix a significant bug introduced in 6.30.0 that allowed control messages to be handled concurrently rather than sequentially which broke debugging in JupyterLab and VSCode. From 3b2e4c0be20d165a36b81030899a5917f499d80b Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Tue, 14 Oct 2025 16:26:09 +0200 Subject: [PATCH 1100/1195] ci: Test on PyPy 3.11 instead of 3.10 (#1444) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4debd4891..c3947ef31 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,7 +27,7 @@ jobs: - "3.11" - "3.12" - "3.13" - - "pypy-3.10" + - "pypy-3.11" steps: - name: Checkout uses: actions/checkout@v5 @@ -51,7 +51,7 @@ jobs: timeout-minutes: 15 if: ${{ startsWith( matrix.python-version, 'pypy' ) }} run: | - hatch run test:nowarn + hatch run test:nowarn --ignore=tests/test_debugger.py - name: Run the tests on Windows timeout-minutes: 15 From a3f73e1c101ee42e47cec990bb5f4226822bb9ee Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 14 Oct 2025 11:12:12 -0400 Subject: [PATCH 1101/1195] Fix 7.x license warnings (#1448) --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 57d937d44..c2b149d96 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [build-system] -requires = ["hatchling>=1.4", "jupyter_client>=6"] +requires = ["hatchling>=1.22", "jupyter_client>=6"] build-backend = "hatchling.build" [project] name = "ipykernel" dynamic = ["version"] authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] -license = {file = "LICENSE"} +license = "BSD-3-Clause" readme = "README.md" description = "IPython Kernel for Jupyter" keywords = ["Interactive", "Interpreter", "Shell", "Web"] From 6d9a14a21a8e328e384ebac48e4ccbaad85b1d45 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Tue, 14 Oct 2025 16:38:11 +0100 Subject: [PATCH 1102/1195] Avoid overriding Thread._context in Python 3.14 (#1447) --- .github/workflows/ci.yml | 2 ++ ipykernel/shellchannel.py | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c3947ef31..99b1f3304 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,8 @@ jobs: - "3.11" - "3.12" - "3.13" + - "3.14" + - "3.14t" - "pypy-3.11" steps: - name: Checkout diff --git a/ipykernel/shellchannel.py b/ipykernel/shellchannel.py index 32f5f7399..8335e8887 100644 --- a/ipykernel/shellchannel.py +++ b/ipykernel/shellchannel.py @@ -26,7 +26,7 @@ def __init__( """Initialize the thread.""" super().__init__(name=SHELL_CHANNEL_THREAD_NAME, **kwargs) self._manager: SubshellManager | None = None - self._context = context + self._zmq_context = context # Avoid use of self._context self._shell_socket = shell_socket self.asyncio_lock = asyncio.Lock() @@ -36,7 +36,7 @@ def manager(self) -> SubshellManager: # Lazy initialisation. if self._manager is None: self._manager = SubshellManager( - self._context, + self._zmq_context, self.io_loop, self._shell_socket, ) From f14b95bd54888eeaee4e70d9232e188638addc35 Mon Sep 17 00:00:00 2001 From: ianthomas23 Date: Tue, 14 Oct 2025 16:16:55 +0000 Subject: [PATCH 1103/1195] Publish 7.0.1 SHA256 hashes: ipykernel-7.0.1-py3-none-any.whl: 87182a8305e28954b6721087dec45b171712610111d494c17bb607befa1c4000 ipykernel-7.0.1.tar.gz: 2d3fd7cdef22071c2abbad78f142b743228c5d59cd470d034871ae0ac359533c --- CHANGELOG.md | 29 +++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c98a2caa8..7350310b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,33 @@ +## 7.0.1 + +IPykernel 7.0.1 is a bug fix release to support CPython 3.14. + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.0.0...6d9a14a21a8e328e384ebac48e4ccbaad85b1d45)) + +### Bugs fixed + +- Avoid overriding Thread.\_context in Python 3.14 [#1447](https://github.com/ipython/ipykernel/pull/1447) ([@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- Fix 7.x license warnings [#1448](https://github.com/ipython/ipykernel/pull/1448) ([@bollwyvl](https://github.com/bollwyvl)) +- ci: Test on PyPy 3.11 instead of 3.10 [#1444](https://github.com/ipython/ipykernel/pull/1444) ([@cclauss](https://github.com/cclauss)) + +### Documentation improvements + +- Clean up changelog following 7.0.0 release [#1439](https://github.com/ipython/ipykernel/pull/1439) ([@ianthomas23](https://github.com/ianthomas23)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2025-10-13&to=2025-10-14&type=c)) + +[@bollwyvl](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abollwyvl+updated%3A2025-10-13..2025-10-14&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-10-13..2025-10-14&type=Issues) | [@cclauss](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Acclauss+updated%3A2025-10-13..2025-10-14&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-10-13..2025-10-14&type=Issues) + + + ## 7.0.0 IPykernel 7.0.0 is a major release containing experimental support for [kernel subshells](https://github.com/jupyter/enhancement-proposals/pull/91). @@ -50,8 +77,6 @@ For further information and to report problems please see [ipykernel 7.0.0 relea [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-05-16..2025-10-13&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2025-05-16..2025-10-13&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2025-05-16..2025-10-13&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adependabot+updated%3A2025-05-16..2025-10-13&type=Issues) | [@fleming79](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Afleming79+updated%3A2025-05-16..2025-10-13&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-05-16..2025-10-13&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2025-05-16..2025-10-13&type=Issues) | [@mgorny](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amgorny+updated%3A2025-05-16..2025-10-13&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2025-05-16..2025-10-13&type=Issues) | [@pankaj-bind](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apankaj-bind+updated%3A2025-05-16..2025-10-13&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apre-commit-ci+updated%3A2025-05-16..2025-10-13&type=Issues) - - ## 6.30.1 This is a bugfix release to fix a significant bug introduced in 6.30.0 that allowed control messages to be handled concurrently rather than sequentially which broke debugging in JupyterLab and VSCode. diff --git a/ipykernel/_version.py b/ipykernel/_version.py index d78f3018c..e3f011017 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "7.0.0" +__version__ = "7.0.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 93f11dbb02692a7922cb6fdb4ebadffdb8b691c0 Mon Sep 17 00:00:00 2001 From: Min RK Date: Wed, 15 Oct 2025 03:11:07 -0700 Subject: [PATCH 1104/1195] update tests for 3.14 (#1453) --- .github/workflows/ci.yml | 22 +++++++++++++++++++++- pyproject.toml | 9 +++------ tests/test_kernel.py | 12 ++++++++---- 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99b1f3304..3dd030d5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,14 +22,29 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] + qt: + - qt5 + - qt6 python-version: - "3.10" - "3.11" - "3.12" - "3.13" - "3.14" - - "3.14t" + # 3.14t needs a jupyter-core release + # - "3.14t" - "pypy-3.11" + exclude: + # qt6 not supported on 3.14 yet + - python-version: "3.14" + qt: qt6 + - python-version: "3.13" + qt: qt5 + - python-version: "3.12" + qt: qt5 + - python-version: "3.11" + qt: qt5 + steps: - name: Checkout uses: actions/checkout@v5 @@ -38,6 +53,11 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: set qt env + run: | + echo "QT=${{ matrix.qt }}" >> $GITHUB_ENV + shell: bash + - name: Install hatch run: | python --version diff --git a/pyproject.toml b/pyproject.toml index c2b149d96..c979015df 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -103,13 +103,10 @@ features = ["test", "cov"] test = "python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered {args}" nowarn = "test -W default {args}" -[[tool.hatch.envs.cov.matrix]] -qt = ["qt5", "qt6"] - [tool.hatch.envs.cov.overrides] -matrix.qt.features = [ - { value = "pyqt5", if = ["qt5"] }, - { value = "pyside6", if = ["qt6"] }, +env.QT.features = [ + { value = "pyqt5", if = ["qt5"] }, + { value = "pyside6", if = ["qt6"] }, ] [tool.hatch.envs.typing] diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 1e3722630..3f05f75dd 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -211,10 +211,14 @@ def test_sys_path_profile_dir(): assert "" in sys_path +# the subprocess print tests fail in pytest, +# but manual tests in notebooks work fine... + + @pytest.mark.flaky(max_runs=3) @pytest.mark.skipif( - sys.platform == "win32" or (sys.platform == "darwin"), - reason="subprocess prints fail on Windows and MacOS Python 3.8+", + sys.platform in {"win32", "darwin"} or sys.version_info >= (3, 14), + reason="test doesn't reliably reproduce subprocess output capture", ) def test_subprocess_print(): """printing from forked mp.Process""" @@ -268,8 +272,8 @@ def test_subprocess_noprint(): @pytest.mark.flaky(max_runs=3) @pytest.mark.skipif( - (sys.platform == "win32") or (sys.platform == "darwin"), - reason="subprocess prints fail on Windows and MacOS Python 3.8+", + sys.platform in {"win32", "darwin"} or sys.version_info >= (3, 14), + reason="test doesn't reliably reproduce subprocess output capture", ) def test_subprocess_error(): """error in mp.Process doesn't crash""" From b8f5dfc3a35a658c66b85213e60f634bd8a44488 Mon Sep 17 00:00:00 2001 From: Darshan Poudel Date: Wed, 15 Oct 2025 20:46:39 +0545 Subject: [PATCH 1105/1195] Store display outputs in history for `%notebook` magic (#1435) --- ipykernel/zmqshell.py | 27 ++++++++++++++++++++++++++- tests/test_zmq_shell.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 37575ee2d..ba707d481 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -35,12 +35,17 @@ from IPython.utils.process import arg_split, system # type:ignore[attr-defined] from jupyter_client.session import Session, extract_header from jupyter_core.paths import jupyter_runtime_dir -from traitlets import Any, CBool, CBytes, Instance, Type, default, observe +from traitlets import Any, Bool, CBool, CBytes, Instance, Type, default, observe from ipykernel import connect_qtconsole, get_connection_file, get_connection_info from ipykernel.displayhook import ZMQShellDisplayHook from ipykernel.jsonutil import encode_images, json_clean +try: + from IPython.core.history import HistoryOutput +except ImportError: + HistoryOutput = None # type: ignore[assignment,misc] + # ----------------------------------------------------------------------------- # Functions and classes # ----------------------------------------------------------------------------- @@ -54,6 +59,11 @@ class ZMQDisplayPublisher(DisplayPublisher): _parent_header: contextvars.ContextVar[dict[str, Any]] topic = CBytes(b"display_data") + store_display_history = Bool( + False, + help="If set to True, store display outputs in the history manager. Default is False.", + ).tag(config=True) + # thread_local: # An attribute used to ensure the correct output message # is processed. See ipykernel Issue 113 for a discussion. @@ -115,6 +125,21 @@ def publish( # type:ignore[override] update : bool, optional, keyword-only If True, send an update_display_data message instead of display_data. """ + if ( + self.store_display_history + and self.shell is not None + and hasattr(self.shell, "history_manager") + and HistoryOutput is not None + ): + # Reference: github.com/ipython/ipython/pull/14998 + exec_count = self.shell.execution_count + if getattr(self.shell.display_pub, "_in_post_execute", False): + exec_count -= 1 + outputs = getattr(self.shell.history_manager, "outputs", None) + if outputs is not None: + outputs.setdefault(exec_count, []).append( + HistoryOutput(output_type="display_data", bundle=data) + ) self._flush_streams() if metadata is None: metadata = {} diff --git a/tests/test_zmq_shell.py b/tests/test_zmq_shell.py index bc5e3f556..c4859cfa0 100644 --- a/tests/test_zmq_shell.py +++ b/tests/test_zmq_shell.py @@ -22,6 +22,11 @@ ZMQInteractiveShell, ) +try: + from IPython.core.history import HistoryOutput +except ImportError: + HistoryOutput = None # type: ignore[assignment,misc] + class NoReturnDisplayHook: """ @@ -209,6 +214,35 @@ def test_unregister_hook(self): second = self.disp_pub.unregister_hook(hook) assert not bool(second) + @unittest.skipIf(HistoryOutput is None, "HistoryOutput not available") + def test_display_stored_in_history(self): + """ + Test that published display data gets stored in shell history + for %notebook magic support, and not stored when disabled. + """ + for enable in [False, True]: + # Mock shell with history manager + mock_shell = MagicMock() + mock_shell.execution_count = 1 + mock_shell.history_manager.outputs = dict() + mock_shell.display_pub._in_post_execute = False + + self.disp_pub.shell = mock_shell + self.disp_pub.store_display_history = enable + + data = {"text/plain": "test output"} + self.disp_pub.publish(data) + + if enable: + # Check that output was stored in history + stored_outputs = mock_shell.history_manager.outputs[1] + assert len(stored_outputs) == 1 + assert stored_outputs[0].output_type == "display_data" + assert stored_outputs[0].bundle == data + else: + # Should not store anything in history + assert mock_shell.history_manager.outputs == {} + def test_magics(tmp_path): context = zmq.Context() From 7193d14de447a18470a18d60b81eda5f0048b6aa Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 16 Oct 2025 08:57:50 -0700 Subject: [PATCH 1106/1195] Fix routing of background thread output when no parent is set explicitly (#1451) --- ipykernel/displayhook.py | 21 ++++-- ipykernel/iostream.py | 18 +----- ipykernel/ipkernel.py | 90 -------------------------- ipykernel/kernelbase.py | 29 +++++++-- ipykernel/zmqshell.py | 35 ++++++++-- tests/test_kernel.py | 136 ++++++++++++++++++++++++++++++++------- 6 files changed, 183 insertions(+), 146 deletions(-) diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index 5f42a1445..d5e136748 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -29,6 +29,7 @@ def __init__(self, session, pub_socket): self._parent_header: ContextVar[dict[str, Any]] = ContextVar("parent_header") self._parent_header.set({}) + self._parent_header_global = {} def get_execution_count(self): """This method is replaced in kernelapp""" @@ -57,11 +58,16 @@ def __call__(self, obj): @property def parent_header(self): - return self._parent_header.get() + try: + return self._parent_header.get() + except LookupError: + return self._parent_header_global def set_parent(self, parent): """Set the parent header.""" - self._parent_header.set(extract_header(parent)) + parent_header = extract_header(parent) + self._parent_header.set(parent_header) + self._parent_header_global = parent_header class ZMQShellDisplayHook(DisplayHook): @@ -83,11 +89,16 @@ def __init__(self, *args, **kwargs): @property def parent_header(self): - return self._parent_header.get() + try: + return self._parent_header.get() + except LookupError: + return self._parent_header_global def set_parent(self, parent): - """Set the parent for outbound messages.""" - self._parent_header.set(extract_header(parent)) + """Set the parent header.""" + parent_header = extract_header(parent) + self._parent_header.set(parent_header) + self._parent_header_global = parent_header def start_displayhook(self): """Start the display hook.""" diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index d9c90af4f..0a2115f3b 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -456,8 +456,6 @@ def __init__( "parent_header" ) self._parent_header.set({}) - self._thread_to_parent = {} - self._thread_to_parent_header = {} self._parent_header_global = {} self._master_pid = os.getpid() self._flush_pending = False @@ -512,21 +510,11 @@ def __init__( @property def parent_header(self): try: - # asyncio-specific + # asyncio or thread-specific return self._parent_header.get() except LookupError: - try: - # thread-specific - identity = threading.current_thread().ident - # retrieve the outermost (oldest ancestor, - # discounting the kernel thread) thread identity - while identity in self._thread_to_parent: - identity = self._thread_to_parent[identity] - # use the header of the oldest ancestor - return self._thread_to_parent_header[identity] - except KeyError: - # global (fallback) - return self._parent_header_global + # global (fallback) + return self._parent_header_global @parent_header.setter def parent_header(self, value): diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 3e3927cc5..b8508ad68 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -4,7 +4,6 @@ import asyncio import builtins -import gc import getpass import os import signal @@ -17,7 +16,6 @@ import comm from IPython.core import release from IPython.utils.tokenutil import line_at_cursor, token_at_cursor -from jupyter_client.session import extract_header from traitlets import Any, Bool, HasTraits, Instance, List, Type, default, observe, observe_compat from zmq.eventloop.zmqstream import ZMQStream @@ -25,7 +23,6 @@ from .comm.manager import CommManager from .compiler import XCachingCompiler from .eventloops import _use_appnope -from .iostream import OutStream from .kernelbase import Kernel as KernelBase from .kernelbase import _accepts_parameters from .zmqshell import ZMQInteractiveShell @@ -167,14 +164,6 @@ def __init__(self, **kwargs): appnope.nope() - self._new_threads_parent_header = {} - self._initialize_thread_hooks() - - if hasattr(gc, "callbacks"): - # while `gc.callbacks` exists since Python 3.3, pypy does not - # implement it even as of 3.9. - gc.callbacks.append(self._clean_thread_parent_frames) - help_links = List( [ { @@ -374,8 +363,6 @@ def _dummy_context_manager(self, *args): async def execute_request(self, stream, ident, parent): """Override for cell output - cell reconciliation.""" - parent_header = extract_header(parent) - self._associate_new_top_level_threads_with(parent_header) await super().execute_request(stream, ident, parent) async def do_execute( @@ -750,83 +737,6 @@ def do_clear(self): self.shell.reset(False) return dict(status="ok") - def _associate_new_top_level_threads_with(self, parent_header): - """Store the parent header to associate it with new top-level threads""" - self._new_threads_parent_header = parent_header - - def _initialize_thread_hooks(self): - """Store thread hierarchy and thread-parent_header associations.""" - stdout = self._stdout - stderr = self._stderr - kernel_thread_ident = threading.get_ident() - kernel = self - _threading_Thread_run = threading.Thread.run - _threading_Thread__init__ = threading.Thread.__init__ - - def run_closure(self: threading.Thread): - """Wrap the `threading.Thread.start` to intercept thread identity. - - This is needed because there is no "start" hook yet, but there - might be one in the future: https://bugs.python.org/issue14073 - - This is a no-op if the `self._stdout` and `self._stderr` are not - sub-classes of `OutStream`. - """ - - try: - parent = self._ipykernel_parent_thread_ident # type:ignore[attr-defined] - except AttributeError: - return - for stream in [stdout, stderr]: - if isinstance(stream, OutStream): - if parent == kernel_thread_ident: - stream._thread_to_parent_header[self.ident] = ( - kernel._new_threads_parent_header - ) - else: - stream._thread_to_parent[self.ident] = parent - _threading_Thread_run(self) - - def init_closure(self: threading.Thread, *args, **kwargs): - _threading_Thread__init__(self, *args, **kwargs) - self._ipykernel_parent_thread_ident = threading.get_ident() # type:ignore[attr-defined] - - threading.Thread.__init__ = init_closure # type:ignore[method-assign] - threading.Thread.run = run_closure # type:ignore[method-assign] - - def _clean_thread_parent_frames( - self, phase: t.Literal["start", "stop"], info: dict[str, t.Any] - ): - """Clean parent frames of threads which are no longer running. - This is meant to be invoked by garbage collector callback hook. - - The implementation enumerates the threads because there is no "exit" hook yet, - but there might be one in the future: https://bugs.python.org/issue14073 - - This is a no-op if the `self._stdout` and `self._stderr` are not - sub-classes of `OutStream`. - """ - # Only run before the garbage collector starts - if phase != "start": - return - active_threads = {thread.ident for thread in threading.enumerate()} - for stream in [self._stdout, self._stderr]: - if isinstance(stream, OutStream): - thread_to_parent_header = stream._thread_to_parent_header - for identity in list(thread_to_parent_header.keys()): - if identity not in active_threads: - try: - del thread_to_parent_header[identity] - except KeyError: - pass - thread_to_parent = stream._thread_to_parent - for identity in list(thread_to_parent.keys()): - if identity not in active_threads: - try: - del thread_to_parent[identity] - except KeyError: - pass - # This exists only for backwards compatibility - use IPythonKernel instead diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index b815b934b..931b7aac3 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -17,7 +17,7 @@ import uuid import warnings from collections.abc import Mapping -from contextvars import ContextVar +from contextvars import Context, ContextVar, copy_context from datetime import datetime from functools import partial from signal import SIGINT, SIGTERM, Signals, default_int_handler, signal @@ -72,6 +72,8 @@ " ipykernel 6.0 (2021). {target} does not seem to return an awaitable" ) +T = t.TypeVar("T") + def _accepts_parameters(meth, param_names): parameters = inspect.signature(meth).parameters @@ -201,6 +203,7 @@ def _default_ident(self): _control_parent_ident: bytes = b"" _shell_parent: ContextVar[dict[str, Any]] _shell_parent_ident: ContextVar[bytes] + _shell_context: Context # Kept for backward-compatibility, accesses _control_parent_ident and _shell_parent_ident, # see https://github.com/jupyterlab/jupyterlab/issues/17785 _parent_ident: Mapping[str, bytes] @@ -320,13 +323,14 @@ def __init__(self, **kwargs): self._shell_parent.set({}) self._shell_parent_ident = ContextVar("shell_parent_ident") self._shell_parent_ident.set(b"") + self._shell_context = copy_context() # For backward compatibility so that _parent_ident["shell"] and _parent_ident["control"] # work as they used to for ipykernel >= 7 self._parent_ident = LazyDict( { "control": lambda: self._control_parent_ident, - "shell": lambda: self._shell_parent_ident.get(), + "shell": lambda: self._get_shell_context_var(self._shell_parent_ident), } ) @@ -768,6 +772,8 @@ def set_parent(self, ident, parent, channel="shell"): else: self._shell_parent_ident.set(ident) self._shell_parent.set(parent) + # preserve the last call to set_parent + self._shell_context = copy_context() def get_parent(self, channel=None): """Get the parent request associated with a channel. @@ -794,7 +800,20 @@ def get_parent(self, channel=None): if channel == "control": return self._control_parent - return self._shell_parent.get() + + return self._get_shell_context_var(self._shell_parent) + + def _get_shell_context_var(self, var: ContextVar[T]) -> T: + """Lookup a ContextVar, falling back on the shell context + + Allows for user-launched Threads to still resolve to the shell's main context + + necessary for e.g. display from threads. + """ + try: + return var.get() + except LookupError: + return self._shell_context[var] def send_response( self, @@ -1455,7 +1474,7 @@ def getpass(self, prompt="", stream=None): ) return self._input_request( prompt, - self._shell_parent_ident.get(), + self._get_shell_context_var(self._shell_parent_ident), self.get_parent("shell"), password=True, ) @@ -1472,7 +1491,7 @@ def raw_input(self, prompt=""): raise StdinNotImplementedError(msg) return self._input_request( str(prompt), - self._shell_parent_ident.get(), + self._get_shell_context_var(self._shell_parent_ident), self.get_parent("shell"), password=False, ) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index ba707d481..39e2f1381 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -73,14 +73,20 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._parent_header = contextvars.ContextVar("parent_header") self._parent_header.set({}) + self._parent_header_global = {} @property def parent_header(self): - return self._parent_header.get() + try: + return self._parent_header.get() + except LookupError: + return self._parent_header_global def set_parent(self, parent): """Set the parent for outbound messages.""" - self._parent_header.set(extract_header(parent)) + parent_header = extract_header(parent) + self._parent_header.set(parent_header) + self._parent_header_global = parent_header def _flush_streams(self): """flush IO Streams prior to display""" @@ -698,11 +704,23 @@ def set_next_input(self, text, replace=False): @property def parent_header(self): - return self._parent_header.get() + try: + return self._parent_header.get() + except LookupError: + return self._parent_header_global + + @parent_header.setter + def parent_header(self, value): + self._parent_header_global = value + self._parent_header.set(value) def set_parent(self, parent): - """Set the parent header for associating output with its triggering input""" - self._parent_header.set(parent) + """Set the parent header for associating output with its triggering input + + When called from a thread, sets the thread-local value, which persists + until the next call from this thread. + """ + self.parent_header = parent self.displayhook.set_parent(parent) # type:ignore[attr-defined] self.display_pub.set_parent(parent) # type:ignore[attr-defined] if hasattr(self, "_data_pub"): @@ -713,7 +731,12 @@ def set_parent(self, parent): sys.stderr.set_parent(parent) def get_parent(self): - """Get the parent header.""" + """Get the parent header. + + If set_parent has never been called from the current thread, + the value from the last call to set_parent from _any_ thread will be used + (typically the currently running cell). + """ return self.parent_header def init_magics(self): diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 3f05f75dd..2c5fe2b58 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -58,36 +58,119 @@ def test_simple_print(): _check_master(kc, expected=True) -def test_print_to_correct_cell_from_thread(): - """should print to the cell that spawned the thread, not a subsequently run cell""" - iterations = 5 - interval = 0.25 - code = f"""\ - from threading import Thread - from time import sleep +def collect_outputs(get_iopub_msg, parent_msg_id, timeout=5): + """Collect outputs until we get an idle message - def thread_target(): - for i in range({iterations}): - print(i, end='', flush=True) - sleep({interval}) + Returns list of complete output messages. + """ + while True: + msg = get_iopub_msg(timeout=timeout) + msg_type = msg["msg_type"] + content = msg["content"] + + if ( + msg["parent_header"]["msg_id"] == parent_msg_id + and msg_type == "status" + and content["execution_state"] == "idle" + ): + # idle message signals end of output + break + elif msg["msg_type"] in {"stream", "display_data"}: + yield msg + elif msg["msg_type"] == "error": + tb = "\n".join(msg["content"]["traceback"]) + raise RuntimeError(f"Error during execution: {tb}") + else: + # other output, ignored + print(msg["msg_type"]) + + +@pytest.mark.parametrize("explicit_parent", [True, False]) +def test_print_to_correct_cell_from_thread(explicit_parent: bool): + """should print to the current cell unless + + get_ipython().set_parent sets the thread-local value, + which supersedes the default. - Thread(target=thread_target).start() """ + code = f"""\ + from threading import Event, Thread + from time import sleep + from IPython.display import display + + explicit_parent = {explicit_parent} + parent = get_ipython().get_parent() + + cell_start_event = Event() + cell_end_event = Event() + + def thread_target(): + if explicit_parent: + get_ipython().set_parent(parent) + + print("before", flush=True) + display(1) + cell_start_event.wait(timeout=10) + cell_start_event.clear() + + print("during", flush=True) + display(2) + cell_end_event.set() + cell_start_event.wait(timeout=10) + cell_start_event.clear() + print("after", flush=True) + display(3) + + thread = Thread(target=thread_target) + thread.start() + """ + outputs = {} + + def add_output(msg): + parent_id = msg["parent_header"]["msg_id"] + if parent_id not in outputs: + outputs[parent_id] = { + "stdout": "", + "stderr": "", + "display_data": [], + } + cell_outputs = outputs[parent_id] + msg_type = msg["header"]["msg_type"] + content = msg["content"] + if msg_type == "stream": + cell_outputs[content["name"]] += content["text"] + else: + cell_outputs[msg_type].append(msg["content"]["data"]["text/plain"]) + with kernel() as kc: thread_msg_id = kc.execute(code) - _ = kc.execute("pass") - - received = 0 - while received < iterations: - msg = kc.get_iopub_msg(timeout=interval * 2) - if msg["msg_type"] != "stream": - continue - content = msg["content"] - assert content["name"] == "stdout" - assert content["text"] == str(received) - # this is crucial as the parent header decides to which cell the output goes - assert msg["parent_header"]["msg_id"] == thread_msg_id - received += 1 + for msg in collect_outputs(kc.get_iopub_msg, thread_msg_id): + add_output(msg) + + next_cell_msg_id = kc.execute("cell_start_event.set()\ncell_end_event.wait(timeout=10)") + for msg in collect_outputs(kc.get_iopub_msg, next_cell_msg_id): + add_output(msg) + + last_cell_msg_id = kc.execute("cell_start_event.set()\nthread.join()") + for msg in collect_outputs(kc.get_iopub_msg, last_cell_msg_id): + add_output(msg) + print(outputs) + if explicit_parent: + # assert next_cell_msg_id not in outputs + # assert last_cell_msg_id not in outputs + thread_cell_output = outputs[thread_msg_id] + assert thread_cell_output["stdout"] == "before\nduring\nafter\n" + assert thread_cell_output["display_data"] == ["1", "2", "3"] + else: + thread_cell_output = outputs[thread_msg_id] + assert thread_cell_output["stdout"] == "before\n" + assert thread_cell_output["display_data"] == ["1"] + next_cell_output = outputs[next_cell_msg_id] + assert next_cell_output["stdout"] == "during\n" + assert next_cell_output["display_data"] == ["2"] + last_cell_output = outputs[last_cell_msg_id] + assert last_cell_output["stdout"] == "after\n" + assert last_cell_output["display_data"] == ["3"] def test_print_to_correct_cell_from_child_thread(): @@ -98,7 +181,10 @@ def test_print_to_correct_cell_from_child_thread(): from threading import Thread from time import sleep + parent = get_ipython().get_parent() + def child_target(): + get_ipython().set_parent(parent) for i in range({iterations}): print(i, end='', flush=True) sleep({interval}) From c7af34cd19ebcd43f5aafe1919909feb6e898387 Mon Sep 17 00:00:00 2001 From: Daniel Falbel Date: Fri, 17 Oct 2025 11:40:38 -0300 Subject: [PATCH 1107/1195] Refer to kernel laucnhing thread instead of assuming the main thread (#1455) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/ipkernel.py | 2 +- ipykernel/kernelbase.py | 6 +++--- ipykernel/shellchannel.py | 4 ++++ ipykernel/subshell_manager.py | 8 ++++---- tests/test_start_kernel.py | 34 ++++++++++++++++++++++++++++++++++ 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index b8508ad68..e1505a9a7 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -439,7 +439,7 @@ async def run_cell(*args, **kwargs): cm = ( self._cancel_on_sigint - if threading.current_thread() == threading.main_thread() + if threading.current_thread() == self.shell_channel_thread.parent_thread else self._dummy_context_manager ) with cm(coro_future): diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 931b7aac3..6e2575b18 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -681,19 +681,19 @@ async def shell_main(self, subshell_id: str | None, msg): """Handler of shell messages for a single subshell""" if self._supports_kernel_subshells: if subshell_id is None: - assert threading.current_thread() == threading.main_thread() + assert threading.current_thread() == self.shell_channel_thread.parent_thread asyncio_lock = self._main_asyncio_lock else: assert threading.current_thread() not in ( self.shell_channel_thread, - threading.main_thread(), + self.shell_channel_thread.parent_thread, ) asyncio_lock = self.shell_channel_thread.manager.get_subshell_asyncio_lock( subshell_id ) else: assert subshell_id is None - assert threading.current_thread() == threading.main_thread() + assert threading.current_thread() == self.shell_channel_thread.parent_thread asyncio_lock = self._main_asyncio_lock # Whilst executing a shell message, do not accept any other shell messages on the diff --git a/ipykernel/shellchannel.py b/ipykernel/shellchannel.py index 8335e8887..8205840d1 100644 --- a/ipykernel/shellchannel.py +++ b/ipykernel/shellchannel.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio +from threading import current_thread from typing import Any import zmq @@ -28,6 +29,8 @@ def __init__( self._manager: SubshellManager | None = None self._zmq_context = context # Avoid use of self._context self._shell_socket = shell_socket + # Record the parent thread - the thread that started the app (usually the main thread) + self.parent_thread = current_thread() self.asyncio_lock = asyncio.Lock() @@ -35,6 +38,7 @@ def __init__( def manager(self) -> SubshellManager: # Lazy initialisation. if self._manager is None: + assert current_thread() == self.parent_thread self._manager = SubshellManager( self._zmq_context, self.io_loop, diff --git a/ipykernel/subshell_manager.py b/ipykernel/subshell_manager.py index 7f65183dd..3e4e0b6bc 100644 --- a/ipykernel/subshell_manager.py +++ b/ipykernel/subshell_manager.py @@ -7,7 +7,7 @@ import typing as t import uuid from functools import partial -from threading import Lock, current_thread, main_thread +from threading import Lock, current_thread import zmq from tornado.ioloop import IOLoop @@ -41,7 +41,7 @@ def __init__( shell_socket: zmq.Socket[t.Any], ): """Initialize the subshell manager.""" - assert current_thread() == main_thread() + self._parent_thread = current_thread() self._context: zmq.Context[t.Any] = context self._shell_channel_io_loop = shell_channel_io_loop @@ -127,7 +127,7 @@ def set_on_recv_callback(self, on_recv_callback): """Set the callback used by the main shell and all subshells to receive messages sent from the shell channel thread. """ - assert current_thread() == main_thread() + assert current_thread() == self._parent_thread self._on_recv_callback = on_recv_callback self._shell_channel_to_main.on_recv(IOLoop.current(), partial(self._on_recv_callback, None)) @@ -144,7 +144,7 @@ def subshell_id_from_thread_id(self, thread_id: int) -> str | None: Only used by %subshell magic so does not have to be fast/cached. """ with self._lock_cache: - if thread_id == main_thread().ident: + if thread_id == self._parent_thread.ident: return None for id, subshell in self._cache.items(): if subshell.ident == thread_id: diff --git a/tests/test_start_kernel.py b/tests/test_start_kernel.py index b9276e33b..6e373ccff 100644 --- a/tests/test_start_kernel.py +++ b/tests/test_start_kernel.py @@ -50,6 +50,40 @@ def test_ipython_start_kernel_userns(): assert EXPECTED in text +def test_start_kernel_background_thread(): + cmd = dedent( + """ + import threading + import asyncio + from ipykernel.kernelapp import launch_new_instance + + def launch(): + # Threads don't always have a default event loop so we need to + # create and set a default + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + launch_new_instance() + + thread = threading.Thread(target=launch) + thread.start() + thread.join() + """ + ) + + with setup_kernel(cmd) as client: + client.execute("a = 1") + msg = client.get_shell_msg(timeout=TIMEOUT) + content = msg["content"] + assert content["status"] == "ok" + + client.inspect("a") + msg = client.get_shell_msg(timeout=TIMEOUT) + content = msg["content"] + assert content["found"] + text = content["data"]["text/plain"] + assert "1" in text + + @pytest.mark.flaky(max_runs=3) def test_ipython_start_kernel_no_userns(): # Issue #4188 - user_ns should be passed to shell as None, not {} From c56a7aab3cad1fb91f7e7185dc7403d561ecd667 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Mon, 20 Oct 2025 12:59:50 +0100 Subject: [PATCH 1108/1195] Fix matplotlib eventloops (#1458) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/downstream.yml | 2 +- ipykernel/eventloops.py | 70 ++++++++++++++------- ipykernel/kernelbase.py | 104 ++----------------------------- tests/test_ipkernel_direct.py | 26 +++++--- tests/test_kernel_direct.py | 6 -- 5 files changed, 69 insertions(+), 139 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index e02ba68e3..601d322e1 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -117,7 +117,7 @@ jobs: shell: bash -l {0} run: | cd ${GITHUB_WORKSPACE}/../qtconsole - xvfb-run --auto-servernum ${pythonLocation}/bin/python -m pytest -x -vv -s --full-trace --color=yes qtconsole + xvfb-run --auto-servernum ${pythonLocation}/bin/python -m pytest -x -vv -s --full-trace --color=yes qtconsole -k "not test_scroll" spyder_kernels: runs-on: ubuntu-latest diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 853738d9e..9423b208a 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -40,8 +40,9 @@ def register_integration(*toolkitnames): You can provide alternative names for the same toolkit. The decorated function should take a single argument, the IPython kernel - instance, arrange for the event loop to call ``kernel.do_one_iteration()`` - at least every ``kernel._poll_interval`` seconds, and start the event loop. + instance, arrange for the event loop to yield the asyncio loop when a + message is received by the main shell zmq stream or at least every + ``kernel._poll_interval`` seconds, and start the event loop. :mod:`ipykernel.eventloops` provides and registers such functions for a few common event loops. @@ -68,6 +69,15 @@ def exit_decorator(exit_func): return decorator +def get_shell_stream(kernel): + # Return the zmq stream that receives messages for the main shell. + if kernel._supports_kernel_subshells: + manager = kernel.shell_channel_thread.manager + socket_pair = manager.get_shell_channel_to_subshell_pair(None) + return socket_pair.to_stream + return kernel.shell_stream + + def _notify_stream_qt(kernel): import operator from functools import lru_cache @@ -87,17 +97,20 @@ def exit_loop(): kernel._qt_notifier.setEnabled(False) kernel.app.qt_event_loop.quit() - def process_stream_events(): + def process_stream_events_wrap(shell_stream, *args, **kwargs): """fall back to main loop when there's a socket event""" # call flush to ensure that the stream doesn't lose events # due to our consuming of the edge-triggered FD # flush returns the number of events consumed. # if there were any, wake it up - if kernel.shell_stream.flush(limit=1): + if shell_stream.flush(limit=1): exit_loop() + shell_stream = get_shell_stream(kernel) + process_stream_events = partial(process_stream_events_wrap, shell_stream) + if not hasattr(kernel, "_qt_notifier"): - fd = kernel.shell_stream.getsockopt(zmq.FD) + fd = shell_stream.getsockopt(zmq.FD) kernel._qt_notifier = QtCore.QSocketNotifier( fd, enum_helper("QtCore.QSocketNotifier.Type").Read, kernel.app.qt_event_loop ) @@ -177,9 +190,11 @@ def loop_wx(kernel): # Wx uses milliseconds poll_interval = int(1000 * kernel._poll_interval) - def wake(): + shell_stream = get_shell_stream(kernel) + + def wake(shell_stream): """wake from wx""" - if kernel.shell_stream.flush(limit=1): + if shell_stream.flush(limit=1): kernel.app.ExitMainLoop() return @@ -201,7 +216,7 @@ def on_timer(self, event): # wx.Timer to defer back to the tornado event loop. class IPWxApp(wx.App): # type:ignore[misc] def OnInit(self): - self.frame = TimerFrame(wake) + self.frame = TimerFrame(partial(wake, shell_stream)) self.frame.Show(False) return True @@ -248,14 +263,14 @@ def __init__(self, app): def exit_loop(): """fall back to main loop""" - app.tk.deletefilehandler(kernel.shell_stream.getsockopt(zmq.FD)) + app.tk.deletefilehandler(shell_stream.getsockopt(zmq.FD)) app.quit() app.destroy() del kernel.app_wrapper - def process_stream_events(*a, **kw): + def process_stream_events_wrap(shell_stream, *a, **kw): """fall back to main loop when there's a socket event""" - if kernel.shell_stream.flush(limit=1): + if shell_stream.flush(limit=1): exit_loop() # allow for scheduling exits from the loop in case a timeout needs to @@ -268,9 +283,10 @@ def _schedule_exit(delay): # For Tkinter, we create a Tk object and call its withdraw method. kernel.app_wrapper = BasicAppWrapper(app) - app.tk.createfilehandler( - kernel.shell_stream.getsockopt(zmq.FD), READABLE, process_stream_events - ) + shell_stream = get_shell_stream(kernel) + process_stream_events = partial(process_stream_events_wrap, shell_stream) + + app.tk.createfilehandler(shell_stream.getsockopt(zmq.FD), READABLE, process_stream_events) # schedule initial call after start app.after(0, process_stream_events) @@ -283,15 +299,19 @@ def _schedule_exit(delay): nest_asyncio.apply() - doi = kernel.do_one_iteration # Tk uses milliseconds poll_interval = int(1000 * kernel._poll_interval) + shell_stream = get_shell_stream(kernel) + class TimedAppWrapper: - def __init__(self, app, func): + def __init__(self, app, shell_stream): self.app = app + self.shell_stream = shell_stream self.app.withdraw() - self.func = func + + async def func(self): + self.shell_stream.flush(limit=1) def on_timer(self): loop = asyncio.get_event_loop() @@ -305,7 +325,7 @@ def start(self): self.on_timer() # Call it once to get things going. self.app.mainloop() - kernel.app_wrapper = TimedAppWrapper(app, doi) + kernel.app_wrapper = TimedAppWrapper(app, shell_stream) kernel.app_wrapper.start() @@ -313,8 +333,10 @@ def start(self): def loop_tk_exit(kernel): """Exit the tk loop.""" try: + kernel.app_wrapper.app.quit() kernel.app_wrapper.app.destroy() del kernel.app_wrapper + kernel.eventloop = None except (RuntimeError, AttributeError): pass @@ -359,6 +381,7 @@ def loop_cocoa(kernel): from ._eventloop_macos import mainloop, stop real_excepthook = sys.excepthook + shell_stream = get_shell_stream(kernel) def handle_int(etype, value, tb): """don't let KeyboardInterrupts look like crashes""" @@ -377,7 +400,7 @@ def handle_int(etype, value, tb): # don't let interrupts during mainloop invoke crash_handler: sys.excepthook = handle_int mainloop(kernel._poll_interval) - if kernel.shell_stream.flush(limit=1): + if shell_stream.flush(limit=1): # events to process, return control to kernel return except BaseException: @@ -415,13 +438,14 @@ def loop_asyncio(kernel): loop._should_close = False # type:ignore[attr-defined] # pause eventloop when there's an event on a zmq socket - def process_stream_events(stream): + def process_stream_events(shell_stream): """fall back to main loop when there's a socket event""" - if stream.flush(limit=1): + if shell_stream.flush(limit=1): loop.stop() - notifier = partial(process_stream_events, kernel.shell_stream) - loop.add_reader(kernel.shell_stream.getsockopt(zmq.FD), notifier) + shell_stream = get_shell_stream(kernel) + notifier = partial(process_stream_events, shell_stream) + loop.add_reader(shell_stream.getsockopt(zmq.FD), notifier) loop.call_soon(notifier) while True: diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 6e2575b18..d4778f49b 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -6,7 +6,6 @@ import asyncio import inspect -import itertools import logging import os import socket @@ -42,7 +41,6 @@ from IPython.core.error import StdinNotImplementedError from jupyter_client.session import Session from tornado import ioloop -from tornado.queues import Queue, QueueEmpty from traitlets.config.configurable import SingletonConfigurable from traitlets.traitlets import ( Any, @@ -511,11 +509,6 @@ async def advance_eventloop(): if self.eventloop is not eventloop: self.log.info("exiting eventloop %s", eventloop) return - if self.msg_queue.qsize(): - self.log.debug("Delaying eventloop due to waiting messages") - # still messages to process, make the eventloop wait - schedule_next() - return self.log.debug("Advancing eventloop %s", eventloop) try: eventloop(self) @@ -534,88 +527,11 @@ def schedule_next(): # already consumed from the queue by process_one and the queue is # technically empty. self.log.debug("Scheduling eventloop advance") - self.io_loop.call_later(0.001, partial(self.schedule_dispatch, advance_eventloop)) + self.io_loop.call_later(0.001, advance_eventloop) # begin polling the eventloop schedule_next() - async def do_one_iteration(self): - """Process a single shell message - - Any pending control messages will be flushed as well - - .. versionchanged:: 5 - This is now a coroutine - """ - # flush messages off of shell stream into the message queue - if self.shell_stream and not self._supports_kernel_subshells: - self.shell_stream.flush() - # process at most one shell message per iteration - await self.process_one(wait=False) - - async def process_one(self, wait=True): - """Process one request - - Returns None if no message was handled. - """ - if wait: - t, dispatch, args = await self.msg_queue.get() - else: - try: - t, dispatch, args = self.msg_queue.get_nowait() - except (asyncio.QueueEmpty, QueueEmpty): - return - - if self.control_thread is None and self.control_stream is not None: - # If there isn't a separate control thread then this main thread handles both shell - # and control messages. Before processing a shell message we need to flush all control - # messages and allow them all to be processed. - await asyncio.sleep(0) - self.control_stream.flush() - - socket = self.control_stream.socket - while socket.poll(1): - await asyncio.sleep(0) - self.control_stream.flush() - - await dispatch(*args) - - async def dispatch_queue(self): - """Coroutine to preserve order of message handling - - Ensures that only one message is processing at a time, - even when the handler is async - """ - - while True: - try: - await self.process_one() - except Exception: - self.log.exception("Error in message handler") - - _message_counter = Any( - help="""Monotonic counter of messages - """, - ) - - @default("_message_counter") - def _message_counter_default(self): - return itertools.count() - - def schedule_dispatch(self, dispatch, *args): - """schedule a message for dispatch""" - idx = next(self._message_counter) - - self.msg_queue.put_nowait( - ( - idx, - dispatch, - args, - ) - ) - # ensure the eventloop wakes up - self.io_loop.add_callback(lambda: None) - async def _create_control_lock(self): # This can be removed when minimum python increases to 3.10 self._control_lock = asyncio.Lock() @@ -623,9 +539,6 @@ async def _create_control_lock(self): def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() - self.msg_queue: Queue[t.Any] = Queue() - if not self.shell_channel_thread: - self.io_loop.add_callback(self.dispatch_queue) if self.control_stream: self.control_stream.on_recv(self.dispatch_control, copy=False) @@ -644,10 +557,7 @@ def start(self): self.shell_stream.on_recv(self.shell_channel_thread_main, copy=False) else: self.shell_stream.on_recv( - partial( - self.schedule_dispatch, - self.dispatch_shell, - ), + partial(self.shell_main, None), copy=False, ) @@ -693,7 +603,6 @@ async def shell_main(self, subshell_id: str | None, msg): ) else: assert subshell_id is None - assert threading.current_thread() == self.shell_channel_thread.parent_thread asyncio_lock = self._main_asyncio_lock # Whilst executing a shell message, do not accept any other shell messages on the @@ -1410,15 +1319,10 @@ async def stop_aborting(): self.log.info("Finishing abort") self._aborting = False - # put the stop-aborting event on the message queue - # so that all messages already waiting in the queue are aborted - # before we reset the flag - schedule_stop_aborting = partial(self.schedule_dispatch, stop_aborting) - if self.stop_on_error_timeout: # if we have a delay, give messages this long to arrive on the queue # before we stop aborting requests - self.io_loop.call_later(self.stop_on_error_timeout, schedule_stop_aborting) + self.io_loop.call_later(self.stop_on_error_timeout, stop_aborting) # If we have an eventloop, it may interfere with the call_later above. # If the loop has a _schedule_exit method, we call that so the loop exits # after stop_on_error_timeout, returning to the main io_loop and letting @@ -1426,7 +1330,7 @@ async def stop_aborting(): if self.eventloop is not None and hasattr(self.eventloop, "_schedule_exit"): self.eventloop._schedule_exit(self.stop_on_error_timeout + 0.01) else: - schedule_stop_aborting() + self.io_loop.add_callback(stop_aborting) def _send_abort_reply(self, stream, msg, idents): """Send a reply to an aborted request""" diff --git a/tests/test_ipkernel_direct.py b/tests/test_ipkernel_direct.py index 037489f34..a2f6cc29e 100644 --- a/tests/test_ipkernel_direct.py +++ b/tests/test_ipkernel_direct.py @@ -165,29 +165,37 @@ def test_dispatch_debugpy(ipkernel: IPythonKernel) -> None: async def test_start(ipkernel: IPythonKernel) -> None: shell_future: asyncio.Future = asyncio.Future() - async def fake_dispatch_queue(): - shell_future.set_result(None) + def fake_publish_status(status, channel): + if status == "starting" and channel == "shell": + shell_future.set_result(None) - ipkernel.dispatch_queue = fake_dispatch_queue # type:ignore + ipkernel._publish_status = fake_publish_status # type:ignore ipkernel.start() - ipkernel.debugpy_stream = None - ipkernel.start() - await ipkernel.process_one(False) + await shell_future + shell_stream = ipkernel.shell_stream + assert shell_stream is not None + assert not shell_stream.closed() + async def test_start_no_debugpy(ipkernel: IPythonKernel) -> None: shell_future: asyncio.Future = asyncio.Future() - async def fake_dispatch_queue(): - shell_future.set_result(None) + def fake_publish_status(status, channel): + if status == "starting" and channel == "shell": + shell_future.set_result(None) - ipkernel.dispatch_queue = fake_dispatch_queue # type:ignore + ipkernel._publish_status = fake_publish_status # type:ignore ipkernel.debugpy_stream = None ipkernel.start() await shell_future + shell_stream = ipkernel.shell_stream + assert shell_stream is not None + assert not shell_stream.closed() + def test_create_comm(): assert isinstance(_create_comm(), BaseComm) diff --git a/tests/test_kernel_direct.py b/tests/test_kernel_direct.py index c799052be..f4a2e59b7 100644 --- a/tests/test_kernel_direct.py +++ b/tests/test_kernel_direct.py @@ -3,7 +3,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -import asyncio import os import warnings @@ -145,11 +144,6 @@ def __init__(self, bytes): await kernel.dispatch_shell(msg) -async def test_do_one_iteration(kernel): - kernel.msg_queue = asyncio.Queue() - await kernel.do_one_iteration() - - async def test_publish_debug_event(kernel): kernel._publish_debug_event({}) From 95f245138589db4f723b8af06107cdfeadc53314 Mon Sep 17 00:00:00 2001 From: Min RK Date: Tue, 21 Oct 2025 21:32:47 -0700 Subject: [PATCH 1109/1195] fix ContextVar persistence across cells (#1462) --- ipykernel/kernelbase.py | 4 +-- ipykernel/subshell_manager.py | 7 +++-- ipykernel/utils.py | 55 +++++++++++++++++++++++++++++++++++ ipykernel/zmqshell.py | 2 +- tests/test_kernel.py | 30 +++++++++++++++++++ 5 files changed, 93 insertions(+), 5 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index d4778f49b..e241933a2 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -60,7 +60,7 @@ from ._version import kernel_protocol_version from .iostream import OutStream -from .utils import LazyDict +from .utils import LazyDict, _async_in_context _AWAITABLE_MESSAGE: str = ( "For consistency across implementations, it is recommended that `{func_name}`" @@ -557,7 +557,7 @@ def start(self): self.shell_stream.on_recv(self.shell_channel_thread_main, copy=False) else: self.shell_stream.on_recv( - partial(self.shell_main, None), + _async_in_context(partial(self.shell_main, None)), copy=False, ) diff --git a/ipykernel/subshell_manager.py b/ipykernel/subshell_manager.py index 3e4e0b6bc..3305bc67e 100644 --- a/ipykernel/subshell_manager.py +++ b/ipykernel/subshell_manager.py @@ -15,6 +15,7 @@ from .socket_pair import SocketPair from .subshell import SubshellThread from .thread import SHELL_CHANNEL_THREAD_NAME +from .utils import _async_in_context class SubshellManager: @@ -129,7 +130,9 @@ def set_on_recv_callback(self, on_recv_callback): """ assert current_thread() == self._parent_thread self._on_recv_callback = on_recv_callback - self._shell_channel_to_main.on_recv(IOLoop.current(), partial(self._on_recv_callback, None)) + self._shell_channel_to_main.on_recv( + IOLoop.current(), _async_in_context(partial(on_recv_callback, None)) + ) def set_subshell_aborting(self, subshell_id: str, aborting: bool) -> None: """Set the aborting flag of the specified subshell.""" @@ -165,7 +168,7 @@ def _create_subshell(self) -> str: subshell_thread.shell_channel_to_subshell.on_recv( subshell_thread.io_loop, - partial(self._on_recv_callback, subshell_id), + _async_in_context(partial(self._on_recv_callback, subshell_id)), ) subshell_thread.subshell_to_shell_channel.on_recv( diff --git a/ipykernel/utils.py b/ipykernel/utils.py index 8f6513ce0..50fbe8ba2 100644 --- a/ipykernel/utils.py +++ b/ipykernel/utils.py @@ -1,7 +1,17 @@ """Utilities""" +from __future__ import annotations + +import asyncio +import sys import typing as t from collections.abc import Mapping +from contextvars import copy_context +from functools import partial, wraps + +if t.TYPE_CHECKING: + from collections.abc import Callable + from contextvars import Context class LazyDict(Mapping[str, t.Any]): @@ -24,3 +34,48 @@ def __len__(self): def __iter__(self): return iter(self._dict) + + +T = t.TypeVar("T") +U = t.TypeVar("U") +V = t.TypeVar("V") + + +def _async_in_context( + f: Callable[..., t.Coroutine[T, U, V]], context: Context | None = None +) -> Callable[..., t.Coroutine[T, U, V]]: + """ + Wrapper to run a coroutine in a persistent ContextVar Context. + + Backports asyncio.create_task(context=...) behavior from Python 3.11 + """ + if context is None: + context = copy_context() + + if sys.version_info >= (3, 11): + + @wraps(f) + async def run_in_context(*args, **kwargs): + coro = f(*args, **kwargs) + return await asyncio.create_task(coro, context=context) + + return run_in_context + + # don't need this backport when we require 3.11 + # context_holder so we have a modifiable container for later calls + context_holder = [context] # type: ignore[unreachable] + + async def preserve_context(f, *args, **kwargs): + """call a coroutine, preserving the context after it is called""" + try: + return await f(*args, **kwargs) + finally: + # persist changes to the context for future calls + context_holder[0] = copy_context() + + @wraps(f) + async def run_in_context_pre311(*args, **kwargs): + ctx = context_holder[0] + return await ctx.run(partial(asyncio.create_task, preserve_context(f, *args, **kwargs))) + + return run_in_context_pre311 diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 39e2f1381..04c06a463 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -570,7 +570,7 @@ def _update_exit_now(self, change): # Over ZeroMQ, GUI control isn't done with PyOS_InputHook as there is no # interactive input being read; we provide event loop support in ipkernel def enable_gui(self, gui: typing.Any = None) -> None: - """Enable a given guil.""" + """Enable a given gui.""" from .eventloops import enable_gui as real_enable_gui try: diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 2c5fe2b58..cbdbceddd 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -848,3 +848,33 @@ def test_parent_header_and_ident(): msg_id, _ = execute(kc=kc, code="print(k._parent_ident['control'])") stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id) assert stdout == f"[b'{session}']\n" + + +def test_context_vars(): + with new_kernel() as kc: + msg_id, _ = execute( + kc=kc, + code="from contextvars import ContextVar, copy_context\nctxvar = ContextVar('var', default='default')", + ) + stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id) + + msg_id, _ = execute( + kc=kc, + code="print(ctxvar.get())", + ) + stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id) + assert stdout.strip() == "default" + + msg_id, _ = execute( + kc=kc, + code="ctxvar.set('set'); print(ctxvar.get())", + ) + stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id) + assert stdout.strip() == "set" + + msg_id, _ = execute( + kc=kc, + code="print(ctxvar.get())", + ) + stdout, _ = assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id) + assert stdout.strip() == "set" From dd1e09484854c8dedcd98436bc01b6b8e1cc9034 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Fri, 24 Oct 2025 13:24:35 +0200 Subject: [PATCH 1110/1195] update pre-commit and related (#1465) --- .pre-commit-config.yaml | 21 ++++++++++----------- ipykernel/debugger.py | 2 +- ipykernel/inprocess/client.py | 2 +- ipykernel/inprocess/ipkernel.py | 2 +- ipykernel/pylab/backend_inline.py | 2 +- ipykernel/pylab/config.py | 2 +- ipykernel/zmqshell.py | 8 ++++---- pyproject.toml | 2 +- tests/inprocess/test_kernel.py | 6 +++--- tests/test_async.py | 2 +- tests/test_embed_kernel.py | 2 +- tests/test_io.py | 2 +- tests/test_kernel.py | 28 ++++++++++++++-------------- tests/test_message_spec.py | 10 +++++----- 14 files changed, 45 insertions(+), 46 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3acbeb4a9..214d4cf10 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v5.0.0 + rev: v6.0.0 hooks: - id: check-case-conflict - id: check-ast @@ -22,16 +22,15 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.33.2 + rev: 0.34.1 hooks: - id: check-github-workflows - - repo: https://github.com/executablebooks/mdformat - rev: 0.7.22 + - repo: https://github.com/hukkin/mdformat + rev: 1.0.0 hooks: - id: mdformat - additional_dependencies: - [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] + additional_dependencies: [mdformat-footnote] - repo: https://github.com/pre-commit/mirrors-prettier rev: "v4.0.0-alpha.8" @@ -40,7 +39,7 @@ repos: types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.17.0" + rev: "v1.18.2" hooks: - id: mypy files: ipykernel @@ -55,7 +54,7 @@ repos: ] - repo: https://github.com/adamchainz/blacken-docs - rev: "1.19.1" + rev: "1.20.0" hooks: - id: blacken-docs additional_dependencies: [black==23.7.0] @@ -74,16 +73,16 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.12.4 + rev: v0.14.2 hooks: - - id: ruff + - id: ruff-check types_or: [python, jupyter] args: ["--fix", "--show-fixes"] - id: ruff-format types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2025.05.02" + rev: "2025.10.20" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 64320e631..cfa976107 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -429,7 +429,7 @@ def start(self): (self.shell_socket.getsockopt(ROUTING_ID)), ) - ident, msg = self.session.recv(self.shell_socket, mode=0) + _ident, msg = self.session.recv(self.shell_socket, mode=0) self.debugpy_initialized = msg["content"]["status"] == "ok" # Don't remove leading empty lines when debugging so the breakpoints are correctly positioned diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index ffe044826..d18eec12e 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -206,7 +206,7 @@ def _dispatch_to_kernel(self, msg): else: loop = asyncio.get_event_loop() # type:ignore[unreachable] loop.run_until_complete(kernel.dispatch_shell(msg_parts)) - idents, reply_msg = self.session.recv(stream, copy=False) + _idents, reply_msg = self.session.recv(stream, copy=False) self.shell_channel.call_handlers_later(reply_msg) def get_shell_msg(self, block=True, timeout=None): diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 62c15036b..b68bec442 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -135,7 +135,7 @@ def _io_dispatch(self, change): """Called when a message is sent to the IO socket.""" assert self.iopub_socket.io_thread is not None assert self.session is not None - ident, msg = self.session.recv(self.iopub_socket.io_thread.socket, copy=False) + _ident, msg = self.session.recv(self.iopub_socket.io_thread.socket, copy=False) for frontend in self.frontends: assert frontend is not None frontend.iopub_channel.call_handlers(msg) diff --git a/ipykernel/pylab/backend_inline.py b/ipykernel/pylab/backend_inline.py index fdeece28c..2ce3716b8 100644 --- a/ipykernel/pylab/backend_inline.py +++ b/ipykernel/pylab/backend_inline.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.backend_inline import * # type:ignore[import-untyped] # noqa: F403 # analysis: ignore +from matplotlib_inline.backend_inline import * # noqa: F403 # analysis: ignore warnings.warn( "`ipykernel.pylab.backend_inline` is deprecated, directly " diff --git a/ipykernel/pylab/config.py b/ipykernel/pylab/config.py index 0048bbf84..8c3a63ab4 100644 --- a/ipykernel/pylab/config.py +++ b/ipykernel/pylab/config.py @@ -5,7 +5,7 @@ import warnings -from matplotlib_inline.config import * # type:ignore[import-untyped] # noqa: F403 # analysis: ignore +from matplotlib_inline.config import * # noqa: F403 # analysis: ignore warnings.warn( "`ipykernel.pylab.config` is deprecated, directly use `matplotlib_inline.config`", diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 04c06a463..b70b1e5fd 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -352,7 +352,7 @@ def edit(self, parameter_s="", last_call=None): payload = {"source": "edit_magic", "filename": filename, "line_number": lineno} assert self.shell is not None - self.shell.payload_manager.write_payload(payload) + self.shell.payload_manager.write_payload(payload) # type: ignore[unreachable] # A few magics that are adapted to the specifics of using pexpect and a # remote terminal @@ -361,7 +361,7 @@ def edit(self, parameter_s="", last_call=None): def clear(self, arg_s): """Clear the terminal.""" assert self.shell is not None - if os.name == "posix": + if os.name == "posix": # type: ignore[unreachable] self.shell.system("clear") else: self.shell.system("cls") @@ -383,7 +383,7 @@ def less(self, arg_s): if arg_s.endswith(".py"): assert self.shell is not None - cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False)) + cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False)) # type: ignore[unreachable] else: with open(arg_s) as fid: cont = fid.read() @@ -398,7 +398,7 @@ def less(self, arg_s): def man(self, arg_s): """Find the man page for the given command and display in pager.""" assert self.shell is not None - page.page(self.shell.getoutput("man %s | col -b" % arg_s, split=False)) + page.page(self.shell.getoutput("man %s | col -b" % arg_s, split=False)) # type: ignore[unreachable] @line_magic def connect_info(self, arg_s): diff --git a/pyproject.toml b/pyproject.toml index c979015df..524b6ef22 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,7 +120,7 @@ dependencies = ["pre-commit"] detached = true [tool.hatch.envs.lint.scripts] build = [ - "pre-commit run --all-files ruff", + "pre-commit run --all-files ruff-check", "pre-commit run --all-files ruff-format" ] diff --git a/tests/inprocess/test_kernel.py b/tests/inprocess/test_kernel.py index 33692e85f..b965d9b89 100644 --- a/tests/inprocess/test_kernel.py +++ b/tests/inprocess/test_kernel.py @@ -57,7 +57,7 @@ def test_pylab(kc): """Does %pylab work in the in-process kernel?""" _ = pytest.importorskip("matplotlib", reason="This test requires matplotlib") kc.execute("%pylab") - out, err = assemble_output(kc.get_iopub_msg) + out, _err = assemble_output(kc.get_iopub_msg) assert "matplotlib" in out @@ -85,7 +85,7 @@ def test_stdout(kc): kc = BlockingInProcessKernelClient(kernel=kernel, session=kernel.session) kernel.frontends.append(kc) kc.execute('print("bar")') - out, err = assemble_output(kc.get_iopub_msg) + out, _err = assemble_output(kc.get_iopub_msg) assert out == "bar\n" @@ -102,7 +102,7 @@ def test_capfd(kc): kernel.frontends.append(kc) kc.execute("import os") kc.execute('os.system("echo capfd")') - out, err = assemble_output(kc.iopub_channel) + out, _err = assemble_output(kc.iopub_channel) assert out == "capfd\n" diff --git a/tests/test_async.py b/tests/test_async.py index 5a8445466..4e9b9ad46 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -23,7 +23,7 @@ def _setup_env(): def test_async_await(): flush_channels(KC) - msg_id, content = execute("import asyncio; await asyncio.sleep(0.1)", KC) + _msg_id, content = execute("import asyncio; await asyncio.sleep(0.1)", KC) assert content["status"] == "ok", content diff --git a/tests/test_embed_kernel.py b/tests/test_embed_kernel.py index 8e70f8b4c..d6ad44614 100644 --- a/tests/test_embed_kernel.py +++ b/tests/test_embed_kernel.py @@ -64,7 +64,7 @@ def connection_file_ready(connection_file): time.sleep(0.1) if kernel.poll() is not None: - o, e = kernel.communicate() + _o, e = kernel.communicate() raise OSError("Kernel failed to start:\n%s" % e) if not os.path.exists(connection_file): diff --git a/tests/test_io.py b/tests/test_io.py index 0e23b4b14..c7320af84 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -222,7 +222,7 @@ def test_echo_watch(ctx): print(f"{p.stderr}=", file=sys.stderr) assert p.returncode == 0 while s.poll(timeout=100): - ident, msg = session.recv(s) + _ident, msg = session.recv(s) assert msg is not None # for type narrowing if msg["header"]["msg_type"] == "stream" and msg["content"]["name"] == "stdout": stdout_chunks.append(msg["content"]["text"]) diff --git a/tests/test_kernel.py b/tests/test_kernel.py index cbdbceddd..7fc4d44d2 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -34,8 +34,8 @@ def _check_master(kc, expected=True, stream="stdout"): execute(kc=kc, code="import sys") flush_channels(kc) - msg_id, content = execute(kc=kc, code="print(sys.%s._is_master_process())" % stream) - stdout, stderr = assemble_output(kc.get_iopub_msg) + _msg_id, _content = execute(kc=kc, code="print(sys.%s._is_master_process())" % stream) + stdout, _stderr = assemble_output(kc.get_iopub_msg) assert stdout.strip() == repr(expected) @@ -51,7 +51,7 @@ def _check_status(content): def test_simple_print(): """simple print statement in kernel""" with kernel() as kc: - msg_id, content = execute(kc=kc, code="print('hi')") + _msg_id, _content = execute(kc=kc, code="print('hi')") stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == "hi\n" assert stderr == "" @@ -251,7 +251,7 @@ def test_capture_fd(): """simple print statement in kernel""" with kernel() as kc: iopub = kc.iopub_channel - msg_id, content = execute(kc=kc, code="import os; os.system('echo capsys')") + _msg_id, _content = execute(kc=kc, code="import os; os.system('echo capsys')") stdout, stderr = assemble_output(iopub) assert stdout == "capsys\n" assert stderr == "" @@ -262,7 +262,7 @@ def test_capture_fd(): def test_subprocess_peek_at_stream_fileno(): with kernel() as kc: iopub = kc.iopub_channel - msg_id, content = execute( + _msg_id, _content = execute( kc=kc, code="import subprocess, sys; subprocess.run(['python', '-c', 'import os; os.system(\"echo CAP1\"); print(\"CAP2\")'], stderr=sys.stderr)", ) @@ -275,7 +275,7 @@ def test_subprocess_peek_at_stream_fileno(): def test_sys_path(): """test that sys.path doesn't get messed up by default""" with kernel() as kc: - msg_id, content = execute(kc=kc, code="import sys; print(repr(sys.path))") + _msg_id, _content = execute(kc=kc, code="import sys; print(repr(sys.path))") stdout, stderr = assemble_output(kc.get_iopub_msg) # for error-output on failure sys.stderr.write(stderr) @@ -288,7 +288,7 @@ def test_sys_path_profile_dir(): """test that sys.path doesn't get messed up when `--profile-dir` is specified""" with new_kernel(["--profile-dir", locate_profile("default")]) as kc: - msg_id, content = execute(kc=kc, code="import sys; print(repr(sys.path))") + _msg_id, _content = execute(kc=kc, code="import sys; print(repr(sys.path))") stdout, stderr = assemble_output(kc.get_iopub_msg) # for error-output on failure sys.stderr.write(stderr) @@ -323,7 +323,7 @@ def test_subprocess_print(): ] ) - msg_id, content = execute(kc=kc, code=code) + _msg_id, _content = execute(kc=kc, code=code) stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout.count("hello") == np, stdout for n in range(np): @@ -347,7 +347,7 @@ def test_subprocess_noprint(): ] ) - msg_id, content = execute(kc=kc, code=code) + _msg_id, _content = execute(kc=kc, code=code) stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == "" assert stderr == "" @@ -373,7 +373,7 @@ def test_subprocess_error(): ] ) - msg_id, content = execute(kc=kc, code=code) + _msg_id, _content = execute(kc=kc, code=code) stdout, stderr = assemble_output(kc.get_iopub_msg) assert stdout == "" assert "ValueError" in stderr @@ -400,7 +400,7 @@ def test_raw_input(): kc.input(text) reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply["content"]["status"] == "ok" - stdout, stderr = assemble_output(kc.get_iopub_msg) + stdout, _stderr = assemble_output(kc.get_iopub_msg) assert stdout == text + "\n" @@ -548,14 +548,14 @@ def test_unc_paths(): kc.execute(f"cd {unc_file_path:s}") reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply["content"]["status"] == "ok" - out, err = assemble_output(kc.get_iopub_msg) + out, _err = assemble_output(kc.get_iopub_msg) assert unc_file_path in out flush_channels(kc) kc.execute(code="ls") reply = kc.get_shell_msg(timeout=TIMEOUT) assert reply["content"]["status"] == "ok" - out, err = assemble_output(kc.get_iopub_msg) + out, _err = assemble_output(kc.get_iopub_msg) assert "unc.txt" in out kc.execute(code="cd") @@ -774,7 +774,7 @@ def test_shutdown_subprocesses(): """Kernel exits after polite shutdown_request""" with new_kernel() as kc: km = kc.parent - msg_id, reply = execute( + _msg_id, reply = execute( f"from {__name__} import _start_children\n_start_children()", kc=kc, user_expressions={ diff --git a/tests/test_message_spec.py b/tests/test_message_spec.py index b35861cb3..497027d91 100644 --- a/tests/test_message_spec.py +++ b/tests/test_message_spec.py @@ -426,7 +426,7 @@ def test_non_execute_stop_on_error(): def test_user_expressions(): flush_channels() - msg_id, reply = execute(code="x=1", user_expressions=dict(foo="x+1")) + _msg_id, reply = execute(code="x=1", user_expressions=dict(foo="x+1")) user_expressions = reply["user_expressions"] assert user_expressions == { "foo": { @@ -440,7 +440,7 @@ def test_user_expressions(): def test_user_expressions_fail(): flush_channels() - msg_id, reply = execute(code="x=0", user_expressions=dict(foo="nosuchname")) + _msg_id, reply = execute(code="x=0", user_expressions=dict(foo="nosuchname")) user_expressions = reply["user_expressions"] foo = user_expressions["foo"] assert foo["status"] == "error" @@ -572,7 +572,7 @@ def test_single_payload(): transform) should avoid setting multiple set_next_input). """ flush_channels() - msg_id, reply = execute( + _msg_id, reply = execute( code="ip = get_ipython()\nfor i in range(3):\n ip.set_next_input('Hello There')\n" ) payload = reply["payload"] @@ -635,7 +635,7 @@ def test_history_search(): def test_stream(): flush_channels() - msg_id, reply = execute("print('hi')") + msg_id, _reply = execute("print('hi')") stdout = KC.get_iopub_msg(timeout=TIMEOUT) validate_message(stdout, "stream", msg_id) @@ -646,7 +646,7 @@ def test_stream(): def test_display_data(): flush_channels() - msg_id, reply = execute("from IPython.display import display; display(1)") + msg_id, _reply = execute("from IPython.display import display; display(1)") display = KC.get_iopub_msg(timeout=TIMEOUT) validate_message(display, "display_data", parent=msg_id) From 8446e02948c390793e129affeceedfa30ed5080f Mon Sep 17 00:00:00 2001 From: Paolo Tosco Date: Mon, 27 Oct 2025 05:37:23 +0100 Subject: [PATCH 1111/1195] Fix KeyboardInterrupt on Windows by manually resetting interrupt event (#1434) Co-authored-by: Min RK --- ipykernel/parentpoller.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ipykernel/parentpoller.py b/ipykernel/parentpoller.py index 895a785c7..941e8d12b 100644 --- a/ipykernel/parentpoller.py +++ b/ipykernel/parentpoller.py @@ -121,6 +121,7 @@ def run(self): if WAIT_OBJECT_0 <= result < len(handles): handle = handles[result - WAIT_OBJECT_0] + ctypes.windll.kernel32.ResetEvent(handle) # type:ignore[attr-defined] if handle == self.interrupt_handle: # check if signal handler is callable From 6f61a6835c217e42c406ee01b359e2fa235baf43 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 27 Oct 2025 02:21:45 -0700 Subject: [PATCH 1112/1195] test that matplotlib event loop integration is responsive (#1463) --- ipykernel/eventloops.py | 2 +- pyproject.toml | 1 + tests/conftest.py | 8 +++ tests/test_eventloop.py | 5 ++ tests/test_matplotlib_eventloops.py | 106 ++++++++++++++++++++++++++++ tests/utils.py | 8 ++- 6 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 tests/test_matplotlib_eventloops.py diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index 9423b208a..dd131172f 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -373,7 +373,7 @@ def loop_gtk3_exit(kernel): kernel._gtk.stop() -@register_integration("osx") +@register_integration("osx", "macosx") def loop_cocoa(kernel): """Start the kernel, coordinating with the Cocoa CFRunLoop event loop via the matplotlib MacOSX backend. diff --git a/pyproject.toml b/pyproject.toml index 524b6ef22..85d7515e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,7 @@ docs = [ test = [ "pytest>=7.0,<9", "pytest-cov", + # 'pytest-xvfb; platform_system == "Linux"', "flaky", "ipyparallel", "pre-commit", diff --git a/tests/conftest.py b/tests/conftest.py index 214b32aa8..540ad36c3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -4,6 +4,7 @@ from typing import no_type_check from unittest.mock import MagicMock +import pytest import pytest_asyncio import zmq from jupyter_client.session import Session @@ -20,6 +21,7 @@ # Windows resource = None # type:ignore +from .utils import new_kernel # Handle resource limit # Ensure a minimal soft limit of DEFAULT_SOFT if the current hard limit is at least that much. @@ -158,3 +160,9 @@ def ipkernel(): yield kernel kernel.destroy() ZMQInteractiveShell.clear_instance() + + +@pytest.fixture +def kc(): + with new_kernel() as kc: + yield kc diff --git a/tests/test_eventloop.py b/tests/test_eventloop.py index b18ceff5d..428a7cdb7 100644 --- a/tests/test_eventloop.py +++ b/tests/test_eventloop.py @@ -56,6 +56,11 @@ def _setup_env(): windows_skip = pytest.mark.skipif(os.name == "nt", reason="causing failures on windows") +# some part of this module seems to hang when run with xvfb +pytestmark = pytest.mark.skipif( + sys.platform == "linux" and bool(os.getenv("CI")), reason="hangs on linux CI" +) + @windows_skip @pytest.mark.skipif(sys.platform == "darwin", reason="hangs on macos") diff --git a/tests/test_matplotlib_eventloops.py b/tests/test_matplotlib_eventloops.py new file mode 100644 index 000000000..e9ee14864 --- /dev/null +++ b/tests/test_matplotlib_eventloops.py @@ -0,0 +1,106 @@ +import os +import sys +import time + +import pytest +from jupyter_client.blocking.client import BlockingKernelClient + +from .test_eventloop import qt_guis_avail +from .utils import assemble_output + +# these tests don't seem to work with xvfb yet +# these tests seem to be a problem on CI in general +pytestmark = pytest.mark.skipif( + bool(os.getenv("CI")), + reason="tests not working yet reliably on CI", +) + +guis = [] +if not sys.platform.startswith("tk"): + guis.append("tk") +if qt_guis_avail: + guis.append("qt") +if sys.platform == "darwin": + guis.append("osx") + +backends = { + "tk": "tkagg", + "qt": "qtagg", + "osx": "macosx", +} + + +def execute( + kc: BlockingKernelClient, + code: str, + timeout=120, +): + msg_id = kc.execute(code) + stdout, stderr = assemble_output(kc.get_iopub_msg, timeout=timeout, parent_msg_id=msg_id) + assert not stderr.strip() + return stdout.strip(), stderr.strip() + + +@pytest.mark.parametrize("gui", guis) +@pytest.mark.timeout(300) +def test_matplotlib_gui(kc, gui): + """Make sure matplotlib activates and its eventloop runs while the kernel is also responsive""" + pytest.importorskip("matplotlib", reason="this test requires matplotlib") + stdout, stderr = execute(kc, f"%matplotlib {gui}") + assert not stderr + # debug: show output from invoking the matplotlib magic + print(stdout) + execute( + kc, + """ + from concurrent.futures import Future + import matplotlib as mpl + import matplotlib.pyplot as plt + """, + ) + stdout, _ = execute(kc, "print(mpl.get_backend())") + assert stdout == backends[gui] + execute( + kc, + """ +fig, ax = plt.subplots() +timer = fig.canvas.new_timer(interval=10) +f = Future() + +call_count = 0 +def add_call(): + global call_count + call_count += 1 + if not f.done(): + f.set_result(None) + +timer.add_callback(add_call) +timer.start() +""", + ) + # wait for the first call (up to 60 seconds) + deadline = time.monotonic() + 60 + done = False + while time.monotonic() <= deadline: + stdout, _ = execute(kc, "print(f.done())") + if stdout.strip() == "True": + done = True + break + if stdout == "False": + time.sleep(0.1) + else: + pytest.fail(f"Unexpected output {stdout}") + if not done: + pytest.fail("future never finished...") + + time.sleep(0.25) + stdout, _ = execute(kc, "print(call_count)") + call_count = int(stdout) + assert call_count > 0 + time.sleep(0.25) + stdout, _ = execute(kc, "timer.stop()\nprint(call_count)") + call_count_2 = int(stdout) + assert call_count_2 > call_count + stdout, _ = execute(kc, "print(call_count)") + call_count_3 = int(stdout) + assert call_count_3 <= call_count_2 + 5 diff --git a/tests/utils.py b/tests/utils.py index 19a7b6be8..72585ef73 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -168,7 +168,7 @@ def new_kernel(argv=None): return manager.run_kernel(**kwargs) -def assemble_output(get_msg, timeout=1, parent_msg_id: str | None = None): +def assemble_output(get_msg, timeout=1, parent_msg_id: str | None = None, raise_error=True): """assemble stdout/err from an execution""" stdout = "" stderr = "" @@ -191,6 +191,12 @@ def assemble_output(get_msg, timeout=1, parent_msg_id: str | None = None): stderr += content["text"] else: raise KeyError("bad stream: %r" % content["name"]) + elif raise_error and msg["msg_type"] == "error": + tb = "\n".join(msg["content"]["traceback"]) + msg = f"Execution failed with:\n{tb}" + if stderr: + msg = f"{msg}\nstderr:\n{stderr}" + raise RuntimeError(msg) else: # other output, ignored pass From 39eaf96ab6db0d0ff9ad269831384c53a11e11d8 Mon Sep 17 00:00:00 2001 From: ianthomas23 Date: Mon, 27 Oct 2025 09:46:25 +0000 Subject: [PATCH 1113/1195] Publish 7.1.0 SHA256 hashes: ipykernel-7.1.0-py3-none-any.whl: 763b5ec6c5b7776f6a8d7ce09b267693b4e5ce75cb50ae696aaefb3c85e1ea4c ipykernel-7.1.0.tar.gz: 58a3fc88533d5930c3546dc7eac66c6d288acde4f801e2001e65edc5dc9cf0db --- CHANGELOG.md | 38 ++++++++++++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7350310b0..7fef0c497 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,42 @@ +## 7.1.0 + +IPykernel 7.1.0 fixes an issue where display outputs such as Matplotlib plots were not included when using `%notebook` magic to save sessions as `.ipynb` files (#1435). This is enabled using the traitlet `ZMQDisplayPublisher.store_display_history` which defaults to the previous behaviour of False. This is a minor release rather than a patch release due to the addition of the new traitlet. + +Output from threads is restored to the pre-6.29 behavior by default (route to latest cell, unless `get_ipython().set_parent()` is called explicitly from the thread. If it is called, output from that thread will continue to be routed to the same cell). This behavior is now opt-in, instead of unconditional (#1451). + +This release also fixes bugs that were introduced into the 7.x branch relating to Matplotlib plots in separate windows not being displayed correctly (#1458), kernels launched in new threads failing asserts (#1455), and `ContextVar`s persisting between cells (#1462). There is also a fix for keyboard interrupts on Windows (#1434). + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.0.1...6f61a6835c217e42c406ee01b359e2fa235baf43)) + +### Enhancements made + +- Store display outputs in history for `%notebook` magic [#1435](https://github.com/ipython/ipykernel/pull/1435) ([@Darshan808](https://github.com/Darshan808)) + +### Bugs fixed + +- fix ContextVar persistence across cells [#1462](https://github.com/ipython/ipykernel/pull/1462) ([@minrk](https://github.com/minrk)) +- Fix matplotlib eventloops [#1458](https://github.com/ipython/ipykernel/pull/1458) ([@ianthomas23](https://github.com/ianthomas23)) +- Refer to kernel launching thread instead of assuming the main thread [#1455](https://github.com/ipython/ipykernel/pull/1455) ([@dfalbel](https://github.com/dfalbel)) +- Fix routing of background thread output when no parent is set explicitly [#1451](https://github.com/ipython/ipykernel/pull/1451) ([@minrk](https://github.com/minrk)) +- Fix KeyboardInterrupt on Windows by manually resetting interrupt event [#1434](https://github.com/ipython/ipykernel/pull/1434) ([@ptosco](https://github.com/ptosco)) + +### Maintenance and upkeep improvements + +- update pre-commit and related [#1465](https://github.com/ipython/ipykernel/pull/1465) ([@Carreau](https://github.com/Carreau)) +- test that matplotlib event loop integration is responsive [#1463](https://github.com/ipython/ipykernel/pull/1463) ([@minrk](https://github.com/minrk)) +- update tests for 3.14 [#1453](https://github.com/ipython/ipykernel/pull/1453) ([@minrk](https://github.com/minrk)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2025-10-14&to=2025-10-27&type=c)) + +[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-10-14..2025-10-27&type=Issues) | [@Darshan808](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ADarshan808+updated%3A2025-10-14..2025-10-27&type=Issues) | [@dfalbel](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adfalbel+updated%3A2025-10-14..2025-10-27&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-10-14..2025-10-27&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2025-10-14..2025-10-27&type=Issues) | [@lumberbot-app](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Alumberbot-app+updated%3A2025-10-14..2025-10-27&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2025-10-14..2025-10-27&type=Issues) | [@ptosco](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aptosco+updated%3A2025-10-14..2025-10-27&type=Issues) + + + ## 7.0.1 IPykernel 7.0.1 is a bug fix release to support CPython 3.14. @@ -27,8 +63,6 @@ IPykernel 7.0.1 is a bug fix release to support CPython 3.14. [@bollwyvl](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abollwyvl+updated%3A2025-10-13..2025-10-14&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-10-13..2025-10-14&type=Issues) | [@cclauss](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Acclauss+updated%3A2025-10-13..2025-10-14&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-10-13..2025-10-14&type=Issues) - - ## 7.0.0 IPykernel 7.0.0 is a major release containing experimental support for [kernel subshells](https://github.com/jupyter/enhancement-proposals/pull/91). diff --git a/ipykernel/_version.py b/ipykernel/_version.py index e3f011017..806193c97 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "7.0.1" +__version__ = "7.1.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 31ec38aaeef5cbb53a17e1885c1915d487339a1c Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Thu, 30 Oct 2025 12:26:39 +0100 Subject: [PATCH 1114/1195] Test changing base method to async after #1295 (#1464) --- ipykernel/kernelbase.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index e241933a2..52d40b952 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -862,7 +862,7 @@ async def execute_request(self, stream, ident, parent): subshell_id = parent["header"].get("subshell_id") self._abort_queues(subshell_id) - def do_execute( + async def do_execute( self, code, silent, @@ -897,7 +897,7 @@ async def complete_request(self, stream, ident, parent): matches = json_clean(matches) self.session.send(stream, "complete_reply", matches, parent, ident) - def do_complete(self, code, cursor_pos): + async def do_complete(self, code, cursor_pos): """Override in subclasses to find completions.""" return { "matches": [], @@ -933,7 +933,7 @@ async def inspect_request(self, stream, ident, parent): msg = self.session.send(stream, "inspect_reply", reply_content, parent, ident) self.log.debug("%s", msg) - def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): + async def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): """Override in subclasses to allow introspection.""" return {"status": "ok", "data": {}, "metadata": {}, "found": False} @@ -957,7 +957,7 @@ async def history_request(self, stream, ident, parent): msg = self.session.send(stream, "history_reply", reply_content, parent, ident) self.log.debug("%s", msg) - def do_history( + async def do_history( self, hist_access_type, output, @@ -1097,7 +1097,7 @@ async def shutdown_request(self, stream, ident, parent): shell_io_loop = self.shell_stream.io_loop shell_io_loop.add_callback(shell_io_loop.stop) - def do_shutdown(self, restart): + async def do_shutdown(self, restart): """Override in subclasses to do things when the frontend shuts down the kernel. """ @@ -1123,7 +1123,7 @@ async def is_complete_request(self, stream, ident, parent): reply_msg = self.session.send(stream, "is_complete_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) - def do_is_complete(self, code): + async def do_is_complete(self, code): """Override in subclasses to find completions.""" return {"status": "unknown"} From d63090caac0d069d2daf2ac65d9b53778ddc209d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:18:47 +0000 Subject: [PATCH 1115/1195] chore: update pre-commit hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.14.2 → v0.14.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.14.2...v0.14.3) --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 214d4cf10..53a0922af 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -73,7 +73,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.2 + rev: v0.14.3 hooks: - id: ruff-check types_or: [python, jupyter] From c71e621a364a2124142efc5275d50580cd2f3a24 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 26 Nov 2025 09:08:10 +0000 Subject: [PATCH 1116/1195] Bump actions/checkout from 5 to 6 in the actions group (#1479) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 20 ++++++++++---------- .github/workflows/downstream.yml | 14 +++++++------- .github/workflows/nightly.yml | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3dd030d5f..03ab0a0ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - uses: actions/setup-python@v6 with: @@ -94,7 +94,7 @@ jobs: needs: - build steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: jupyterlab/maintainer-tools/.github/actions/report-coverage@v1 with: fail_under: 80 @@ -103,7 +103,7 @@ jobs: name: Test Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Run Linters run: | @@ -115,7 +115,7 @@ jobs: check_release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 with: @@ -124,7 +124,7 @@ jobs: test_docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Build API docs run: | @@ -146,7 +146,7 @@ jobs: python-version: ["3.10"] steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -169,7 +169,7 @@ jobs: timeout-minutes: 20 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: @@ -190,7 +190,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: @@ -204,7 +204,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1 @@ -220,6 +220,6 @@ jobs: link_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 601d322e1..ed9519dea 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -60,7 +60,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -75,7 +75,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -92,7 +92,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Python uses: actions/setup-python@v6 with: @@ -125,7 +125,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Setup Python uses: actions/setup-python@v6 with: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index dc620e9fa..e892e2a00 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -17,7 +17,7 @@ jobs: python-version: ["3.12"] steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 From e4d02f603f204c68bbbd96071d171d6429008fc8 Mon Sep 17 00:00:00 2001 From: Matt Newville Date: Wed, 26 Nov 2025 03:13:08 -0600 Subject: [PATCH 1117/1195] add close event for wx timer app in loop_wx (#1478) --- ipykernel/eventloops.py | 42 +++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index dd131172f..d79f7ee60 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -184,39 +184,45 @@ def _loop_wx(app): @register_integration("wx") def loop_wx(kernel): """Start a kernel with wx event loop support.""" - import wx - # Wx uses milliseconds - poll_interval = int(1000 * kernel._poll_interval) - - shell_stream = get_shell_stream(kernel) - - def wake(shell_stream): - """wake from wx""" - if shell_stream.flush(limit=1): - kernel.app.ExitMainLoop() - return - # We have to put the wx.Timer in a wx.Frame for it to fire properly. # We make the Frame hidden when we create it in the main app below. class TimerFrame(wx.Frame): # type:ignore[misc] - def __init__(self, func): + def __init__(self, kernel): + self.kernel = kernel + self.shell_stream = get_shell_stream(kernel) + wx.Frame.__init__(self, None, -1) self.timer = wx.Timer(self) - # Units for the timer are in milliseconds - self.timer.Start(poll_interval) + + self.Bind(wx.EVT_CLOSE, self.on_exit) self.Bind(wx.EVT_TIMER, self.on_timer) - self.func = func + + # Units for the timer are in milliseconds + self.timer.Start(int(1000 * self.kernel._poll_interval)) + + def wake(self): + """wake from wx""" + try: + if self.shell_stream.flush(limit=1): + self.kernel.app.ExitMainLoop() + except Exception: + pass def on_timer(self, event): - self.func() + self.wake() + + def on_exit(self, event): + self.timer.Stop() + self.wake() + self.Destroy() # We need a custom wx.App to create our Frame subclass that has the # wx.Timer to defer back to the tornado event loop. class IPWxApp(wx.App): # type:ignore[misc] def OnInit(self): - self.frame = TimerFrame(partial(wake, shell_stream)) + self.frame = TimerFrame(kernel) self.frame.Show(False) return True From 5b9f05bdc43ee224cc1410064471edc46106818a Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Wed, 26 Nov 2025 10:18:46 +0000 Subject: [PATCH 1118/1195] Fix linting errors (#1480) --- ipykernel/zmqshell.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index b70b1e5fd..9e9ded064 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -352,7 +352,7 @@ def edit(self, parameter_s="", last_call=None): payload = {"source": "edit_magic", "filename": filename, "line_number": lineno} assert self.shell is not None - self.shell.payload_manager.write_payload(payload) # type: ignore[unreachable] + self.shell.payload_manager.write_payload(payload) # type: ignore[union-attr] # A few magics that are adapted to the specifics of using pexpect and a # remote terminal @@ -361,7 +361,7 @@ def edit(self, parameter_s="", last_call=None): def clear(self, arg_s): """Clear the terminal.""" assert self.shell is not None - if os.name == "posix": # type: ignore[unreachable] + if os.name == "posix": self.shell.system("clear") else: self.shell.system("cls") @@ -383,7 +383,7 @@ def less(self, arg_s): if arg_s.endswith(".py"): assert self.shell is not None - cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False)) # type: ignore[unreachable] + cont = self.shell.pycolorize(openpy.read_py_file(arg_s, skip_encoding_cookie=False)) else: with open(arg_s) as fid: cont = fid.read() @@ -398,7 +398,7 @@ def less(self, arg_s): def man(self, arg_s): """Find the man page for the given command and display in pager.""" assert self.shell is not None - page.page(self.shell.getoutput("man %s | col -b" % arg_s, split=False)) # type: ignore[unreachable] + page.page(self.shell.getoutput("man %s | col -b" % arg_s, split=False)) @line_magic def connect_info(self, arg_s): From 6475f86c0d92437159daf972a87a1a1803d98f9f Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 1 Dec 2025 14:29:52 +0530 Subject: [PATCH 1119/1195] feat: Add dynamic skip rules for kernel modules to debugpy configuration. --- ipykernel/debugger.py | 8 +++++++- ipykernel/ipkernel.py | 7 +++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index cfa976107..7a158ba5b 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -326,7 +326,7 @@ class Debugger: ] def __init__( - self, log, debugpy_stream, event_callback, shell_socket, session, just_my_code=True + self, log, debugpy_stream, event_callback, shell_socket, session, kernel_modules, just_my_code=True ): """Initialize the debugger.""" self.log = log @@ -335,6 +335,7 @@ def __init__( self.session = session self.is_started = False self.event_callback = event_callback + self.kernel_modules = kernel_modules self.just_my_code = just_my_code self.stopped_queue: Queue[t.Any] = Queue() @@ -574,6 +575,11 @@ async def attach(self, message): # Set debugOptions for breakpoints in python standard library source. if not self.just_my_code: message["arguments"]["debugOptions"] = ["DebugStdLib"] + + # Dynamic skip rules (computed at kernel startup) + rules = [{"path": path, "include": False} for path in self.kernel_modules] + message["arguments"]["rules"] = rules + return await self._forward_message(message) async def configurationDone(self, message): diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index e1505a9a7..620f0bb4e 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -119,6 +119,12 @@ def __init__(self, **kwargs): from .debugger import _is_debugpy_available + self._kernel_modules = [ + m.__file__ + for m in sys.modules.values() + if hasattr(m, "__file__") and m.__file__ + ] + # Initialize the Debugger if _is_debugpy_available: self.debugger = self.debugger_class( @@ -127,6 +133,7 @@ def __init__(self, **kwargs): self._publish_debug_event, self.debug_shell_socket, self.session, + self._kernel_modules, self.debug_just_my_code, ) From 7993c995794948e9cef93ce947bb5cbbb4d43c90 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 1 Dec 2025 15:07:04 +0530 Subject: [PATCH 1120/1195] Introduce `filter_internal_frames` option to control internal frame filtering --- ipykernel/debugger.py | 8 +++++--- ipykernel/ipkernel.py | 1 + ipykernel/kernelbase.py | 9 ++++++++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 7a158ba5b..49383810f 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -326,7 +326,7 @@ class Debugger: ] def __init__( - self, log, debugpy_stream, event_callback, shell_socket, session, kernel_modules, just_my_code=True + self, log, debugpy_stream, event_callback, shell_socket, session, kernel_modules, just_my_code=False, filter_internal_frames=True ): """Initialize the debugger.""" self.log = log @@ -337,6 +337,7 @@ def __init__( self.event_callback = event_callback self.kernel_modules = kernel_modules self.just_my_code = just_my_code + self.filter_internal_frames = filter_internal_frames self.stopped_queue: Queue[t.Any] = Queue() self.started_debug_handlers = {} @@ -577,8 +578,9 @@ async def attach(self, message): message["arguments"]["debugOptions"] = ["DebugStdLib"] # Dynamic skip rules (computed at kernel startup) - rules = [{"path": path, "include": False} for path in self.kernel_modules] - message["arguments"]["rules"] = rules + if self.filter_internal_frames: + rules = [{"path": path, "include": False} for path in self.kernel_modules] + message["arguments"]["rules"] = rules return await self._forward_message(message) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 620f0bb4e..2a89d9ea9 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -135,6 +135,7 @@ def __init__(self, **kwargs): self.session, self._kernel_modules, self.debug_just_my_code, + self.filter_internal_frames, ) # Initialize the InteractiveShell subclass diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 52d40b952..7fa1fb9dc 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -179,11 +179,18 @@ def _default_ident(self): # The ipykernel source is in the call stack, so the user # has to manipulate the step-over and step-into in a wize way. debug_just_my_code = Bool( - True, + False, help="""Set to False if you want to debug python standard and dependent libraries. """, ).tag(config=True) + # Experimental option to filter internal frames from the stack trace and stepping. + filter_internal_frames = Bool( + True, + help="""Set to False if you want to debug kernel modules. + """, + ).tag(config=True) + # track associations with current request # Private interface From fa1bd64f330a0227fd2db3d36fecda88f904c7d2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Dec 2025 10:16:27 +0000 Subject: [PATCH 1121/1195] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ipykernel/debugger.py | 10 +++++++++- ipykernel/ipkernel.py | 4 +--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 49383810f..a3f604f70 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -326,7 +326,15 @@ class Debugger: ] def __init__( - self, log, debugpy_stream, event_callback, shell_socket, session, kernel_modules, just_my_code=False, filter_internal_frames=True + self, + log, + debugpy_stream, + event_callback, + shell_socket, + session, + kernel_modules, + just_my_code=False, + filter_internal_frames=True, ): """Initialize the debugger.""" self.log = log diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 2a89d9ea9..aa22bbb0f 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -120,9 +120,7 @@ def __init__(self, **kwargs): from .debugger import _is_debugpy_available self._kernel_modules = [ - m.__file__ - for m in sys.modules.values() - if hasattr(m, "__file__") and m.__file__ + m.__file__ for m in sys.modules.values() if hasattr(m, "__file__") and m.__file__ ] # Initialize the Debugger From dc865b87415e28aa4fd08dcc82cb2bc059afc4aa Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 1 Dec 2025 16:14:34 +0530 Subject: [PATCH 1122/1195] remove ipykernel stack frame filtering from stackTrace response --- ipykernel/debugger.py | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index a3f604f70..15952710e 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -509,23 +509,6 @@ async def source(self, message): async def stackTrace(self, message): """Handle a stack trace message.""" reply = await self._forward_message(message) - # The stackFrames array can have the following content: - # { frames from the notebook} - # ... - # { 'id': xxx, 'name': '', ... } <= this is the first frame of the code from the notebook - # { frames from ipykernel } - # ... - # {'id': yyy, 'name': '', ... } <= this is the first frame of ipykernel code - # or only the frames from the notebook. - # We want to remove all the frames from ipykernel when they are present. - try: - sf_list = reply["body"]["stackFrames"] - module_idx = len(sf_list) - next( - i for i, v in enumerate(reversed(sf_list), 1) if v["name"] == "" and i != 1 - ) - reply["body"]["stackFrames"] = reply["body"]["stackFrames"][: module_idx + 1] - except StopIteration: - pass return reply def accept_variable(self, variable_name): From a97f48afc436ee9ca78f11824033537a95a8f780 Mon Sep 17 00:00:00 2001 From: arjxn-py Date: Mon, 1 Dec 2025 18:41:15 +0530 Subject: [PATCH 1123/1195] remove unnecessary assignment --- ipykernel/debugger.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 15952710e..24fe92849 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -508,8 +508,7 @@ async def source(self, message): async def stackTrace(self, message): """Handle a stack trace message.""" - reply = await self._forward_message(message) - return reply + return await self._forward_message(message) def accept_variable(self, variable_name): """Accept a variable by name.""" From d7aaa3e5f16267efbef7cf21f3b4c984402b8484 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Dec 2025 22:02:52 +0000 Subject: [PATCH 1124/1195] Bump scientific-python/upload-nightly-action in the actions group Bumps the actions group with 1 update: [scientific-python/upload-nightly-action](https://github.com/scientific-python/upload-nightly-action). Updates `scientific-python/upload-nightly-action` from 0.6.2 to 0.6.3 - [Release notes](https://github.com/scientific-python/upload-nightly-action/releases) - [Commits](https://github.com/scientific-python/upload-nightly-action/compare/b36e8c0c10dbcfd2e05bf95f17ef8c14fd708dbf...5748273c71e2d8d3a61f3a11a16421c8954f9ecf) --- updated-dependencies: - dependency-name: scientific-python/upload-nightly-action dependency-version: 0.6.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] --- .github/workflows/nightly.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e892e2a00..ba566e1af 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -27,7 +27,7 @@ jobs: python -m pip install build python -m build - name: Upload wheel - uses: scientific-python/upload-nightly-action@b36e8c0c10dbcfd2e05bf95f17ef8c14fd708dbf # 0.6.2 + uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3 with: artifacts_path: dist anaconda_nightly_upload_token: ${{secrets.UPLOAD_TOKEN}} From 82c3047c7296b35d718cf910e72c530aeb999817 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Thu, 8 Jan 2026 10:52:50 +0100 Subject: [PATCH 1125/1195] Removed spyder downstream tests --- .github/workflows/downstream.yml | 64 -------------------------------- 1 file changed, 64 deletions(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index ed9519dea..894c8626f 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -86,67 +86,3 @@ jobs: cd jupyter_kernel_test pip install -e ".[test]" python test_ipykernel.py - - qtconsole: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.10" - architecture: "x64" - - name: Install System Packages - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends '^libxcb.*-dev' libx11-xcb-dev libglu1-mesa-dev libxrender-dev libxi-dev libxkbcommon-dev libxkbcommon-x11-dev - - name: Install qtconsole dependencies - shell: bash -l {0} - run: | - cd ${GITHUB_WORKSPACE}/.. - git clone https://github.com/spyder-ide/qtconsole.git - cd qtconsole - ${pythonLocation}/bin/python -m pip install -e ".[test]" - ${pythonLocation}/bin/python -m pip install pyqt5 - - name: Install Ipykernel changes - shell: bash -l {0} - run: ${pythonLocation}/bin/python -m pip install -e . - - name: Test qtconsole - shell: bash -l {0} - run: | - cd ${GITHUB_WORKSPACE}/../qtconsole - xvfb-run --auto-servernum ${pythonLocation}/bin/python -m pytest -x -vv -s --full-trace --color=yes qtconsole -k "not test_scroll" - - spyder_kernels: - runs-on: ubuntu-latest - if: false - timeout-minutes: 20 - steps: - - name: Checkout - uses: actions/checkout@v6 - - name: Setup Python - uses: actions/setup-python@v6 - with: - python-version: "3.10" - architecture: "x64" - - name: Install System Packages - run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends libgl1 libglx-mesa0 - - name: Install spyder-kernels dependencies - shell: bash -l {0} - run: | - cd ${GITHUB_WORKSPACE}/.. - git clone https://github.com/spyder-ide/spyder-kernels.git - cd spyder-kernels - ${pythonLocation}/bin/python -m pip install -e ".[test]" - - name: Install IPykernel changes - shell: bash -l {0} - run: ${pythonLocation}/bin/python -m pip install -e . - - name: Test spyder-kernels - shell: bash -l {0} - run: | - cd ${GITHUB_WORKSPACE}/../spyder-kernels - xvfb-run --auto-servernum ${pythonLocation}/bin/python -m pytest -x -vv -s --full-trace --color=yes spyder_kernels From 9f0489b20009e79247493706e4eef84389d7ee87 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Thu, 18 Dec 2025 16:46:45 +0100 Subject: [PATCH 1126/1195] Replaced PUB socket with XPUB socket --- ipykernel/inprocess/ipkernel.py | 2 +- ipykernel/iostream.py | 66 ++++++++++++++++++++++++++++++++- ipykernel/kernelapp.py | 4 +- tests/conftest.py | 2 +- tests/test_io.py | 5 ++- 5 files changed, 71 insertions(+), 8 deletions(-) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index b68bec442..7ed47443b 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -56,7 +56,7 @@ class InProcessKernel(IPythonKernel): @default("iopub_thread") def _default_iopub_thread(self): - thread = IOPubThread(self._underlying_iopub_socket) + thread = IOPubThread(self._underlying_iopub_socket, self.session) thread.start() return thread diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 0a2115f3b..9681c3f6f 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -47,7 +47,7 @@ class IOPubThread: whose IO is always run in a thread. """ - def __init__(self, socket, pipe=False): + def __init__(self, socket, session, pipe=False): """Create IOPub thread Parameters @@ -59,6 +59,7 @@ def __init__(self, socket, pipe=False): piped from subprocesses. """ self.socket = socket + self.session = session self._stopped = False self.background_socket = BackgroundSocket(self) self._master_pid = os.getpid() @@ -73,12 +74,73 @@ def __init__(self, socket, pipe=False): self._event_pipe_gc_seconds: float = 10 self._event_pipe_gc_task: asyncio.Task[Any] | None = None self._setup_event_pipe() + self._setup_xpub_listener() self.thread = threading.Thread(target=self._thread_main, name="IOPub") self.thread.daemon = True self.thread.pydev_do_not_trace = True # type:ignore[attr-defined] self.thread.is_pydev_daemon_thread = True # type:ignore[attr-defined] self.thread.name = "IOPub" + def _setup_xpub_listener(self): + """Setup listener for XPUB subscription events""" + + # Checks the socket is not a DummySocket + if not hasattr(self.socket, "getsockopt"): + return + + socket_type = self.socket.getsockopt(zmq.TYPE) + if socket_type == zmq.XPUB: + self._xpub_stream = ZMQStream(self.socket, self.io_loop) + self._xpub_stream.on_recv(self._handle_subscription) + + def _handle_subscription(self, frames): + """Handle subscription/unsubscription events from XPUB socket + + XPUB sockets receive: + - subscribe: single frame with b'\\x01' + topic + - unsubscribe: single frame with b'\\x00' + topic + """ + + for frame in frames: + event_type = frame[0] + if event_type == 1: + subscription = frame[1:] if len(frame) > 1 else b"" + try: + subscription_str = subscription.decode("utf-8") + except UnicodeDecodeError: + continue + self._send_welcome_message(subscription_str) + + def _send_welcome_message(self, subscription): + """Send iopub_welcome message for new subscription + + Parameters + ---------- + subscription : str + The subscription topic (UTF-8 decoded) + """ + + content = {"subscription": subscription} + + header = self.session.msg_header("iopub_welcome") + msg = { + "header": header, + "parent_header": {}, + "metadata": {}, + "content": content, + "buffers": [], + } + + msg_list = self.session.serialize(msg) + + if subscription: + identity = subscription.encode("utf-8") + full_msg = [identity, *msg_list] + else: + full_msg = msg_list + # Send directly on socket (we're already in IO thread context) + self.socket.send_multipart(full_msg) + def _thread_main(self): """The inner loop that's actually run in a thread""" @@ -447,7 +509,7 @@ def __init__( DeprecationWarning, stacklevel=2, ) - pub_thread = IOPubThread(pub_thread) + pub_thread = IOPubThread(pub_thread, self.session) pub_thread.start() self.pub_thread = pub_thread self.name = name diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 86b275a82..a450d6ca7 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -377,12 +377,12 @@ def init_control(self, context): def init_iopub(self, context): """Initialize the iopub channel.""" - self.iopub_socket = context.socket(zmq.PUB) + self.iopub_socket = context.socket(zmq.XPUB) self.iopub_socket.linger = 1000 self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port) self.log.debug("iopub PUB Channel on port: %i", self.iopub_port) self.configure_tornado_logger() - self.iopub_thread = IOPubThread(self.iopub_socket, pipe=True) + self.iopub_thread = IOPubThread(self.iopub_socket, self.session, pipe=True) self.iopub_thread.start() # backward-compat: wrap iopub socket API in background thread self.iopub_socket = self.iopub_thread.background_socket diff --git a/tests/conftest.py b/tests/conftest.py index 540ad36c3..ad6c5830a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -48,7 +48,7 @@ class KernelMixin: def _initialize(self): self.context = context = zmq.Context() - self.iopub_socket = context.socket(zmq.PUB) + self.iopub_socket = context.socket(zmq.XPUB) self.stdin_socket = context.socket(zmq.ROUTER) self.session = Session() self.test_sockets = [self.iopub_socket] diff --git a/tests/test_io.py b/tests/test_io.py index c7320af84..da1000f4e 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -26,8 +26,9 @@ def ctx(): @pytest.fixture() def iopub_thread(ctx): + session = Session() with ctx.socket(zmq.PUB) as pub: - thread = IOPubThread(pub) + thread = IOPubThread(pub, session) thread.start() yield thread @@ -155,7 +156,7 @@ def subprocess_test_echo_watch(): # use PUSH socket to avoid subscription issues with zmq.Context() as ctx, ctx.socket(zmq.PUSH) as pub: pub.connect(os.environ["IOPUB_URL"]) - iopub_thread = IOPubThread(pub) + iopub_thread = IOPubThread(pub, session) iopub_thread.start() stdout_fd = sys.stdout.fileno() sys.stdout.flush() From 4634d3fb3c73a895c576b1b095b57b1eab5e3b65 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 22 Dec 2025 15:12:20 +0100 Subject: [PATCH 1127/1195] FIxes ipyparallel --- ipykernel/iostream.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 9681c3f6f..a426139ed 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -47,7 +47,7 @@ class IOPubThread: whose IO is always run in a thread. """ - def __init__(self, socket, session, pipe=False): + def __init__(self, socket, session=None, pipe=False): """Create IOPub thread Parameters @@ -120,6 +120,15 @@ def _send_welcome_message(self, subscription): The subscription topic (UTF-8 decoded) """ + # TODO: This early return is for backward-compatibility with ipyparallel. + # This should be removed when ipykernel has been released with support of + # xpub and ipyparallel has been updated to pass the session parameter + # to IOPubThread upon construction. + # (NB: the call to fix is here: + # https://github.com/ipython/ipyparallel/blob/main/ipyparallel/engine/app.py#L679 + if self.session is None: + return + content = {"subscription": subscription} header = self.session.msg_header("iopub_welcome") From fc185cc5161b8db62f05accee8a5f5151a2318b4 Mon Sep 17 00:00:00 2001 From: ianthomas23 Date: Thu, 8 Jan 2026 11:03:20 +0000 Subject: [PATCH 1128/1195] Publish 7.2.0a0 SHA256 hashes: ipykernel-7.2.0a0-py3-none-any.whl: 5faaba2b9ffa4f0224ec1ad66efaa9656452f9da426c903185d8b03e3a64ad9c ipykernel-7.2.0a0.tar.gz: 914085b822867126f659f55237f0b07a2eab50dfc647019f3c2dc287bafba1f0 --- CHANGELOG.md | 38 ++++++++++++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7fef0c497..d99d12641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,42 @@ +## 7.2.0a0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.1.0...6786ddd040c1f0e8aaeae7e261eec511e2a37fd9)) + +### New features added + +- Replaced PUB socket with XPUB socket [#1482](https://github.com/ipython/ipykernel/pull/1482) ([@JohanMabille](https://github.com/JohanMabille), [@SylvainCorlay](https://github.com/SylvainCorlay)) + +### Enhancements made + +- Implement kernel-side callstack filtering for internal frames [#1481](https://github.com/ipython/ipykernel/pull/1481) ([@arjxn-py](https://github.com/arjxn-py), [@JohanMabille](https://github.com/JohanMabille)) + +### Bugs fixed + +- add close event for wx timer app in loop_wx [#1478](https://github.com/ipython/ipykernel/pull/1478) ([@newville](https://github.com/newville), [@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- Removed spyder downstream tests [#1486](https://github.com/ipython/ipykernel/pull/1486) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) +- Bump scientific-python/upload-nightly-action from 0.6.2 to 0.6.3 in the actions group [#1484](https://github.com/ipython/ipykernel/pull/1484) ([@JohanMabille](https://github.com/JohanMabille)) +- Fix linting errors [#1480](https://github.com/ipython/ipykernel/pull/1480) ([@ianthomas23](https://github.com/ianthomas23)) +- Bump actions/checkout from 5 to 6 in the actions group [#1479](https://github.com/ipython/ipykernel/pull/1479) ([@ianthomas23](https://github.com/ianthomas23)) +- chore: update pre-commit hooks [#1467](https://github.com/ipython/ipykernel/pull/1467) ([@JohanMabille](https://github.com/JohanMabille)) +- Test changing base method to async after #1295 [#1464](https://github.com/ipython/ipykernel/pull/1464) ([@Carreau](https://github.com/Carreau), [@ianthomas23](https://github.com/ianthomas23)) + +### Contributors to this release + +The following people contributed discussions, new ideas, code and documentation contributions, and review. +See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports). + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2025-10-27&to=2026-01-08&type=c)) + +@arjxn-py ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aarjxn-py+updated%3A2025-10-27..2026-01-08&type=Issues)) | @Carreau ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-10-27..2026-01-08&type=Issues)) | @ianthomas23 ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-10-27..2026-01-08&type=Issues)) | @JohanMabille ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2025-10-27..2026-01-08&type=Issues)) | @newville ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anewville+updated%3A2025-10-27..2026-01-08&type=Issues)) | @SylvainCorlay ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2025-10-27..2026-01-08&type=Issues)) + + + ## 7.1.0 IPykernel 7.1.0 fixes an issue where display outputs such as Matplotlib plots were not included when using `%notebook` magic to save sessions as `.ipynb` files (#1435). This is enabled using the traitlet `ZMQDisplayPublisher.store_display_history` which defaults to the previous behaviour of False. This is a minor release rather than a patch release due to the addition of the new traitlet. @@ -36,8 +72,6 @@ This release also fixes bugs that were introduced into the 7.x branch relating t [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-10-14..2025-10-27&type=Issues) | [@Darshan808](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ADarshan808+updated%3A2025-10-14..2025-10-27&type=Issues) | [@dfalbel](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adfalbel+updated%3A2025-10-14..2025-10-27&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-10-14..2025-10-27&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2025-10-14..2025-10-27&type=Issues) | [@lumberbot-app](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Alumberbot-app+updated%3A2025-10-14..2025-10-27&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2025-10-14..2025-10-27&type=Issues) | [@ptosco](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aptosco+updated%3A2025-10-14..2025-10-27&type=Issues) - - ## 7.0.1 IPykernel 7.0.1 is a bug fix release to support CPython 3.14. diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 806193c97..736ce47c7 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "7.1.0" +__version__ = "7.2.0a0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 29c2422885216359d0855887b5073f4cd72632f6 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Thu, 8 Jan 2026 16:26:51 +0100 Subject: [PATCH 1129/1195] Upgrade to jupyter_client 8.8.0 (#1487) --- pyproject.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 85d7515e9..ac31b558e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,11 +23,11 @@ dependencies = [ "ipython>=7.23.1", "comm>=0.1.1", "traitlets>=5.4.0", - "jupyter_client>=8.0.0", - "jupyter_core>=4.12,!=5.0.*", + "jupyter_client>=8.8.0", + "jupyter_core>=5.1,!=6.0.*", # For tk event loop support only. "nest_asyncio>=1.4", - "tornado>=6.2", + "tornado>=6.4.1", "matplotlib-inline>=0.1", 'appnope>=0.1.2;platform_system=="Darwin"', "pyzmq>=25", From ad85e4203c348074df86d98ad26fbf01562e772d Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Thu, 18 Dec 2025 17:51:18 +0100 Subject: [PATCH 1130/1195] Added kernel_protocol_version to kernelspec --- ipykernel/kernelspec.py | 1 + tests/test_kernelspec.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index c0cc9ffe7..a9990c90a 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -67,6 +67,7 @@ def get_kernel_dict( "display_name": "Python %i (ipykernel)" % sys.version_info[0], "language": "python", "metadata": {"debugger": True}, + "kernel_protocol_version": "5.4", } diff --git a/tests/test_kernelspec.py b/tests/test_kernelspec.py index c3b62b21a..4deca842b 100644 --- a/tests/test_kernelspec.py +++ b/tests/test_kernelspec.py @@ -35,6 +35,7 @@ def assert_kernel_dict(d): assert d["argv"] == make_ipkernel_cmd() assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0] assert d["language"] == "python" + assert d["kernel_protocol_version"] == "5.4" def test_get_kernel_dict(): @@ -46,6 +47,7 @@ def assert_kernel_dict_with_profile(d): assert d["argv"] == make_ipkernel_cmd(extra_arguments=["--profile", "test"]) assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0] assert d["language"] == "python" + assert d["kernel_protocol_version"] == "5.4" def test_get_kernel_dict_with_profile(): From 59f0c6525cd3b23c28e5af7257f5db6bb8f78ae6 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Thu, 8 Jan 2026 17:28:57 +0100 Subject: [PATCH 1131/1195] Advertizes kernel protocol 5.5 --- ipykernel/kernelspec.py | 2 +- tests/test_kernelspec.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index a9990c90a..f08005a24 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -67,7 +67,7 @@ def get_kernel_dict( "display_name": "Python %i (ipykernel)" % sys.version_info[0], "language": "python", "metadata": {"debugger": True}, - "kernel_protocol_version": "5.4", + "kernel_protocol_version": "5.5", } diff --git a/tests/test_kernelspec.py b/tests/test_kernelspec.py index 4deca842b..66fce69b7 100644 --- a/tests/test_kernelspec.py +++ b/tests/test_kernelspec.py @@ -35,7 +35,7 @@ def assert_kernel_dict(d): assert d["argv"] == make_ipkernel_cmd() assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0] assert d["language"] == "python" - assert d["kernel_protocol_version"] == "5.4" + assert d["kernel_protocol_version"] == "5.5" def test_get_kernel_dict(): @@ -47,7 +47,7 @@ def assert_kernel_dict_with_profile(d): assert d["argv"] == make_ipkernel_cmd(extra_arguments=["--profile", "test"]) assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0] assert d["language"] == "python" - assert d["kernel_protocol_version"] == "5.4" + assert d["kernel_protocol_version"] == "5.5" def test_get_kernel_dict_with_profile(): From 56b2e29a29236a5aee494c332c2832cf78bb5f21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Jan 2026 03:03:00 +0000 Subject: [PATCH 1132/1195] Update pytest requirement in the actions group across 1 directory Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version. Updates `pytest` to 9.0.2 - [Release notes](https://github.com/pytest-dev/pytest/releases) - [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest/compare/7.0.0...9.0.2) --- updated-dependencies: - dependency-name: pytest dependency-version: 9.0.2 dependency-type: direct:development dependency-group: actions ... Signed-off-by: dependabot[bot] --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ac31b558e..8ce0eb4c3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ docs = [ "trio" ] test = [ - "pytest>=7.0,<9", + "pytest>=7.0,<10", "pytest-cov", # 'pytest-xvfb; platform_system == "Linux"', "flaky", From 220a3c6e8b24ffb3f8678925712ff3644aafb41e Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Fri, 30 Jan 2026 11:22:26 +0100 Subject: [PATCH 1133/1195] Made IOPubThread constructor backward compatible (#1492) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/inprocess/ipkernel.py | 2 +- ipykernel/iostream.py | 4 ++-- ipykernel/kernelapp.py | 2 +- tests/test_io.py | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 7ed47443b..e61af4277 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -56,7 +56,7 @@ class InProcessKernel(IPythonKernel): @default("iopub_thread") def _default_iopub_thread(self): - thread = IOPubThread(self._underlying_iopub_socket, self.session) + thread = IOPubThread(self._underlying_iopub_socket, session=self.session) thread.start() return thread diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index a426139ed..33213d167 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -47,7 +47,7 @@ class IOPubThread: whose IO is always run in a thread. """ - def __init__(self, socket, session=None, pipe=False): + def __init__(self, socket, pipe=False, session=False): """Create IOPub thread Parameters @@ -518,7 +518,7 @@ def __init__( DeprecationWarning, stacklevel=2, ) - pub_thread = IOPubThread(pub_thread, self.session) + pub_thread = IOPubThread(pub_thread, session=self.session) pub_thread.start() self.pub_thread = pub_thread self.name = name diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index a450d6ca7..b2f614ea9 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -382,7 +382,7 @@ def init_iopub(self, context): self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port) self.log.debug("iopub PUB Channel on port: %i", self.iopub_port) self.configure_tornado_logger() - self.iopub_thread = IOPubThread(self.iopub_socket, self.session, pipe=True) + self.iopub_thread = IOPubThread(self.iopub_socket, pipe=True, session=self.session) self.iopub_thread.start() # backward-compat: wrap iopub socket API in background thread self.iopub_socket = self.iopub_thread.background_socket diff --git a/tests/test_io.py b/tests/test_io.py index da1000f4e..217680a4f 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -28,7 +28,7 @@ def ctx(): def iopub_thread(ctx): session = Session() with ctx.socket(zmq.PUB) as pub: - thread = IOPubThread(pub, session) + thread = IOPubThread(pub, session=session) thread.start() yield thread @@ -156,7 +156,7 @@ def subprocess_test_echo_watch(): # use PUSH socket to avoid subscription issues with zmq.Context() as ctx, ctx.socket(zmq.PUSH) as pub: pub.connect(os.environ["IOPUB_URL"]) - iopub_thread = IOPubThread(pub, session) + iopub_thread = IOPubThread(pub, session=session) iopub_thread.start() stdout_fd = sys.stdout.fileno() sys.stdout.flush() From 06c9aee5793896c497e900796145a6a3a23feb25 Mon Sep 17 00:00:00 2001 From: ianthomas23 Date: Fri, 30 Jan 2026 10:35:59 +0000 Subject: [PATCH 1134/1195] Publish 7.2.0a1 SHA256 hashes: ipykernel-7.2.0a1-py3-none-any.whl: c6bb11acd2709516b249b2d79b9d21a081c2ae9f0a752730a364bb6e31602cc9 ipykernel-7.2.0a1.tar.gz: 0518cde8025c07f628b4dab09a84a7bd68aa456439f146d3a271185c4064f446 --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d99d12641..06b7caa15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,35 @@ +## 7.2.0a1 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.2.0a0...220a3c6e8b24ffb3f8678925712ff3644aafb41e)) + +### New features added + +- Added kernel_protocol_version to kernelspec [#1483](https://github.com/ipython/ipykernel/pull/1483) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) + +### Enhancements made + +- Made IOPubThread constructor backward compatible [#1492](https://github.com/ipython/ipykernel/pull/1492) ([@JohanMabille](https://github.com/JohanMabille), [@SylvainCorlay](https://github.com/SylvainCorlay), [@ianthomas23](https://github.com/ianthomas23)) +- Advertizes kernel protocol 5.5 [#1488](https://github.com/ipython/ipykernel/pull/1488) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) +- Upgrade to jupyter_client 8.8.0 [#1487](https://github.com/ipython/ipykernel/pull/1487) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- Update pytest requirement from \<9,>=7.0 to >=7.0,\<10 in the actions group across 1 directory [#1489](https://github.com/ipython/ipykernel/pull/1489) ([@JohanMabille](https://github.com/JohanMabille)) + +### Contributors to this release + +The following people contributed discussions, new ideas, code and documentation contributions, and review. +See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports). + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2026-01-08&to=2026-01-30&type=c)) + +@ianthomas23 ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2026-01-08..2026-01-30&type=Issues)) | @JohanMabille ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2026-01-08..2026-01-30&type=Issues)) | @SylvainCorlay ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2026-01-08..2026-01-30&type=Issues)) + + + ## 7.2.0a0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.1.0...6786ddd040c1f0e8aaeae7e261eec511e2a37fd9)) @@ -36,8 +65,6 @@ See [our definition of contributors](https://github-activity.readthedocs.io/en/l @arjxn-py ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aarjxn-py+updated%3A2025-10-27..2026-01-08&type=Issues)) | @Carreau ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-10-27..2026-01-08&type=Issues)) | @ianthomas23 ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-10-27..2026-01-08&type=Issues)) | @JohanMabille ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2025-10-27..2026-01-08&type=Issues)) | @newville ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anewville+updated%3A2025-10-27..2026-01-08&type=Issues)) | @SylvainCorlay ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2025-10-27..2026-01-08&type=Issues)) - - ## 7.1.0 IPykernel 7.1.0 fixes an issue where display outputs such as Matplotlib plots were not included when using `%notebook` magic to save sessions as `.ipynb` files (#1435). This is enabled using the traitlet `ZMQDisplayPublisher.store_display_history` which defaults to the previous behaviour of False. This is a minor release rather than a patch release due to the addition of the new traitlet. diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 736ce47c7..35e3142f2 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "7.2.0a0" +__version__ = "7.2.0a1" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From a2d47a2ca372509e553737b196f995f313949b2c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 30 Jan 2026 11:07:58 +0000 Subject: [PATCH 1135/1195] chore: update pre-commit hooks (#1472) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ian Thomas --- .pre-commit-config.yaml | 6 +++--- CHANGELOG.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 53a0922af..9efa63150 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.34.1 + rev: 0.36.1 hooks: - id: check-github-workflows @@ -39,7 +39,7 @@ repos: types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.18.2" + rev: "v1.19.1" hooks: - id: mypy files: ipykernel @@ -73,7 +73,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.3 + rev: v0.14.14 hooks: - id: ruff-check types_or: [python, jupyter] diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b7caa15..fa732e346 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ ### Enhancements made - Made IOPubThread constructor backward compatible [#1492](https://github.com/ipython/ipykernel/pull/1492) ([@JohanMabille](https://github.com/JohanMabille), [@SylvainCorlay](https://github.com/SylvainCorlay), [@ianthomas23](https://github.com/ianthomas23)) -- Advertizes kernel protocol 5.5 [#1488](https://github.com/ipython/ipykernel/pull/1488) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) +- Advertises kernel protocol 5.5 [#1488](https://github.com/ipython/ipykernel/pull/1488) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) - Upgrade to jupyter_client 8.8.0 [#1487](https://github.com/ipython/ipykernel/pull/1487) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) ### Maintenance and upkeep improvements From 8086199395f1dc069c46582e2a7373b00a25b8b8 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 30 Jan 2026 11:34:55 +0000 Subject: [PATCH 1136/1195] Temporarily revert "Test changing base method to async after (#1464)" This reverts commit 31ec38aaeef5cbb53a17e1885c1915d487339a1c. --- ipykernel/kernelbase.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 7fa1fb9dc..05bc10e90 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -869,7 +869,7 @@ async def execute_request(self, stream, ident, parent): subshell_id = parent["header"].get("subshell_id") self._abort_queues(subshell_id) - async def do_execute( + def do_execute( self, code, silent, @@ -904,7 +904,7 @@ async def complete_request(self, stream, ident, parent): matches = json_clean(matches) self.session.send(stream, "complete_reply", matches, parent, ident) - async def do_complete(self, code, cursor_pos): + def do_complete(self, code, cursor_pos): """Override in subclasses to find completions.""" return { "matches": [], @@ -940,7 +940,7 @@ async def inspect_request(self, stream, ident, parent): msg = self.session.send(stream, "inspect_reply", reply_content, parent, ident) self.log.debug("%s", msg) - async def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): + def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): """Override in subclasses to allow introspection.""" return {"status": "ok", "data": {}, "metadata": {}, "found": False} @@ -964,7 +964,7 @@ async def history_request(self, stream, ident, parent): msg = self.session.send(stream, "history_reply", reply_content, parent, ident) self.log.debug("%s", msg) - async def do_history( + def do_history( self, hist_access_type, output, @@ -1104,7 +1104,7 @@ async def shutdown_request(self, stream, ident, parent): shell_io_loop = self.shell_stream.io_loop shell_io_loop.add_callback(shell_io_loop.stop) - async def do_shutdown(self, restart): + def do_shutdown(self, restart): """Override in subclasses to do things when the frontend shuts down the kernel. """ @@ -1130,7 +1130,7 @@ async def is_complete_request(self, stream, ident, parent): reply_msg = self.session.send(stream, "is_complete_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) - async def do_is_complete(self, code): + def do_is_complete(self, code): """Override in subclasses to find completions.""" return {"status": "unknown"} From 4b37e7504b1e7563b434b23961d2d6b75dc95ed1 Mon Sep 17 00:00:00 2001 From: ianthomas23 Date: Fri, 6 Feb 2026 16:43:14 +0000 Subject: [PATCH 1137/1195] Publish 7.2.0 SHA256 hashes: ipykernel-7.2.0-py3-none-any.whl: 3bbd4420d2b3cc105cbdf3756bfc04500b1e52f090a90716851f3916c62e1661 ipykernel-7.2.0.tar.gz: 18ed160b6dee2cbb16e5f3575858bc19d8f1fe6046a9a680c708494ce31d909e --- CHANGELOG.md | 45 +++++++++++++++++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa732e346..ac5d7ada4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,49 @@ +## 7.2.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/39eaf96a...1630c4f7d5365918c4f06cf3caee3c278b52afc2)) + +### New features added + +- Added kernel_protocol_version to kernelspec [#1483](https://github.com/ipython/ipykernel/pull/1483) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) +- Replaced PUB socket with XPUB socket [#1482](https://github.com/ipython/ipykernel/pull/1482) ([@JohanMabille](https://github.com/JohanMabille), [@SylvainCorlay](https://github.com/SylvainCorlay), [@ccordoba12](https://github.com/ccordoba12), [@ianthomas23](https://github.com/ianthomas23), [@minrk](https://github.com/minrk)) + +### Enhancements made + +- Made IOPubThread constructor backward compatible [#1492](https://github.com/ipython/ipykernel/pull/1492) ([@JohanMabille](https://github.com/JohanMabille), [@SylvainCorlay](https://github.com/SylvainCorlay), [@ianthomas23](https://github.com/ianthomas23), [@minrk](https://github.com/minrk)) +- Advertizes kernel protocol 5.5 [#1488](https://github.com/ipython/ipykernel/pull/1488) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) +- Upgrade to jupyter_client 8.8.0 [#1487](https://github.com/ipython/ipykernel/pull/1487) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) +- Implement kernel-side callstack filtering for internal frames [#1481](https://github.com/ipython/ipykernel/pull/1481) ([@arjxn-py](https://github.com/arjxn-py), [@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) + +### Bugs fixed + +- add close event for wx timer app in loop_wx [#1478](https://github.com/ipython/ipykernel/pull/1478) ([@newville](https://github.com/newville), [@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- Temporarily revert "Test changing base method to async after (#1464)" [#1493](https://github.com/ipython/ipykernel/pull/1493) ([@ianthomas23](https://github.com/ianthomas23), [@JohanMabille](https://github.com/JohanMabille)) +- Update pytest requirement from \<9,>=7.0 to >=7.0,\<10 in the actions group across 1 directory [#1489](https://github.com/ipython/ipykernel/pull/1489) ([@JohanMabille](https://github.com/JohanMabille)) +- Removed spyder downstream tests [#1486](https://github.com/ipython/ipykernel/pull/1486) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) +- Bump scientific-python/upload-nightly-action from 0.6.2 to 0.6.3 in the actions group [#1484](https://github.com/ipython/ipykernel/pull/1484) ([@JohanMabille](https://github.com/JohanMabille)) +- Fix linting errors [#1480](https://github.com/ipython/ipykernel/pull/1480) ([@ianthomas23](https://github.com/ianthomas23)) +- Bump actions/checkout from 5 to 6 in the actions group [#1479](https://github.com/ipython/ipykernel/pull/1479) ([@ianthomas23](https://github.com/ianthomas23)) +- chore: update pre-commit hooks [#1472](https://github.com/ipython/ipykernel/pull/1472) ([@ianthomas23](https://github.com/ianthomas23)) +- chore: update pre-commit hooks [#1467](https://github.com/ipython/ipykernel/pull/1467) ([@JohanMabille](https://github.com/JohanMabille)) +- Test changing base method to async after #1295 [#1464](https://github.com/ipython/ipykernel/pull/1464) ([@Carreau](https://github.com/Carreau), [@ianthomas23](https://github.com/ianthomas23)) + +### Contributors to this release + +The following people contributed discussions, new ideas, code and documentation contributions, and review. +See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports). + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2025-10-27&to=2026-02-06&type=c)) + +@arjxn-py ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aarjxn-py+updated%3A2025-10-27..2026-02-06&type=Issues)) | @Carreau ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-10-27..2026-02-06&type=Issues)) | @ccordoba12 ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2025-10-27..2026-02-06&type=Issues)) | @ianthomas23 ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-10-27..2026-02-06&type=Issues)) | @JohanMabille ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2025-10-27..2026-02-06&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2025-10-27..2026-02-06&type=Issues)) | @newville ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anewville+updated%3A2025-10-27..2026-02-06&type=Issues)) | @SylvainCorlay ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2025-10-27..2026-02-06&type=Issues)) + + + ## 7.2.0a1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.2.0a0...220a3c6e8b24ffb3f8678925712ff3644aafb41e)) @@ -29,8 +72,6 @@ See [our definition of contributors](https://github-activity.readthedocs.io/en/l @ianthomas23 ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2026-01-08..2026-01-30&type=Issues)) | @JohanMabille ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2026-01-08..2026-01-30&type=Issues)) | @SylvainCorlay ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2026-01-08..2026-01-30&type=Issues)) - - ## 7.2.0a0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.1.0...6786ddd040c1f0e8aaeae7e261eec511e2a37fd9)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 35e3142f2..4c4be1dc4 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "7.2.0a1" +__version__ = "7.2.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From 5d1b61698ee273777ce054b5f06d9501e392a780 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 27 Feb 2026 10:56:50 +0000 Subject: [PATCH 1138/1195] Temporary pin virtualenv < 21 (#1498) --- .github/workflows/ci.yml | 46 ++++++++++++++++++++++++++++++++++++++-- CHANGELOG.md | 2 +- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03ab0a0ba..c46aab31d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: - name: Install hatch run: | python --version - python -m pip install hatch + python -m pip install hatch "virtualenv<21" - name: Run the tests timeout-minutes: 15 @@ -104,6 +104,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + + - name: Preinstall hatch and virtualenv + run: | + python --version + python -m pip install hatch "virtualenv<21" + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Run Linters run: | @@ -116,7 +122,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + + - name: Preinstall hatch and virtualenv + run: | + python --version + python -m pip install hatch "virtualenv<21" + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 with: token: ${{ secrets.GITHUB_TOKEN }} @@ -125,7 +138,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + + - name: Preinstall hatch and virtualenv + run: | + python --version + python -m pip install hatch "virtualenv<21" + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Build API docs run: | hatch run docs:api @@ -148,6 +168,11 @@ jobs: - name: Checkout uses: actions/checkout@v6 + - name: Preinstall hatch and virtualenv + run: | + python --version + python -m pip install hatch "virtualenv<21" + - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -170,11 +195,21 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: "3.10" + + - name: Preinstall hatch and virtualenv + run: | + python --version + python -m pip install hatch "virtualenv<21" + - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: dependency_type: minimum - python_version: "3.10" + python_version: ${{ matrix.python-version }} - name: List installed packages run: | @@ -191,10 +226,17 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + + - name: Preinstall hatch and virtualenv + run: | + python --version + python -m pip install hatch "virtualenv<21" + - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: dependency_type: pre + - name: Run the tests run: | hatch run test:nowarn diff --git a/CHANGELOG.md b/CHANGELOG.md index ac5d7ada4..97c1f230b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ ### Enhancements made - Made IOPubThread constructor backward compatible [#1492](https://github.com/ipython/ipykernel/pull/1492) ([@JohanMabille](https://github.com/JohanMabille), [@SylvainCorlay](https://github.com/SylvainCorlay), [@ianthomas23](https://github.com/ianthomas23), [@minrk](https://github.com/minrk)) -- Advertizes kernel protocol 5.5 [#1488](https://github.com/ipython/ipykernel/pull/1488) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) +- Advertises kernel protocol 5.5 [#1488](https://github.com/ipython/ipykernel/pull/1488) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) - Upgrade to jupyter_client 8.8.0 [#1487](https://github.com/ipython/ipykernel/pull/1487) ([@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) - Implement kernel-side callstack filtering for internal frames [#1481](https://github.com/ipython/ipykernel/pull/1481) ([@arjxn-py](https://github.com/arjxn-py), [@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) From 2b43f5e2ec1e724a2fbf184d47263a24002608f3 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 27 Feb 2026 11:11:56 +0000 Subject: [PATCH 1139/1195] Reintroduce changing base method to async (#1497) --- ipykernel/kernelbase.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 05bc10e90..7fa1fb9dc 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -869,7 +869,7 @@ async def execute_request(self, stream, ident, parent): subshell_id = parent["header"].get("subshell_id") self._abort_queues(subshell_id) - def do_execute( + async def do_execute( self, code, silent, @@ -904,7 +904,7 @@ async def complete_request(self, stream, ident, parent): matches = json_clean(matches) self.session.send(stream, "complete_reply", matches, parent, ident) - def do_complete(self, code, cursor_pos): + async def do_complete(self, code, cursor_pos): """Override in subclasses to find completions.""" return { "matches": [], @@ -940,7 +940,7 @@ async def inspect_request(self, stream, ident, parent): msg = self.session.send(stream, "inspect_reply", reply_content, parent, ident) self.log.debug("%s", msg) - def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): + async def do_inspect(self, code, cursor_pos, detail_level=0, omit_sections=()): """Override in subclasses to allow introspection.""" return {"status": "ok", "data": {}, "metadata": {}, "found": False} @@ -964,7 +964,7 @@ async def history_request(self, stream, ident, parent): msg = self.session.send(stream, "history_reply", reply_content, parent, ident) self.log.debug("%s", msg) - def do_history( + async def do_history( self, hist_access_type, output, @@ -1104,7 +1104,7 @@ async def shutdown_request(self, stream, ident, parent): shell_io_loop = self.shell_stream.io_loop shell_io_loop.add_callback(shell_io_loop.stop) - def do_shutdown(self, restart): + async def do_shutdown(self, restart): """Override in subclasses to do things when the frontend shuts down the kernel. """ @@ -1130,7 +1130,7 @@ async def is_complete_request(self, stream, ident, parent): reply_msg = self.session.send(stream, "is_complete_reply", reply_content, parent, ident) self.log.debug("%s", reply_msg) - def do_is_complete(self, code): + async def do_is_complete(self, code): """Override in subclasses to find completions.""" return {"status": "unknown"} From ed5e40015c18e4255581b61a591cc9e8bf22283d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:53:42 +0000 Subject: [PATCH 1140/1195] chore: update pre-commit hooks (#1495) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ian Thomas --- .pre-commit-config.yaml | 6 +++--- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9efa63150..ac395bdf4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.36.1 + rev: 0.36.2 hooks: - id: check-github-workflows @@ -73,7 +73,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.14 + rev: v0.15.2 hooks: - id: ruff-check types_or: [python, jupyter] @@ -82,7 +82,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2025.10.20" + rev: "2025.11.21" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/pyproject.toml b/pyproject.toml index 8ce0eb4c3..12f1c9e3c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -289,4 +289,4 @@ toplevel = ["ipykernel/", "ipykernel_launcher.py"] ignore = ["W002"] [tool.repo-review] -ignore = ["PY007", "PP308", "GH102", "MY101"] +ignore = ["PC902", "PP006", "PP304"] From d26d88f02f139919f5591e16a5b60e8e7eee1e1b Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Fri, 27 Feb 2026 13:13:44 +0000 Subject: [PATCH 1141/1195] Switch from using nest-asyncio to nest-asyncio2 (#1499) --- ipykernel/eventloops.py | 4 ++-- pyproject.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index d79f7ee60..a767eb1a9 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -301,9 +301,9 @@ def _schedule_exit(delay): else: import asyncio - import nest_asyncio + import nest_asyncio2 - nest_asyncio.apply() + nest_asyncio2.apply() # Tk uses milliseconds poll_interval = int(1000 * kernel._poll_interval) diff --git a/pyproject.toml b/pyproject.toml index 12f1c9e3c..7552b738e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,7 +26,7 @@ dependencies = [ "jupyter_client>=8.8.0", "jupyter_core>=5.1,!=6.0.*", # For tk event loop support only. - "nest_asyncio>=1.4", + "nest_asyncio2>=1.7.0", "tornado>=6.4.1", "matplotlib-inline>=0.1", 'appnope>=0.1.2;platform_system=="Darwin"', From f1241ad7f875675e41626a2ff6820a7cfbf79b87 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 2 Mar 2026 10:11:23 +0100 Subject: [PATCH 1142/1195] Removed pinning of virtualenv --- .github/workflows/ci.yml | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c46aab31d..99ec2dd9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -61,7 +61,7 @@ jobs: - name: Install hatch run: | python --version - python -m pip install hatch "virtualenv<21" + python -m pip install hatch - name: Run the tests timeout-minutes: 15 @@ -105,11 +105,6 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Preinstall hatch and virtualenv - run: | - python --version - python -m pip install hatch "virtualenv<21" - - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Run Linters run: | @@ -123,11 +118,6 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Preinstall hatch and virtualenv - run: | - python --version - python -m pip install hatch "virtualenv<21" - - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 @@ -139,11 +129,6 @@ jobs: steps: - uses: actions/checkout@v6 - - name: Preinstall hatch and virtualenv - run: | - python --version - python -m pip install hatch "virtualenv<21" - - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Build API docs @@ -168,11 +153,6 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Preinstall hatch and virtualenv - run: | - python --version - python -m pip install hatch "virtualenv<21" - - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -200,11 +180,6 @@ jobs: with: python-version: "3.10" - - name: Preinstall hatch and virtualenv - run: | - python --version - python -m pip install hatch "virtualenv<21" - - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: @@ -227,11 +202,6 @@ jobs: - name: Checkout uses: actions/checkout@v6 - - name: Preinstall hatch and virtualenv - run: | - python --version - python -m pip install hatch "virtualenv<21" - - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: From 852ef9acb2f76dd7270361144785344c2ed2fef2 Mon Sep 17 00:00:00 2001 From: adityawasudeo <37742279+adityawasudeo@users.noreply.github.com> Date: Mon, 2 Mar 2026 01:34:26 -0800 Subject: [PATCH 1143/1195] Fixed a flaky windows test (#1500) --- tests/test_kernel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 7fc4d44d2..5fc828435 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -203,7 +203,7 @@ def parent_target(): received = 0 while received < iterations: - msg = kc.get_iopub_msg(timeout=interval * 2) + msg = kc.get_iopub_msg(timeout=interval * 10) if msg["msg_type"] != "stream": continue content = msg["content"] @@ -235,7 +235,7 @@ async def async_task(): received = 0 while received < iterations: - msg = kc.get_iopub_msg(timeout=interval * 2) + msg = kc.get_iopub_msg(timeout=interval * 10) if msg["msg_type"] != "stream": continue content = msg["content"] From f25a103c2b1970825f2505159fd911edadda9538 Mon Sep 17 00:00:00 2001 From: adityawasudeo <37742279+adityawasudeo@users.noreply.github.com> Date: Mon, 2 Mar 2026 09:14:52 -0800 Subject: [PATCH 1144/1195] Support new iPython system_raise_on_error flag in ZMQInteractiveShell (#1496) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- ipykernel/zmqshell.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 9e9ded064..bd3f8ef41 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -21,6 +21,7 @@ import typing import warnings from pathlib import Path +from subprocess import CalledProcessError from IPython.core import page from IPython.core.autocall import ZMQExitAutocall @@ -783,9 +784,14 @@ def system_piped(self, cmd): with AvoidUNCPath() as path: if path is not None: cmd = f"pushd {path} &&{cmd}" - self.user_ns["_exit_code"] = system(cmd) + exit_code = system(cmd) + self.user_ns["_exit_code"] = exit_code else: - self.user_ns["_exit_code"] = system(self.var_expand(cmd, depth=1)) + exit_code = system(self.var_expand(cmd, depth=1)) + self.user_ns["_exit_code"] = exit_code + + if getattr(self, "system_raise_on_error", False) and exit_code != 0: + raise CalledProcessError(exit_code, cmd) # Ensure new system_piped implementation is used system = system_piped From 770afbeb76a5dd4e752cf9547deeaa0651c40f63 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Mar 2026 07:32:14 +0000 Subject: [PATCH 1145/1195] chore: update pre-commit hooks (#1502) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ac395bdf4..41f3fbe6d 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.36.2 + rev: 0.37.0 hooks: - id: check-github-workflows @@ -73,7 +73,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.2 + rev: v0.15.4 hooks: - id: ruff-check types_or: [python, jupyter] @@ -82,7 +82,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2025.11.21" + rev: "2026.03.02" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] From b2872558fa39093a38f1877c344a24f9f8ad2571 Mon Sep 17 00:00:00 2001 From: Eric Rawn Date: Tue, 10 Mar 2026 07:16:12 -0700 Subject: [PATCH 1146/1195] pass cell_meta from do_execute to run_cell (#1475) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ian Thomas --- ipykernel/ipkernel.py | 47 ++++++++++++++-------------------- ipykernel/kernelbase.py | 2 +- tests/inprocess/test_kernel.py | 6 +++++ 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index aa22bbb0f..eba728335 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -392,7 +392,7 @@ async def do_execute( if hasattr(shell, "run_cell_async") and hasattr(shell, "should_run_async"): run_cell = shell.run_cell_async should_run_async = shell.should_run_async - accepts_params = _accepts_parameters(run_cell, ["cell_id"]) + accepts_params = _accepts_parameters(run_cell, ["cell_id", "cell_meta"]) else: should_run_async = lambda cell: False # noqa: ARG005, E731 # older IPython, @@ -401,7 +401,7 @@ async def do_execute( async def run_cell(*args, **kwargs): return shell.run_cell(*args, **kwargs) - accepts_params = _accepts_parameters(shell.run_cell, ["cell_id"]) + accepts_params = _accepts_parameters(shell.run_cell, ["cell_id", "cell_meta"]) try: # default case: runner is asyncio and asyncio is already running # TODO: this should check every case for "are we inside the runner", @@ -413,6 +413,12 @@ async def run_cell(*args, **kwargs): transformed_cell = code preprocessing_exc_tuple = sys.exc_info() + do_execute_args = {} + if accepts_params["cell_meta"]: + do_execute_args["cell_meta"] = cell_meta + if accepts_params["cell_id"]: + do_execute_args["cell_id"] = cell_id + if ( _asyncio_runner # type:ignore[truthy-bool] and shell.loop_runner is _asyncio_runner @@ -423,23 +429,14 @@ async def run_cell(*args, **kwargs): preprocessing_exc_tuple=preprocessing_exc_tuple, ) ): - if accepts_params["cell_id"]: - coro = run_cell( - code, - store_history=store_history, - silent=silent, - transformed_cell=transformed_cell, - preprocessing_exc_tuple=preprocessing_exc_tuple, - cell_id=cell_id, - ) - else: - coro = run_cell( - code, - store_history=store_history, - silent=silent, - transformed_cell=transformed_cell, - preprocessing_exc_tuple=preprocessing_exc_tuple, - ) + coro = run_cell( + code, + store_history=store_history, + silent=silent, + transformed_cell=transformed_cell, + preprocessing_exc_tuple=preprocessing_exc_tuple, + **do_execute_args, + ) coro_future = asyncio.ensure_future(coro) @@ -460,15 +457,9 @@ async def run_cell(*args, **kwargs): # runner isn't already running, # make synchronous call, # letting shell dispatch to loop runners - if accepts_params["cell_id"]: - res = shell.run_cell( - code, - store_history=store_history, - silent=silent, - cell_id=cell_id, - ) - else: - res = shell.run_cell(code, store_history=store_history, silent=silent) + res = shell.run_cell( + code, store_history=store_history, silent=silent, **do_execute_args + ) finally: self._restore_input() diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 7fa1fb9dc..27bd371c9 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -794,7 +794,7 @@ async def execute_request(self, stream, ident, parent): store_history = content.get("store_history", not silent) user_expressions = content.get("user_expressions", {}) allow_stdin = content.get("allow_stdin", False) - cell_meta = parent.get("metadata", {}) + cell_meta = parent.get("metadata", None) cell_id = cell_meta.get("cellId") except Exception: self.log.error("Got bad msg: ") diff --git a/tests/inprocess/test_kernel.py b/tests/inprocess/test_kernel.py index b965d9b89..ad663313a 100644 --- a/tests/inprocess/test_kernel.py +++ b/tests/inprocess/test_kernel.py @@ -119,3 +119,9 @@ async def test_do_execute(kc): kernel = InProcessKernel() await kernel.do_execute("a=1", True) assert kernel.shell.user_ns["a"] == 1 + + +async def test_cell_meta_do_execute(): + kernel: InProcessKernel = InProcessKernel() + await kernel.do_execute("a=1", True, cell_meta={"testkey": "testvalue"}) + assert kernel.shell.user_ns["a"] == 1 From 5acbca59769a4ac268df0cd8f851bee0ea3486b0 Mon Sep 17 00:00:00 2001 From: Ian Thomas Date: Tue, 10 Mar 2026 14:56:41 +0000 Subject: [PATCH 1147/1195] Linting fixes (#1509) --- ipykernel/zmqshell.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index bd3f8ef41..0833432bf 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -369,7 +369,7 @@ def clear(self, arg_s): if os.name == "nt": # This is the usual name in windows - cls = line_magic("cls")(clear) + cls = line_magic("cls")(clear) # type: ignore[arg-type,var-annotated] # Terminal pagers won't work over pexpect, but we do have our own pager @@ -390,7 +390,7 @@ def less(self, arg_s): cont = fid.read() page.page(cont) - more = line_magic("more")(less) + more = line_magic("more")(less) # type: ignore[arg-type,var-annotated] # Man calls a pager, so we also need to redefine it if os.name == "posix": From 0de32e5801a2ca94c6234ccccd948152e689309e Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 6 May 2026 11:37:08 +0100 Subject: [PATCH 1148/1195] Add test for curve encryption --- tests/test_curve.py | 179 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 tests/test_curve.py diff --git a/tests/test_curve.py b/tests/test_curve.py new file mode 100644 index 000000000..1431c1830 --- /dev/null +++ b/tests/test_curve.py @@ -0,0 +1,179 @@ +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import json +import os +import time + +import pytest +import zmq + +from ipykernel.kernelapp import IPKernelApp + + +@pytest.fixture +def temp_folder_path(tmp_path): + return str(tmp_path) + + +@pytest.fixture +def curve_disabled_kernel_app(temp_folder_path): + app, connection_file_path = _make_app(temp_folder_path, enable_curve=False) + try: + yield app, connection_file_path + finally: + app.close() + + +@pytest.fixture +def curve_enabled_kernel_app(temp_folder_path): + app, connection_file_path = _make_app(temp_folder_path, enable_curve=True) + try: + yield app, connection_file_path + finally: + app.close() + + +def test_curve_disabled_by_default(): + """CurveZMQ must be off by default so existing kernels are unaffected.""" + app = IPKernelApp() + assert app.enable_curve is False + + +def test_connection_file_no_curve_keys_by_default(curve_disabled_kernel_app): + """Connection file must not contain curve keys when Curve is disabled.""" + app, connection_file_path = curve_disabled_kernel_app + app.init_sockets() + app.init_heartbeat() + app.write_connection_file() + with open(connection_file_path) as f: + info = json.load(f) + assert "curve_publickey" not in info + assert "curve_secretkey" not in info + + +def test_curve_connection_file_has_keys(curve_enabled_kernel_app): + """When Curve is enabled the connection file must carry both keys.""" + app, connection_file_path = curve_enabled_kernel_app + app.init_sockets() + app.init_heartbeat() + app.write_connection_file() + with open(connection_file_path) as f: + info = json.load(f) + assert "curve_publickey" in info, "curve_publickey missing from connection file" + assert "curve_secretkey" in info, "curve_secretkey missing from connection file" + # Keys are Z85-encoded ASCII strings - always exactly 40 characters. + assert len(info["curve_publickey"]) == 40 + assert len(info["curve_secretkey"]) == 40 + # Existing fields must still be present (backward-compat check). + assert "key" in info + assert "shell_port" in info + + +def test_curve_keys_are_stable_per_startup(curve_enabled_kernel_app): + """Keys generated at startup stay the same throughout the process lifetime.""" + app, connection_file_path = curve_enabled_kernel_app + app.init_sockets() + pub1 = app._curve_publickey + # Writing the file twice should not regenerate keys. + app.init_heartbeat() + app.write_connection_file() + assert app._curve_publickey == pub1 + + +def test_curve_socket_server_options(curve_enabled_kernel_app): + """Bound sockets must have CURVE_SERVER=True when Curve is enabled.""" + app, connection_file_path = curve_enabled_kernel_app + app.init_sockets() + # shell and stdin are ROUTER sockets configured directly. + assert app.shell_socket.curve_server, "shell_socket missing curve_server" + assert app.stdin_socket.curve_server, "stdin_socket missing curve_server" + assert app.control_socket.curve_server, "control_socket missing curve_server" + # Key material is write-only in pyzmq; we verify it was applied + # through the curve_server flag and the reject test below. + + +def test_no_curve_socket_options_when_disabled(curve_disabled_kernel_app): + """No CURVE options are set when Curve is disabled (default).""" + app, connection_file_path = curve_disabled_kernel_app + app.init_sockets() + # curve_server defaults to 0/False; key options are write-only. + assert not app.shell_socket.curve_server + + +def test_curve_unauthenticated_socket_messages_dropped(curve_enabled_kernel_app): + """With CurveZMQ, frames from a socket without the server key are dropped. + + This is the core security property: a raw DEALER socket that connects to + a CURVE_SERVER-enabled ROUTER cannot deliver messages to it. Compare + with test_transport_security.py in jupyter-client which shows the *absence* + of this property today. + """ + app, connection_file_path = curve_enabled_kernel_app + app.init_sockets() + + # Build the endpoint URL from the bound port. + endpoint = f"tcp://{app.ip}:{app.shell_port}" + + ctx = zmq.Context() + unauth = ctx.socket(zmq.DEALER) + try: + unauth.connect(endpoint) + # ZMQ delivers the connect synchronously, but the curve + # handshake silently drops the message. + unauth.send(b"probe", flags=zmq.NOBLOCK) + + poller = zmq.Poller() + poller.register(app.shell_socket, zmq.POLLIN) + events = dict(poller.poll(timeout=300)) + assert app.shell_socket not in events, ( + "Unauthenticated message reached the kernel socket - " + "CurveZMQ should have dropped it" + ) + finally: + unauth.close(linger=0) + ctx.term() + + +def test_curve_authenticated_socket_can_communicate(curve_enabled_kernel_app): + """With CurveZMQ, a correctly-keyed client socket can reach the kernel.""" + app, connection_file_path = curve_enabled_kernel_app + app.init_sockets() + + endpoint = f"tcp://{app.ip}:{app.shell_port}" + server_public = app._curve_publickey + + ctx = zmq.Context() + auth_client = ctx.socket(zmq.DEALER) + # Client uses the server's public key as CURVE_SERVERKEY; its own + # keypair is used only for encryption, not for access control. + client_pub, client_sec = zmq.curve_keypair() + auth_client.curve_secretkey = client_sec + auth_client.curve_publickey = client_pub + auth_client.curve_serverkey = server_public + try: + auth_client.connect(endpoint) + # Allow the handshake to complete. + time.sleep(0.05) + auth_client.send(b"probe", flags=zmq.NOBLOCK) + + poller = zmq.Poller() + poller.register(app.shell_socket, zmq.POLLIN) + events = dict(poller.poll(timeout=1000)) + assert app.shell_socket in events, ( + "Authenticated client message was not received by kernel socket" + ) + finally: + auth_client.close(linger=0) + ctx.term() + + +def _make_app(temp_folder_path, **kwargs): + """Return a minimal IPKernelApp rooted in temporary directory *temp_folder_path*.""" + connection_file_path = os.path.join(temp_folder_path, "kernel.json") + app = IPKernelApp(connection_file=connection_file_path, **kwargs) + # Replicate the subset of initialize() that sets up connection info + # without importing IPython shell machinery. + super(IPKernelApp, app).initialize(argv=[""]) + app.init_connection_file() + return app, connection_file_path From 66b706cc24e1b1a9b2bca49ce2eae3cc2dd978c9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 10:39:55 +0000 Subject: [PATCH 1149/1195] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_curve.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/test_curve.py b/tests/test_curve.py index 1431c1830..d1fab8ca7 100644 --- a/tests/test_curve.py +++ b/tests/test_curve.py @@ -127,8 +127,7 @@ def test_curve_unauthenticated_socket_messages_dropped(curve_enabled_kernel_app) poller.register(app.shell_socket, zmq.POLLIN) events = dict(poller.poll(timeout=300)) assert app.shell_socket not in events, ( - "Unauthenticated message reached the kernel socket - " - "CurveZMQ should have dropped it" + "Unauthenticated message reached the kernel socket - CurveZMQ should have dropped it" ) finally: unauth.close(linger=0) From a53cd8f231d7ccc71824a9929719bdda8ed1be20 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 6 May 2026 11:44:49 +0100 Subject: [PATCH 1150/1195] Fix lint in tests --- tests/test_curve.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_curve.py b/tests/test_curve.py index d1fab8ca7..ea8c25643 100644 --- a/tests/test_curve.py +++ b/tests/test_curve.py @@ -72,7 +72,7 @@ def test_curve_connection_file_has_keys(curve_enabled_kernel_app): def test_curve_keys_are_stable_per_startup(curve_enabled_kernel_app): """Keys generated at startup stay the same throughout the process lifetime.""" - app, connection_file_path = curve_enabled_kernel_app + app, _connection_file_path = curve_enabled_kernel_app app.init_sockets() pub1 = app._curve_publickey # Writing the file twice should not regenerate keys. @@ -83,7 +83,7 @@ def test_curve_keys_are_stable_per_startup(curve_enabled_kernel_app): def test_curve_socket_server_options(curve_enabled_kernel_app): """Bound sockets must have CURVE_SERVER=True when Curve is enabled.""" - app, connection_file_path = curve_enabled_kernel_app + app, _connection_file_path = curve_enabled_kernel_app app.init_sockets() # shell and stdin are ROUTER sockets configured directly. assert app.shell_socket.curve_server, "shell_socket missing curve_server" @@ -95,7 +95,7 @@ def test_curve_socket_server_options(curve_enabled_kernel_app): def test_no_curve_socket_options_when_disabled(curve_disabled_kernel_app): """No CURVE options are set when Curve is disabled (default).""" - app, connection_file_path = curve_disabled_kernel_app + app, _connection_file_path = curve_disabled_kernel_app app.init_sockets() # curve_server defaults to 0/False; key options are write-only. assert not app.shell_socket.curve_server @@ -109,7 +109,7 @@ def test_curve_unauthenticated_socket_messages_dropped(curve_enabled_kernel_app) with test_transport_security.py in jupyter-client which shows the *absence* of this property today. """ - app, connection_file_path = curve_enabled_kernel_app + app, _connection_file_path = curve_enabled_kernel_app app.init_sockets() # Build the endpoint URL from the bound port. @@ -136,7 +136,7 @@ def test_curve_unauthenticated_socket_messages_dropped(curve_enabled_kernel_app) def test_curve_authenticated_socket_can_communicate(curve_enabled_kernel_app): """With CurveZMQ, a correctly-keyed client socket can reach the kernel.""" - app, connection_file_path = curve_enabled_kernel_app + app, _connection_file_path = curve_enabled_kernel_app app.init_sockets() endpoint = f"tcp://{app.ip}:{app.shell_port}" From 38a596f6a2e6205b97ede607807483c531aaa7f6 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 6 May 2026 11:51:38 +0100 Subject: [PATCH 1151/1195] Draft implementation --- ipykernel/heartbeat.py | 23 +++++++++++++++++++-- ipykernel/kernelapp.py | 45 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 3291e0aa6..6fc28b915 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -27,14 +27,29 @@ class Heartbeat(Thread): """A simple ping-pong style heartbeat that runs in a thread.""" - def __init__(self, context, addr=None): - """Initialize the heartbeat thread.""" + def __init__(self, context, addr=None, curve_publickey=None, curve_secretkey=None): + """Initialize the heartbeat thread. + + Parameters + ---------- + context : zmq.Context + addr : tuple, optional + (transport, ip, port) + curve_publickey : bytes, optional + Z85-encoded CurveZMQ public key. When provided together with + *curve_secretkey*, the heartbeat socket will operate as a + CurveZMQ server so that only authenticated clients can connect. + curve_secretkey : bytes, optional + Z85-encoded CurveZMQ secret key (paired with *curve_publickey*). + """ if addr is None: addr = ("tcp", localhost(), 0) Thread.__init__(self, name="Heartbeat") self.context = context self.transport, self.ip, self.port = addr self.original_port = self.port + self.curve_publickey = curve_publickey + self.curve_secretkey = curve_secretkey if self.original_port == 0: self.pick_port() self.addr = (self.ip, self.port) @@ -94,6 +109,10 @@ def run(self): self.name = "Heartbeat" self.socket = self.context.socket(zmq.ROUTER) self.socket.linger = 1000 + if self.curve_secretkey is not None: + self.socket.curve_secretkey = self.curve_secretkey + self.socket.curve_publickey = self.curve_publickey + self.socket.curve_server = True try: self._bind_socket() except Exception: diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index b2f614ea9..48d4aac48 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -188,6 +188,19 @@ def abs_connection_file(self): """, ).tag(config=True) + enable_curve = Bool( + bool(int(os.environ.get("JUPYTER_ENABLE_CURVE", "0"))), + help="Enable CurveZMQ transport encryption and authentication. " + "When True, a keypair is generated at startup and stored in the " + "connection file so that clients can authenticate and encrypt " + "all ZMQ channels.", + ).tag(config=True) + + # Internal CurveZMQ keypair (Z85-encoded bytes); populated in init_sockets + # when enable_curve is True. + _curve_publickey: bytes | None = None + _curve_secretkey: bytes | None = None + # polling parent_handle = Integer( int(os.environ.get("JPY_PARENT_PID") or 0), @@ -211,6 +224,17 @@ def excepthook(self, etype, evalue, tb): # write uncaught traceback to 'real' stderr, not zmq-forwarder traceback.print_exception(etype, evalue, tb, file=sys.__stderr__) + def _apply_curve_server_options(self, socket: zmq.sugar.socket.Socket) -> None: + """Set CurveZMQ server-side options on *socket* before it is bound. + + This is a no-op when enable_curve is False or keys have not been + generated yet, so it is safe to call unconditionally. + """ + if self.enable_curve and self._curve_secretkey is not None: + socket.curve_secretkey = self._curve_secretkey + socket.curve_publickey = self._curve_publickey + socket.curve_server = True + def init_poller(self): """Initialize the poller.""" if sys.platform == "win32": @@ -274,6 +298,12 @@ def write_connection_file(self, **kwargs: Any) -> None: iopub_port=self.iopub_port, control_port=self.control_port, ) + if self.enable_curve and self._curve_publickey is not None: + # Store Z85-encoded keys as ASCII strings alongside the HMAC key. + # Clients that understand CurveZMQ will use these to configure + # their sockets; legacy clients ignore the unknown fields. + connection_info["curve_publickey"] = self._curve_publickey.decode("ascii") + connection_info["curve_secretkey"] = self._curve_secretkey.decode("ascii") # type: ignore[union-attr] if Path(cf).exists(): # If the file exists, merge our info into it. For example, if the # original file had port number 0, we update with the actual port @@ -328,13 +358,19 @@ def init_sockets(self): self.context = context = zmq.Context() atexit.register(self.close) + if self.enable_curve: + self._curve_publickey, self._curve_secretkey = zmq.curve_keypair() + self.log.debug("CurveZMQ enabled; generated server keypair") + self.shell_socket = context.socket(zmq.ROUTER) self.shell_socket.linger = 1000 + self._apply_curve_server_options(self.shell_socket) self.shell_port = self._bind_socket(self.shell_socket, self.shell_port) self.log.debug("shell ROUTER Channel on port: %i", self.shell_port) self.stdin_socket = context.socket(zmq.ROUTER) self.stdin_socket.linger = 1000 + self._apply_curve_server_options(self.stdin_socket) self.stdin_port = self._bind_socket(self.stdin_socket, self.stdin_port) self.log.debug("stdin ROUTER Channel on port: %i", self.stdin_port) @@ -351,6 +387,7 @@ def init_control(self, context): """Initialize the control channel.""" self.control_socket = context.socket(zmq.ROUTER) self.control_socket.linger = 1000 + self._apply_curve_server_options(self.control_socket) self.control_port = self._bind_socket(self.control_socket, self.control_port) self.log.debug("control ROUTER Channel on port: %i", self.control_port) @@ -379,6 +416,7 @@ def init_iopub(self, context): """Initialize the iopub channel.""" self.iopub_socket = context.socket(zmq.XPUB) self.iopub_socket.linger = 1000 + self._apply_curve_server_options(self.iopub_socket) self.iopub_port = self._bind_socket(self.iopub_socket, self.iopub_port) self.log.debug("iopub PUB Channel on port: %i", self.iopub_port) self.configure_tornado_logger() @@ -392,7 +430,12 @@ def init_heartbeat(self): # heartbeat doesn't share context, because it mustn't be blocked # by the GIL, which is accessed by libzmq when freeing zero-copy messages hb_ctx = zmq.Context() - self.heartbeat = Heartbeat(hb_ctx, (self.transport, self.ip, self.hb_port)) + self.heartbeat = Heartbeat( + hb_ctx, + (self.transport, self.ip, self.hb_port), + curve_publickey=self._curve_publickey if self.enable_curve else None, + curve_secretkey=self._curve_secretkey if self.enable_curve else None, + ) self.hb_port = self.heartbeat.port self.log.debug("Heartbeat REP Channel on port: %i", self.hb_port) self.heartbeat.start() From 6007e9b3c39196319bb6490ad800b667d0c956dd Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 6 May 2026 11:53:04 +0100 Subject: [PATCH 1152/1195] Use a fork with curve support for testing/PoC --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7552b738e..f172b758c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "ipython>=7.23.1", "comm>=0.1.1", "traitlets>=5.4.0", - "jupyter_client>=8.8.0", + "jupyter_client @ git+https://github.com/krassowski/jupyter_client.git@add-curve-encryption", "jupyter_core>=5.1,!=6.0.*", # For tk event loop support only. "nest_asyncio2>=1.7.0", From 2f29d5234913ac3209230ca4029eb6b5275fc575 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 6 May 2026 13:07:40 +0100 Subject: [PATCH 1153/1195] Handoff serialization to jupyter-client --- ipykernel/heartbeat.py | 4 ++-- ipykernel/kernelapp.py | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index 6fc28b915..e2a628012 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -36,11 +36,11 @@ def __init__(self, context, addr=None, curve_publickey=None, curve_secretkey=Non addr : tuple, optional (transport, ip, port) curve_publickey : bytes, optional - Z85-encoded CurveZMQ public key. When provided together with + CurveZMQ public key (Z85). When provided together with *curve_secretkey*, the heartbeat socket will operate as a CurveZMQ server so that only authenticated clients can connect. curve_secretkey : bytes, optional - Z85-encoded CurveZMQ secret key (paired with *curve_publickey*). + CurveZMQ secret key (Z85, paired with *curve_publickey*). """ if addr is None: addr = ("tcp", localhost(), 0) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 48d4aac48..11cee33ab 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -299,11 +299,9 @@ def write_connection_file(self, **kwargs: Any) -> None: control_port=self.control_port, ) if self.enable_curve and self._curve_publickey is not None: - # Store Z85-encoded keys as ASCII strings alongside the HMAC key. - # Clients that understand CurveZMQ will use these to configure - # their sockets; legacy clients ignore the unknown fields. - connection_info["curve_publickey"] = self._curve_publickey.decode("ascii") - connection_info["curve_secretkey"] = self._curve_secretkey.decode("ascii") # type: ignore[union-attr] + # write_connection_file() in jupyter-client handles JSON-safe key serialization + connection_info["curve_publickey"] = self._curve_publickey + connection_info["curve_secretkey"] = self._curve_secretkey if Path(cf).exists(): # If the file exists, merge our info into it. For example, if the # original file had port number 0, we update with the actual port From af731377a2d4948108f0504398b6154556afbf3c Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 6 May 2026 13:20:53 +0100 Subject: [PATCH 1154/1195] Add a warning when curve is disabled and using tcp transport --- ipykernel/kernelapp.py | 8 ++++++++ tests/test_kernelapp.py | 24 ++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 11cee33ab..a9991c083 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -359,6 +359,14 @@ def init_sockets(self): if self.enable_curve: self._curve_publickey, self._curve_secretkey = zmq.curve_keypair() self.log.debug("CurveZMQ enabled; generated server keypair") + elif self.transport == "tcp": + self.log.warning( + "Kernel is running over TCP without encryption." + " All communication (including code and outputs) is sent in plain text" + " and is susceptible to eavesdropping." + " Use IPC transport or set IPKernelApp.enable_curve=True to enable" + " CurveZMQ encryption." + ) self.shell_socket = context.socket(zmq.ROUTER) self.shell_socket.linger = 1000 diff --git a/tests/test_kernelapp.py b/tests/test_kernelapp.py index da38777d0..a5d8d261c 100644 --- a/tests/test_kernelapp.py +++ b/tests/test_kernelapp.py @@ -129,3 +129,27 @@ def test_trio_loop(): app.io_loop.add_callback(app.io_loop.stop) app.kernel.destroy() app.close() + + +def test_init_sockets_curve_enabled_logs_debug(): + app = IPKernelApp(enable_curve=True) + with patch.object(app.log, "debug") as mock_debug: + app.init_sockets() + app.cleanup_connection_file() + app.close() + messages = [str(call) for call in mock_debug.call_args_list] + assert any("CurveZMQ enabled" in m for m in messages), ( + "Expected a debug log mentioning CurveZMQ when enable_curve=True" + ) + + +def test_init_sockets_tcp_without_curve_logs_warning(): + app = IPKernelApp(transport="tcp", enable_curve=False) + with patch.object(app.log, "warning") as mock_warning: + app.init_sockets() + app.cleanup_connection_file() + app.close() + messages = [str(call) for call in mock_warning.call_args_list] + assert any("Kernel is running over TCP without encryption" in m for m in messages), ( + "Expected a warning about missing encryption when transport=tcp and enable_curve=False" + ) From 955e1d43b0cb2713855b25bb71e6ef574f715b94 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 6 May 2026 13:36:49 +0100 Subject: [PATCH 1155/1195] Allow references --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index f172b758c..f9bf9759a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,6 +73,9 @@ cov = [ pyqt5 = ["pyqt5"] pyside6 = ["pyside6"] +[tool.hatch.metadata] +allow-direct-references = true + [tool.hatch.version] path = "ipykernel/_version.py" From c2468751b83671deded1d0f2bfea71331e0e3bc6 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 6 May 2026 14:05:08 +0100 Subject: [PATCH 1156/1195] Fix lint --- ipykernel/kernelapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index a9991c083..2b28485f1 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -224,7 +224,7 @@ def excepthook(self, etype, evalue, tb): # write uncaught traceback to 'real' stderr, not zmq-forwarder traceback.print_exception(etype, evalue, tb, file=sys.__stderr__) - def _apply_curve_server_options(self, socket: zmq.sugar.socket.Socket) -> None: + def _apply_curve_server_options(self, socket: zmq.Socket[t.Any]) -> None: """Set CurveZMQ server-side options on *socket* before it is bound. This is a no-op when enable_curve is False or keys have not been From 2cf991c471701bec032a4f58551c034cbe62dbf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Wed, 6 May 2026 15:21:48 +0100 Subject: [PATCH 1157/1195] Fix downstream test: do not use editable install for `ipyparallel` (#1516) --- .github/workflows/downstream.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 894c8626f..88f4627c3 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -69,7 +69,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/downstream-test@v1 with: package_name: ipyparallel - package_spec: '-e ".[test]"' + package_spec: ".[test]" jupyter_kernel_test: runs-on: ubuntu-latest From 4abdde16e0100cc57e0627f539b2c938dd7ebf6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Thu, 7 May 2026 09:28:02 +0100 Subject: [PATCH 1158/1195] Unpin Spinx now that typehints issue ought to be resolved (#1517) --- docs/api/ipykernel.comm.rst | 6 ++-- docs/api/ipykernel.inprocess.rst | 16 +++++------ docs/api/ipykernel.rst | 48 ++++++++++++++++---------------- docs/conf.py | 6 ++++ pyproject.toml | 4 +-- 5 files changed, 42 insertions(+), 38 deletions(-) diff --git a/docs/api/ipykernel.comm.rst b/docs/api/ipykernel.comm.rst index 1cf9ee4e7..a2d529ed0 100644 --- a/docs/api/ipykernel.comm.rst +++ b/docs/api/ipykernel.comm.rst @@ -7,19 +7,19 @@ Submodules .. automodule:: ipykernel.comm.comm :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.comm.manager :members: - :undoc-members: :show-inheritance: + :undoc-members: Module contents --------------- .. automodule:: ipykernel.comm :members: - :undoc-members: :show-inheritance: + :undoc-members: diff --git a/docs/api/ipykernel.inprocess.rst b/docs/api/ipykernel.inprocess.rst index c2d6536bc..24d62e7ad 100644 --- a/docs/api/ipykernel.inprocess.rst +++ b/docs/api/ipykernel.inprocess.rst @@ -7,49 +7,49 @@ Submodules .. automodule:: ipykernel.inprocess.blocking :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.channels :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.client :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.constants :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.ipkernel :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.manager :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.inprocess.socket :members: - :undoc-members: :show-inheritance: + :undoc-members: Module contents --------------- .. automodule:: ipykernel.inprocess :members: - :undoc-members: :show-inheritance: + :undoc-members: diff --git a/docs/api/ipykernel.rst b/docs/api/ipykernel.rst index 0f023070d..85d1f2db3 100644 --- a/docs/api/ipykernel.rst +++ b/docs/api/ipykernel.rst @@ -16,145 +16,145 @@ Submodules .. automodule:: ipykernel.compiler :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.connect :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.control :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.debugger :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.displayhook :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.embed :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.eventloops :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.heartbeat :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.iostream :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.ipkernel :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.jsonutil :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.kernelapp :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.kernelbase :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.kernelspec :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.log :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.parentpoller :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.shellchannel :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.socket_pair :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.subshell :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.subshell_manager :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.thread :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.trio_runner :members: - :undoc-members: :show-inheritance: + :undoc-members: .. automodule:: ipykernel.zmqshell :members: - :undoc-members: :show-inheritance: + :undoc-members: Module contents --------------- .. automodule:: ipykernel :members: - :undoc-members: :show-inheritance: + :undoc-members: diff --git a/docs/conf.py b/docs/conf.py index 5a5f8ed5c..7ab188f4b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -124,6 +124,12 @@ # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False +suppress_warnings = [ + # Remove both once fix for https://github.com/ipython/ipython/issues/15202 is released + "sphinx_autodoc_typehints.forward_reference", + "sphinx_autodoc_typehints.guarded_import", +] + # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False diff --git a/pyproject.toml b/pyproject.toml index 7552b738e..252a684ea 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,9 +43,7 @@ Tracker = "https://github.com/ipython/ipykernel/issues" [project.optional-dependencies] docs = [ - # Sphinx pinned until `sphinx-autodoc-typehints` issue is resolved: - # https://github.com/tox-dev/sphinx-autodoc-typehints/issues/523 - "sphinx<8.2.0", + "sphinx", "myst_parser", "pydata_sphinx_theme", "sphinxcontrib_github_alt", From 2818b6625ac4af590af165ef7d7bf63b1f242d26 Mon Sep 17 00:00:00 2001 From: Phil Elson Date: Thu, 7 May 2026 10:55:42 +0200 Subject: [PATCH 1159/1195] Bugfix: Ensure that we take a copy of the sys.modules before iterating over it (#1514) --- ipykernel/debugger.py | 2 +- ipykernel/ipkernel.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index 24fe92849..b7dd4a259 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -693,7 +693,7 @@ async def copyToGlobals(self, message): async def modules(self, message): """Handle a modules message.""" - modules = list(sys.modules.values()) + modules = list(sys.modules.copy().values()) startModule = message.get("startModule", 0) moduleCount = message.get("moduleCount", len(modules)) mods = [] diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index eba728335..95af43e5e 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -120,7 +120,7 @@ def __init__(self, **kwargs): from .debugger import _is_debugpy_available self._kernel_modules = [ - m.__file__ for m in sys.modules.values() if hasattr(m, "__file__") and m.__file__ + m.__file__ for m in sys.modules.copy().values() if hasattr(m, "__file__") and m.__file__ ] # Initialize the Debugger From 8a2a911b747e14748bb5d22b6198764c14b1bd35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Thu, 7 May 2026 11:45:01 +0100 Subject: [PATCH 1160/1195] Require curve keys to be passed as kwargs Co-authored-by: M Bussonnier --- ipykernel/heartbeat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index e2a628012..ef2af8e04 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -27,7 +27,7 @@ class Heartbeat(Thread): """A simple ping-pong style heartbeat that runs in a thread.""" - def __init__(self, context, addr=None, curve_publickey=None, curve_secretkey=None): + def __init__(self, context, addr=None, *, curve_publickey=None, curve_secretkey=None): """Initialize the heartbeat thread. Parameters From 0eec39da69c9530ee557c57bc06cf0b1efd4c288 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Thu, 7 May 2026 11:56:50 +0100 Subject: [PATCH 1161/1195] Make curve keys private and typed --- ipykernel/heartbeat.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ipykernel/heartbeat.py b/ipykernel/heartbeat.py index ef2af8e04..b74f22f8c 100644 --- a/ipykernel/heartbeat.py +++ b/ipykernel/heartbeat.py @@ -48,8 +48,8 @@ def __init__(self, context, addr=None, *, curve_publickey=None, curve_secretkey= self.context = context self.transport, self.ip, self.port = addr self.original_port = self.port - self.curve_publickey = curve_publickey - self.curve_secretkey = curve_secretkey + self._curve_publickey = curve_publickey + self._curve_secretkey = curve_secretkey if self.original_port == 0: self.pick_port() self.addr = (self.ip, self.port) @@ -109,9 +109,9 @@ def run(self): self.name = "Heartbeat" self.socket = self.context.socket(zmq.ROUTER) self.socket.linger = 1000 - if self.curve_secretkey is not None: - self.socket.curve_secretkey = self.curve_secretkey - self.socket.curve_publickey = self.curve_publickey + if self._curve_secretkey is not None: + self.socket.curve_secretkey = self._curve_secretkey + self.socket.curve_publickey = self._curve_publickey self.socket.curve_server = True try: self._bind_socket() @@ -141,3 +141,6 @@ def run(self): raise else: break + + _curve_publickey: bytes | None + _curve_secretkey: bytes | None From 0c71c214c6a7f5d236ee500693ed6a974360a325 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Thu, 7 May 2026 11:59:55 +0100 Subject: [PATCH 1162/1195] Simplify test fixtures --- tests/test_curve.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/tests/test_curve.py b/tests/test_curve.py index ea8c25643..0ee20df2a 100644 --- a/tests/test_curve.py +++ b/tests/test_curve.py @@ -2,7 +2,6 @@ # Distributed under the terms of the Modified BSD License. import json -import os import time import pytest @@ -12,13 +11,8 @@ @pytest.fixture -def temp_folder_path(tmp_path): - return str(tmp_path) - - -@pytest.fixture -def curve_disabled_kernel_app(temp_folder_path): - app, connection_file_path = _make_app(temp_folder_path, enable_curve=False) +def curve_disabled_kernel_app(tmp_path): + app, connection_file_path = _make_app(tmp_path, enable_curve=False) try: yield app, connection_file_path finally: @@ -26,8 +20,8 @@ def curve_disabled_kernel_app(temp_folder_path): @pytest.fixture -def curve_enabled_kernel_app(temp_folder_path): - app, connection_file_path = _make_app(temp_folder_path, enable_curve=True) +def curve_enabled_kernel_app(tmp_path): + app, connection_file_path = _make_app(tmp_path, enable_curve=True) try: yield app, connection_file_path finally: @@ -167,9 +161,9 @@ def test_curve_authenticated_socket_can_communicate(curve_enabled_kernel_app): ctx.term() -def _make_app(temp_folder_path, **kwargs): - """Return a minimal IPKernelApp rooted in temporary directory *temp_folder_path*.""" - connection_file_path = os.path.join(temp_folder_path, "kernel.json") +def _make_app(tmp_path, **kwargs): + """Return a minimal IPKernelApp rooted in temporary directory *tmp_path*.""" + connection_file_path = str(tmp_path / "kernel.json") app = IPKernelApp(connection_file=connection_file_path, **kwargs) # Replicate the subset of initialize() that sets up connection info # without importing IPython shell machinery. From fcd5a3e55f4bd921a6df83a5c827830411070146 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Fri, 8 May 2026 10:44:40 +0200 Subject: [PATCH 1163/1195] Fix ENOTSOCK traceback when iopub socket closes during shutdown (#1518) --- ipykernel/iostream.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 33213d167..2c1094f35 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -7,6 +7,7 @@ import atexit import contextvars import io +import logging import os import sys import threading @@ -33,6 +34,8 @@ PIPE_BUFFER_SIZE = 1000 +logger = logging.getLogger(__name__) + # ----------------------------------------------------------------------------- # IO classes # ----------------------------------------------------------------------------- @@ -355,8 +358,20 @@ def _really_send(self, msg, *args, **kwargs): mp_mode = self._check_mp_mode() if mp_mode != CHILD: - # we are master, do a regular send - self.socket.send_multipart(msg, *args, **kwargs) + # we are master, do a regular send. + # The closed check above is racy: once the IO thread has been + # joined, _really_send runs on the caller's thread while close() + # may run concurrently on another, so the socket can be closed + # (or become None) between the check and the send. Swallow that + # specific case rather than logging a noisy traceback during + # shutdown. + try: + self.socket.send_multipart(msg, *args, **kwargs) + except (AttributeError, zmq.error.ZMQError) as e: + if isinstance(e, AttributeError) or e.errno == zmq.ENOTSOCK: + logger.debug("IOPub socket closed during send (likely shutdown): %s", e) + return + raise else: # we are a child, pipe to master # new context/socket for every pipe-out From 9daafd844cecb624c0e288cc3d8410ee5f057b0b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 Apr 2026 23:48:48 +0000 Subject: [PATCH 1164/1195] Bump the actions group across 1 directory with 2 updates Bumps the actions group with 2 updates in the / directory: [scientific-python/upload-nightly-action](https://github.com/scientific-python/upload-nightly-action) and [actions/create-github-app-token](https://github.com/actions/create-github-app-token). Updates `scientific-python/upload-nightly-action` from 0.6.3 to 0.6.4 - [Release notes](https://github.com/scientific-python/upload-nightly-action/releases) - [Commits](https://github.com/scientific-python/upload-nightly-action/compare/5748273c71e2d8d3a61f3a11a16421c8954f9ecf...e76cfec8a4611fd02808a801b0ff5a7d7c1b2d99) Updates `actions/create-github-app-token` from 2 to 3 - [Release notes](https://github.com/actions/create-github-app-token/releases) - [Commits](https://github.com/actions/create-github-app-token/compare/v2...v3) --- updated-dependencies: - dependency-name: scientific-python/upload-nightly-action dependency-version: 0.6.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions - dependency-name: actions/create-github-app-token dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] --- .github/workflows/nightly.yml | 2 +- .github/workflows/publish-changelog.yml | 2 +- .github/workflows/publish-release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ba566e1af..deb9a54d5 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -27,7 +27,7 @@ jobs: python -m pip install build python -m build - name: Upload wheel - uses: scientific-python/upload-nightly-action@5748273c71e2d8d3a61f3a11a16421c8954f9ecf # 0.6.3 + uses: scientific-python/upload-nightly-action@e76cfec8a4611fd02808a801b0ff5a7d7c1b2d99 # 0.6.4 with: artifacts_path: dist anaconda_nightly_upload_token: ${{secrets.UPLOAD_TOKEN}} diff --git a/.github/workflows/publish-changelog.yml b/.github/workflows/publish-changelog.yml index c576a5487..7d3d4b909 100644 --- a/.github/workflows/publish-changelog.yml +++ b/.github/workflows/publish-changelog.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - uses: actions/create-github-app-token@v2 + - uses: actions/create-github-app-token@v3 id: app-token with: app-id: ${{ vars.APP_ID }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index f6743a9bf..6df5d8319 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - uses: actions/create-github-app-token@v2 + - uses: actions/create-github-app-token@v3 id: app-token with: app-id: ${{ vars.APP_ID }} From 33a980af5328ba96c530399a00a12164b699cb04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Fri, 8 May 2026 11:04:28 +0100 Subject: [PATCH 1165/1195] Mark cell execution as failing when formatter errors out (#1521) --- ipykernel/ipkernel.py | 4 +++- ipykernel/zmqshell.py | 5 +++++ tests/test_kernel.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 95af43e5e..215129063 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -465,7 +465,9 @@ async def run_cell(*args, **kwargs): err = res.error_before_exec if res.error_before_exec is not None else res.error_in_exec - if res.success: + displayhook_error = getattr(shell, "_last_traceback_during_displayhook", False) + + if res.success and not displayhook_error: reply_content["status"] = "ok" else: reply_content["status"] = "error" diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 0833432bf..a92bf7e49 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -661,6 +661,7 @@ def ask_exit(self): def run_cell(self, *args, **kwargs): """Run a cell.""" self._last_traceback = None + self._last_traceback_during_displayhook = False return super().run_cell(*args, **kwargs) def _showtraceback(self, etype, evalue, stb): @@ -675,6 +676,10 @@ def _showtraceback(self, etype, evalue, stb): } dh = self.displayhook + if getattr(dh, "msg", None) is not None: + # Errors raised while formatting display output should mark the + # current execute_request as failed. + self._last_traceback_during_displayhook = True # Send exception info over pub socket for other clients than the caller # to pick up topic = None diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 5fc828435..db5debb28 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -58,6 +58,42 @@ def test_simple_print(): _check_master(kc, expected=True) +@pytest.mark.parametrize( + ("code", "expect_error_status"), + [ + ("1/0", True), + ( + """ + class Test: + def __repr__(self): + 1 / 0 + + Test() + """, + True, + ), + ( + """ + ip = get_ipython() + try: + 1 / 0 + except: + ip.showtraceback() + """, + False, + ), + ], + ids=["runtime-error", "display-formatting-error", "explicit-showtraceback-ok"], +) +def test_execute_reply_error_status(code, expect_error_status): + with kernel() as kc: + msg_id, reply = execute(kc=kc, code=code) + assemble_output(kc.get_iopub_msg, parent_msg_id=msg_id, raise_error=False) + + has_error_status = reply["status"] == "error" + assert has_error_status is expect_error_status, reply + + def collect_outputs(get_iopub_msg, parent_msg_id, timeout=5): """Collect outputs until we get an idle message From 8d56ecc4d31ef6f1bec9bf746d0f716c796ce8e8 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Fri, 8 May 2026 23:23:49 +0100 Subject: [PATCH 1166/1195] Make the client own key generation --- ipykernel/kernelapp.py | 43 ++++++++++++++--------------------------- tests/test_curve.py | 41 ++++++++++++++++++++++++++++----------- tests/test_kernelapp.py | 20 +++++++++++++------ 3 files changed, 58 insertions(+), 46 deletions(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 2b28485f1..483d0fa88 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -188,19 +188,6 @@ def abs_connection_file(self): """, ).tag(config=True) - enable_curve = Bool( - bool(int(os.environ.get("JUPYTER_ENABLE_CURVE", "0"))), - help="Enable CurveZMQ transport encryption and authentication. " - "When True, a keypair is generated at startup and stored in the " - "connection file so that clients can authenticate and encrypt " - "all ZMQ channels.", - ).tag(config=True) - - # Internal CurveZMQ keypair (Z85-encoded bytes); populated in init_sockets - # when enable_curve is True. - _curve_publickey: bytes | None = None - _curve_secretkey: bytes | None = None - # polling parent_handle = Integer( int(os.environ.get("JPY_PARENT_PID") or 0), @@ -227,12 +214,12 @@ def excepthook(self, etype, evalue, tb): def _apply_curve_server_options(self, socket: zmq.Socket[t.Any]) -> None: """Set CurveZMQ server-side options on *socket* before it is bound. - This is a no-op when enable_curve is False or keys have not been - generated yet, so it is safe to call unconditionally. + This is a no-op when Curve keys are not available yet, so it is safe + to call unconditionally. """ - if self.enable_curve and self._curve_secretkey is not None: - socket.curve_secretkey = self._curve_secretkey - socket.curve_publickey = self._curve_publickey + if self.curve_secretkey is not None: + socket.curve_secretkey = self.curve_secretkey + socket.curve_publickey = self.curve_publickey socket.curve_server = True def init_poller(self): @@ -298,10 +285,9 @@ def write_connection_file(self, **kwargs: Any) -> None: iopub_port=self.iopub_port, control_port=self.control_port, ) - if self.enable_curve and self._curve_publickey is not None: - # write_connection_file() in jupyter-client handles JSON-safe key serialization - connection_info["curve_publickey"] = self._curve_publickey - connection_info["curve_secretkey"] = self._curve_secretkey + if self.curve_publickey is not None: + connection_info["curve_publickey"] = self.curve_publickey + connection_info["curve_secretkey"] = self.curve_secretkey if Path(cf).exists(): # If the file exists, merge our info into it. For example, if the # original file had port number 0, we update with the actual port @@ -356,16 +342,15 @@ def init_sockets(self): self.context = context = zmq.Context() atexit.register(self.close) - if self.enable_curve: - self._curve_publickey, self._curve_secretkey = zmq.curve_keypair() - self.log.debug("CurveZMQ enabled; generated server keypair") + if self.curve_secretkey is not None: + self.log.debug("Detected CurveZMQ secret key; using transport encryption") elif self.transport == "tcp": self.log.warning( "Kernel is running over TCP without encryption." " All communication (including code and outputs) is sent in plain text" " and is susceptible to eavesdropping." - " Use IPC transport or set IPKernelApp.enable_curve=True to enable" - " CurveZMQ encryption." + " Use IPC transport or launch with kernel manager-provisioned" + " CurveZMQ keys to enable transport encryption." ) self.shell_socket = context.socket(zmq.ROUTER) @@ -439,8 +424,8 @@ def init_heartbeat(self): self.heartbeat = Heartbeat( hb_ctx, (self.transport, self.ip, self.hb_port), - curve_publickey=self._curve_publickey if self.enable_curve else None, - curve_secretkey=self._curve_secretkey if self.enable_curve else None, + curve_publickey=self.curve_publickey, + curve_secretkey=self.curve_secretkey, ) self.hb_port = self.heartbeat.port self.log.debug("Heartbeat REP Channel on port: %i", self.hb_port) diff --git a/tests/test_curve.py b/tests/test_curve.py index 0ee20df2a..72dc1b26e 100644 --- a/tests/test_curve.py +++ b/tests/test_curve.py @@ -6,6 +6,7 @@ import pytest import zmq +from jupyter_client import KernelManager from ipykernel.kernelapp import IPKernelApp @@ -28,12 +29,6 @@ def curve_enabled_kernel_app(tmp_path): app.close() -def test_curve_disabled_by_default(): - """CurveZMQ must be off by default so existing kernels are unaffected.""" - app = IPKernelApp() - assert app.enable_curve is False - - def test_connection_file_no_curve_keys_by_default(curve_disabled_kernel_app): """Connection file must not contain curve keys when Curve is disabled.""" app, connection_file_path = curve_disabled_kernel_app @@ -65,14 +60,14 @@ def test_curve_connection_file_has_keys(curve_enabled_kernel_app): def test_curve_keys_are_stable_per_startup(curve_enabled_kernel_app): - """Keys generated at startup stay the same throughout the process lifetime.""" + """Provisioned keys stay unchanged throughout the kernel process lifetime.""" app, _connection_file_path = curve_enabled_kernel_app app.init_sockets() - pub1 = app._curve_publickey + pub1 = app.curve_publickey # Writing the file twice should not regenerate keys. app.init_heartbeat() app.write_connection_file() - assert app._curve_publickey == pub1 + assert app.curve_publickey == pub1 def test_curve_socket_server_options(curve_enabled_kernel_app): @@ -134,7 +129,7 @@ def test_curve_authenticated_socket_can_communicate(curve_enabled_kernel_app): app.init_sockets() endpoint = f"tcp://{app.ip}:{app.shell_port}" - server_public = app._curve_publickey + server_public = app.curve_publickey ctx = zmq.Context() auth_client = ctx.socket(zmq.DEALER) @@ -161,9 +156,33 @@ def test_curve_authenticated_socket_can_communicate(curve_enabled_kernel_app): ctx.term() -def _make_app(tmp_path, **kwargs): +def test_manager_provisioned_curve_keys_are_used(curve_enabled_kernel_app): + """Kernel uses manager-provisioned Curve keys exactly as provided.""" + app, _connection_file_path = curve_enabled_kernel_app + try: + with open(_connection_file_path) as f: + info = json.load(f) + + app.init_sockets() + + assert app.curve_publickey == info["curve_publickey"].encode() + assert app.curve_secretkey == info["curve_secretkey"].encode() + assert app.shell_socket.curve_server + assert app.stdin_socket.curve_server + assert app.control_socket.curve_server + finally: + app.close() + + +def _make_app(tmp_path, *, enable_curve=False, **kwargs): """Return a minimal IPKernelApp rooted in temporary directory *tmp_path*.""" connection_file_path = str(tmp_path / "kernel.json") + if enable_curve: + # Populate the Curve keys into the connection file + km = KernelManager(connection_file=connection_file_path) + km.transport_encryption = True + km.pre_start_kernel() + app = IPKernelApp(connection_file=connection_file_path, **kwargs) # Replicate the subset of initialize() that sets up connection info # without importing IPython shell machinery. diff --git a/tests/test_kernelapp.py b/tests/test_kernelapp.py index a5d8d261c..4e1c5eea2 100644 --- a/tests/test_kernelapp.py +++ b/tests/test_kernelapp.py @@ -5,6 +5,7 @@ from unittest.mock import patch import pytest +from jupyter_client import KernelManager from jupyter_core.paths import secure_write from traitlets.config.loader import Config @@ -131,25 +132,32 @@ def test_trio_loop(): app.close() -def test_init_sockets_curve_enabled_logs_debug(): - app = IPKernelApp(enable_curve=True) +def test_init_sockets_curve_enabled_logs_debug(tmp_path): + connection_file = str(tmp_path / "kernel.json") + km = KernelManager(connection_file=connection_file) + km.transport_encryption = True + km.pre_start_kernel() + + app = IPKernelApp(connection_file=connection_file) + super(IPKernelApp, app).initialize(argv=[""]) + app.init_connection_file() with patch.object(app.log, "debug") as mock_debug: app.init_sockets() app.cleanup_connection_file() app.close() messages = [str(call) for call in mock_debug.call_args_list] - assert any("CurveZMQ enabled" in m for m in messages), ( - "Expected a debug log mentioning CurveZMQ when enable_curve=True" + assert any("Detected CurveZMQ secret key; using transport encryption" in m for m in messages), ( + "Expected a debug log mentioning CurveZMQ when keys are provided" ) def test_init_sockets_tcp_without_curve_logs_warning(): - app = IPKernelApp(transport="tcp", enable_curve=False) + app = IPKernelApp(transport="tcp") with patch.object(app.log, "warning") as mock_warning: app.init_sockets() app.cleanup_connection_file() app.close() messages = [str(call) for call in mock_warning.call_args_list] assert any("Kernel is running over TCP without encryption" in m for m in messages), ( - "Expected a warning about missing encryption when transport=tcp and enable_curve=False" + "Expected a warning about missing encryption when transport=tcp without curve keys" ) From a058d1f0d0e33c2143a0e4b739a8223cbb2e632c Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Sat, 9 May 2026 00:06:21 +0100 Subject: [PATCH 1167/1195] Use traitlets for keys --- ipykernel/kernelapp.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 483d0fa88..036a3c7ca 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -33,6 +33,7 @@ from traitlets.traitlets import ( Any, Bool, + Bytes, Dict, DottedObjectName, Instance, @@ -158,6 +159,11 @@ class IPKernelApp(BaseIPythonApplication, InteractiveShellApp, ConnectionFileMix # connection info: connection_dir = Unicode() + # Optional CurveZMQ keys loaded from the connection file (Z85-encoded bytes). + # None when the kernel was not started with CurveZMQ enabled. + curve_publickey: Bytes | None = Bytes(allow_none=True, default_value=None) + curve_secretkey: Bytes | None = Bytes(allow_none=True, default_value=None) + @default("connection_dir") def _default_connection_dir(self): return jupyter_runtime_dir() From 49af560d20377f67c550e383e4ef51912a35c3e8 Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Tue, 12 May 2026 23:58:54 -0700 Subject: [PATCH 1168/1195] feat(displayhook): add register_hook/unregister_hook to ZMQShellDisplayHook Mirrors the hook chain on ZMQDisplayPublisher so the same transform function can register on both display_data and execute_result. Lets extensions attach ZMQ buffers (or otherwise mutate/suppress) the bare-last-expression execute_result message without subclassing ZMQShellDisplayHook and reimplementing finish_displayhook. Hook contract matches ZMQDisplayPublisher.register_hook: hook(msg) -> msg continue chain hook(msg) -> None suppress send --- ipykernel/displayhook.py | 48 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/ipykernel/displayhook.py b/ipykernel/displayhook.py index d5e136748..9ec44747d 100644 --- a/ipykernel/displayhook.py +++ b/ipykernel/displayhook.py @@ -6,12 +6,13 @@ import builtins import sys +import threading import typing as t from contextvars import ContextVar from IPython.core.displayhook import DisplayHook from jupyter_client.session import Session, extract_header -from traitlets import Any, Instance +from traitlets import Any, Instance, default from ipykernel.jsonutil import encode_images, json_clean @@ -80,6 +81,7 @@ class ZMQShellDisplayHook(DisplayHook): session = Instance(Session, allow_none=True) pub_socket = Any(allow_none=True) _parent_header: ContextVar[dict[str, Any]] + _thread_local = Any() msg: dict[str, t.Any] | None def __init__(self, *args, **kwargs): @@ -87,6 +89,33 @@ def __init__(self, *args, **kwargs): self._parent_header = ContextVar("parent_header") self._parent_header.set({}) + @default("_thread_local") + def _default_thread_local(self): + return threading.local() + + @property + def _hooks(self): + if not hasattr(self._thread_local, "hooks"): + self._thread_local.hooks = [] + return self._thread_local.hooks + + def register_hook(self, hook): + """Register a transform hook on the execute_result message. + + Mirrors ``ZMQDisplayPublisher.register_hook``. Each hook receives the + outbound message dict and must return either a (possibly mutated) + message dict to continue the chain, or ``None`` to suppress the send. + """ + self._hooks.append(hook) + + def unregister_hook(self, hook): + """Remove a previously registered hook. Returns True on success.""" + try: + self._hooks.remove(hook) + return True + except ValueError: + return False + @property def parent_header(self): try: @@ -124,9 +153,22 @@ def write_format_data(self, format_dict, md_dict=None): self.msg["content"]["metadata"] = md_dict def finish_displayhook(self): - """Finish up all displayhook activities.""" + """Finish up all displayhook activities. + + Runs the registered hook chain before ``session.send``. Each hook + either returns a message (to continue) or ``None`` (to suppress the + send). This mirrors the transform pipeline on + ``ZMQDisplayPublisher.publish`` so a single hook implementation can + attach to both the ``display_data`` and ``execute_result`` paths. + """ sys.stdout.flush() sys.stderr.flush() if self.msg and self.msg["content"]["data"] and self.session: - self.session.send(self.pub_socket, self.msg, ident=self.topic) + msg = self.msg + for hook in self._hooks: + msg = hook(msg) + if msg is None: + self.msg = None + return + self.session.send(self.pub_socket, msg, ident=self.topic) self.msg = None From d56c7a3add320bb49f948b14e3c78e7c1a48b6cf Mon Sep 17 00:00:00 2001 From: Kyle Kelley Date: Wed, 13 May 2026 00:11:23 -0700 Subject: [PATCH 1169/1195] test(displayhook): cover register_hook/unregister_hook on ZMQShellDisplayHook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the ZMQDisplayPublisher hook tests (NoReturnHook / ReturnHook, thread-local isolation, unregister) and adds: - a mutation test that attaches buffers to the outbound message — the motivating use case for execute_result hooks - short-circuit semantics in a multi-hook chain - threading semantics: each hook receives the previous hook's return value - the existing empty-data guard still suppresses both hooks and send --- tests/test_displayhook.py | 207 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 tests/test_displayhook.py diff --git a/tests/test_displayhook.py b/tests/test_displayhook.py new file mode 100644 index 000000000..86861d58f --- /dev/null +++ b/tests/test_displayhook.py @@ -0,0 +1,207 @@ +"""Tests for the ZMQ execute_result displayhook.""" + +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. + +import unittest +from queue import Queue +from threading import Thread + +import zmq +from IPython.core.interactiveshell import InteractiveShell +from jupyter_client.session import Session +from traitlets import Int + +from ipykernel.displayhook import ZMQShellDisplayHook + + +class NoReturnHook: + call_count = 0 + + def __call__(self, msg): + self.call_count += 1 + + +class ReturnHook(NoReturnHook): + def __call__(self, msg): + super().__call__(msg) + return msg + + +class MutatingHook(NoReturnHook): + """Attaches a buffer to the message and returns it.""" + + def __call__(self, msg): + super().__call__(msg) + msg.setdefault("buffers", []).append(b"arrow-bytes") + return msg + + +class CounterSession(Session): + send_count = Int(0) + last_msg = None + + def send(self, *args, **kwargs): + self.send_count += 1 + # args: (stream, msg_or_type, ...) + if len(args) >= 2: + self.last_msg = args[1] + return super().send(*args, **kwargs) + + +def _drive(hook, data=None): + """Run a single execute_result emission through the hook.""" + if data is None: + data = {"text/plain": "1"} + hook.start_displayhook() + hook.write_format_data(data, {}) + hook.finish_displayhook() + + +class ZMQShellDisplayHookTests(unittest.TestCase): + def setUp(self): + self.context = zmq.Context() + self.socket = self.context.socket(zmq.PUB) + self.session = CounterSession() + self.shell = InteractiveShell() + self.disp = ZMQShellDisplayHook(shell=self.shell) + self.disp.session = self.session + self.disp.pub_socket = self.socket + + def tearDown(self): + self.socket.close() + self.context.term() + + def test_no_hooks_sends_message(self): + """With no hooks registered, finish_displayhook still calls send.""" + assert self.disp._hooks == [] + _drive(self.disp) + assert self.session.send_count == 1 + + def test_thread_local_hooks(self): + """_hooks is thread-local: registering on one thread doesn't leak.""" + assert self.disp._hooks == [] + + def hook(msg): + return msg + + self.disp.register_hook(hook) + assert self.disp._hooks == [hook] + + q: Queue = Queue() + + def read_other_thread(): + q.put(self.disp._hooks) + + t = Thread(target=read_other_thread) + t.start() + other = q.get(timeout=10) + t.join() + assert other == [] + + def test_hook_returning_none_halts_send(self): + """A hook that returns None suppresses session.send.""" + hook = NoReturnHook() + self.disp.register_hook(hook) + + _drive(self.disp) + + assert hook.call_count == 1 + assert self.session.send_count == 0 + assert self.disp.msg is None + + def test_hook_returning_msg_calls_send(self): + """A hook that returns the message lets it through to send.""" + hook = ReturnHook() + self.disp.register_hook(hook) + + _drive(self.disp) + + assert hook.call_count == 1 + assert self.session.send_count == 1 + + def test_hook_can_mutate_message(self): + """A hook can attach buffers (the original motivation).""" + hook = MutatingHook() + self.disp.register_hook(hook) + + _drive(self.disp) + + assert hook.call_count == 1 + assert self.session.send_count == 1 + sent = self.session.last_msg + assert sent is not None + assert sent.get("buffers") == [b"arrow-bytes"] + + def test_hook_chain_short_circuits(self): + """If an early hook returns None, later hooks are not called.""" + first = NoReturnHook() + second = NoReturnHook() + self.disp.register_hook(first) + self.disp.register_hook(second) + + _drive(self.disp) + + assert first.call_count == 1 + assert second.call_count == 0 + assert self.session.send_count == 0 + + def test_hook_chain_threads_message(self): + """Each hook receives the message returned by the previous hook.""" + observed: list[dict] = [] + + def first(msg): + msg["content"]["metadata"]["seen_by_first"] = True + return msg + + def second(msg): + observed.append(msg) + return msg + + self.disp.register_hook(first) + self.disp.register_hook(second) + + _drive(self.disp) + + assert len(observed) == 1 + assert observed[0]["content"]["metadata"].get("seen_by_first") is True + assert self.session.send_count == 1 + + def test_unregister_hook(self): + """Unregistered hooks no longer run; double-unregister returns False.""" + hook = NoReturnHook() + self.disp.register_hook(hook) + + _drive(self.disp) + assert hook.call_count == 1 + assert self.session.send_count == 0 + + first = self.disp.unregister_hook(hook) + assert bool(first) + + _drive(self.disp) + # Hook didn't run again, but the message went out via session.send. + assert hook.call_count == 1 + assert self.session.send_count == 1 + + # Unregistering an unknown hook returns False. + assert not bool(self.disp.unregister_hook(hook)) + + def test_empty_data_skips_send_and_hooks(self): + """The existing guard: if content.data is empty, don't send or hook.""" + hook = ReturnHook() + self.disp.register_hook(hook) + + # start_displayhook initializes self.msg with empty data; if we never + # call write_format_data, the data dict stays empty and finish should + # short-circuit before calling either hooks or send. + self.disp.start_displayhook() + self.disp.finish_displayhook() + + assert hook.call_count == 0 + assert self.session.send_count == 0 + assert self.disp.msg is None + + +if __name__ == "__main__": + unittest.main() From bb59da28c698aa3b78b4eddc325483f30e4d481f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Krassowski?= <5832902+krassowski@users.noreply.github.com> Date: Tue, 26 May 2026 17:07:46 +0100 Subject: [PATCH 1170/1195] Change log level to info Co-authored-by: Min RK --- ipykernel/kernelapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 036a3c7ca..a9110e920 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -349,7 +349,7 @@ def init_sockets(self): atexit.register(self.close) if self.curve_secretkey is not None: - self.log.debug("Detected CurveZMQ secret key; using transport encryption") + self.log.info("Detected CurveZMQ secret key; using transport encryption") elif self.transport == "tcp": self.log.warning( "Kernel is running over TCP without encryption." From 04ec2fe7928b7a31bafc6b184bc63817c11f33ff Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Thu, 28 May 2026 13:38:01 +0100 Subject: [PATCH 1171/1195] Advertise encryption support in kernelspec --- hatch_build.py | 5 ++++- ipykernel/kernelspec.py | 2 +- tests/test_kernelspec.py | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/hatch_build.py b/hatch_build.py index 4dfdd1a22..16689021c 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -20,7 +20,10 @@ def initialize(self, version, build_data): # When building a standard wheel, the executable specified in the kernelspec is simply 'python'. if version == "standard": - overrides["metadata"] = dict(debugger=True) + overrides["metadata"] = { + "debugger": True, + "supported_encryption": "curve", + } argv = make_ipkernel_cmd(executable="python") # When installing an editable wheel, the full `sys.executable` can be used. diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index f08005a24..4581b3e83 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -66,7 +66,7 @@ def get_kernel_dict( ), "display_name": "Python %i (ipykernel)" % sys.version_info[0], "language": "python", - "metadata": {"debugger": True}, + "metadata": {"debugger": True, "supported_encryption": "curve"}, "kernel_protocol_version": "5.5", } diff --git a/tests/test_kernelspec.py b/tests/test_kernelspec.py index 66fce69b7..06f46a584 100644 --- a/tests/test_kernelspec.py +++ b/tests/test_kernelspec.py @@ -35,6 +35,8 @@ def assert_kernel_dict(d): assert d["argv"] == make_ipkernel_cmd() assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0] assert d["language"] == "python" + assert d["metadata"]["debugger"] is True + assert d["metadata"]["supported_encryption"] == "curve" assert d["kernel_protocol_version"] == "5.5" @@ -47,6 +49,8 @@ def assert_kernel_dict_with_profile(d): assert d["argv"] == make_ipkernel_cmd(extra_arguments=["--profile", "test"]) assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0] assert d["language"] == "python" + assert d["metadata"]["debugger"] is True + assert d["metadata"]["supported_encryption"] == "curve" assert d["kernel_protocol_version"] == "5.5" From 5481d4b2d4fc95a87421ebee8e1208ec2673af2d Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Thu, 28 May 2026 13:51:02 +0100 Subject: [PATCH 1172/1195] Fix tests --- tests/test_curve.py | 2 +- tests/test_kernelapp.py | 28 ++++++++++++++++++++++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/test_curve.py b/tests/test_curve.py index 72dc1b26e..ea0490852 100644 --- a/tests/test_curve.py +++ b/tests/test_curve.py @@ -180,7 +180,7 @@ def _make_app(tmp_path, *, enable_curve=False, **kwargs): if enable_curve: # Populate the Curve keys into the connection file km = KernelManager(connection_file=connection_file_path) - km.transport_encryption = True + km.transport_encryption = "enabled" km.pre_start_kernel() app = IPKernelApp(connection_file=connection_file_path, **kwargs) diff --git a/tests/test_kernelapp.py b/tests/test_kernelapp.py index 4e1c5eea2..9f4044f92 100644 --- a/tests/test_kernelapp.py +++ b/tests/test_kernelapp.py @@ -6,6 +6,7 @@ import pytest from jupyter_client import KernelManager +from jupyter_client.kernelspec import KernelSpecManager from jupyter_core.paths import secure_write from traitlets.config.loader import Config @@ -133,19 +134,38 @@ def test_trio_loop(): def test_init_sockets_curve_enabled_logs_debug(tmp_path): + kernel_name = "curve-test" + kernels_dir = tmp_path / "kernels" + kernel_dir = kernels_dir / kernel_name + kernel_dir.mkdir(parents=True) + with (kernel_dir / "kernel.json").open("w") as f: + json.dump( + { + "argv": ["python", "-m", "ipykernel_launcher", "-f", "{connection_file}"], + "display_name": "curve-test", + "language": "python", + "metadata": {"supported_encryption": "curve"}, + }, + f, + ) + connection_file = str(tmp_path / "kernel.json") - km = KernelManager(connection_file=connection_file) - km.transport_encryption = True + km = KernelManager( + connection_file=connection_file, + kernel_name=kernel_name, + kernel_spec_manager=KernelSpecManager(kernel_dirs=[str(kernels_dir)]), + ) + km.transport_encryption = "enabled" km.pre_start_kernel() app = IPKernelApp(connection_file=connection_file) super(IPKernelApp, app).initialize(argv=[""]) app.init_connection_file() - with patch.object(app.log, "debug") as mock_debug: + with patch.object(app.log, "info") as mock_info: app.init_sockets() app.cleanup_connection_file() app.close() - messages = [str(call) for call in mock_debug.call_args_list] + messages = [str(call) for call in mock_info.call_args_list] assert any("Detected CurveZMQ secret key; using transport encryption" in m for m in messages), ( "Expected a debug log mentioning CurveZMQ when keys are provided" ) From d049d23b7578041d58e123e9c81913e08182bbfa Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 2 Jun 2026 10:26:18 +0200 Subject: [PATCH 1173/1195] Fix recent debugpy failures (#1524) --- tests/test_debugger.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_debugger.py b/tests/test_debugger.py index fe75e28f3..90380629f 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -1,6 +1,7 @@ import sys import pytest +from packaging.version import Version from .utils import TIMEOUT, get_replies, get_reply, new_kernel @@ -119,7 +120,12 @@ def test_attach_debug(kernel_with_debug): ) if debugpy: assert reply["success"] - assert reply["body"]["result"] == "" + # A "repl" evaluate with no frameId hits debugpy's exec path. debugpy < 1.8.21 + # reported an empty result there; debugpy >= 1.8.21 reports the actual value. + if Version(debugpy.__version__) >= Version("1.8.21"): + assert reply["body"]["result"] == "ab" + else: + assert reply["body"]["result"] == "" else: assert reply == {} From b8c74735b42177e536f4bcf0a13d3c19231c24c5 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:02:38 +0100 Subject: [PATCH 1174/1195] Fix debugger when curve encryption is enabled --- ipykernel/kernelapp.py | 9 +++++++++ tests/test_curve.py | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index a9110e920..9413b86d6 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -228,6 +228,14 @@ def _apply_curve_server_options(self, socket: zmq.Socket[t.Any]) -> None: socket.curve_publickey = self.curve_publickey socket.curve_server = True + def _apply_curve_client_options(self, socket: zmq.Socket[t.Any]) -> None: + """Set CurveZMQ client-side options on *socket* before it connects.""" + if self.curve_secretkey is not None: + socket.curve_serverkey = self.curve_publickey + # Reuse manager-provisioned keypair for the in-kernel client socket. + socket.curve_secretkey = self.curve_secretkey + socket.curve_publickey = self.curve_publickey + def init_poller(self): """Initialize the poller.""" if sys.platform == "win32": @@ -393,6 +401,7 @@ def init_control(self, context): self.debug_shell_socket = context.socket(zmq.DEALER) self.debug_shell_socket.linger = 1000 + self._apply_curve_client_options(self.debug_shell_socket) if self.shell_socket.getsockopt(zmq.LAST_ENDPOINT): self.debug_shell_socket.connect(self.shell_socket.getsockopt(zmq.LAST_ENDPOINT)) diff --git a/tests/test_curve.py b/tests/test_curve.py index ea0490852..e5dd80df2 100644 --- a/tests/test_curve.py +++ b/tests/test_curve.py @@ -174,6 +174,23 @@ def test_manager_provisioned_curve_keys_are_used(curve_enabled_kernel_app): app.close() +def test_curve_debug_shell_socket_can_communicate(curve_enabled_kernel_app): + """Debugger shell socket can talk to the shell ROUTER when Curve is enabled.""" + app, _connection_file_path = curve_enabled_kernel_app + app.init_sockets() + + # Allow the Curve handshake to complete before sending a probe. + time.sleep(0.05) + app.debug_shell_socket.send(b"debug-probe", flags=zmq.NOBLOCK) + + poller = zmq.Poller() + poller.register(app.shell_socket, zmq.POLLIN) + events = dict(poller.poll(timeout=1000)) + assert app.shell_socket in events, ( + "debug_shell_socket could not reach shell_socket with Curve enabled" + ) + + def _make_app(tmp_path, *, enable_curve=False, **kwargs): """Return a minimal IPKernelApp rooted in temporary directory *tmp_path*.""" connection_file_path = str(tmp_path / "kernel.json") From 7eb90e04ea31bff61fef1f6b63a588f26ecd3830 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Tue, 2 Jun 2026 11:56:29 +0100 Subject: [PATCH 1175/1195] Fix somewhat flaky test by waiting for the connection file to show up --- tests/test_curve.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_curve.py b/tests/test_curve.py index e5dd80df2..bd47e160c 100644 --- a/tests/test_curve.py +++ b/tests/test_curve.py @@ -3,6 +3,7 @@ import json import time +from pathlib import Path import pytest import zmq @@ -199,6 +200,10 @@ def _make_app(tmp_path, *, enable_curve=False, **kwargs): km = KernelManager(connection_file=connection_file_path) km.transport_encryption = "enabled" km.pre_start_kernel() + for _ in range(100): + if Path(connection_file_path).exists(): + break + time.sleep(0.01) app = IPKernelApp(connection_file=connection_file_path, **kwargs) # Replicate the subset of initialize() that sets up connection info From 7ecb521f6df4d2219dda8596276186796f06af8b Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Tue, 2 Jun 2026 12:26:22 +0100 Subject: [PATCH 1176/1195] Fix tests --- tests/test_curve.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/tests/test_curve.py b/tests/test_curve.py index bd47e160c..8f709cd11 100644 --- a/tests/test_curve.py +++ b/tests/test_curve.py @@ -3,11 +3,11 @@ import json import time -from pathlib import Path import pytest import zmq from jupyter_client import KernelManager +from jupyter_client.kernelspec import KernelSpecManager from ipykernel.kernelapp import IPKernelApp @@ -28,6 +28,8 @@ def curve_enabled_kernel_app(tmp_path): yield app, connection_file_path finally: app.close() + if hasattr(app, "_curve_seed_manager"): + app._curve_seed_manager.cleanup_connection_file() def test_connection_file_no_curve_keys_by_default(curve_disabled_kernel_app): @@ -195,17 +197,38 @@ def test_curve_debug_shell_socket_can_communicate(curve_enabled_kernel_app): def _make_app(tmp_path, *, enable_curve=False, **kwargs): """Return a minimal IPKernelApp rooted in temporary directory *tmp_path*.""" connection_file_path = str(tmp_path / "kernel.json") + km = None if enable_curve: + kernel_name = "curve-test" + kernels_dir = tmp_path / "kernels" + kernel_dir = kernels_dir / kernel_name + kernel_dir.mkdir(parents=True) + with (kernel_dir / "kernel.json").open("w") as f: + json.dump( + { + "argv": ["python", "-m", "ipykernel_launcher", "-f", "{connection_file}"], + "display_name": "curve-test", + "language": "python", + "metadata": {"supported_encryption": "curve"}, + }, + f, + ) + # Populate the Curve keys into the connection file - km = KernelManager(connection_file=connection_file_path) + km = KernelManager( + connection_file=connection_file_path, + kernel_name=kernel_name, + kernel_spec_manager=KernelSpecManager(kernel_dirs=[str(kernels_dir)]), + ) km.transport_encryption = "enabled" km.pre_start_kernel() - for _ in range(100): - if Path(connection_file_path).exists(): - break - time.sleep(0.01) app = IPKernelApp(connection_file=connection_file_path, **kwargs) + if km is not None: + # Keep the seeding manager alive for the test lifetime. + # Its destructor cleans up the connection file, which is otherwise + # timing-dependent across Python versions. + app._curve_seed_manager = km # Replicate the subset of initialize() that sets up connection info # without importing IPython shell machinery. super(IPKernelApp, app).initialize(argv=[""]) From 336971eae905d507d16be4d28ce7622d0239b6df Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Tue, 2 Jun 2026 18:38:29 +0200 Subject: [PATCH 1177/1195] build docs on latest python (#1525) --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 99ec2dd9b..98e683914 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -130,6 +130,8 @@ jobs: - uses: actions/checkout@v6 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + with: + python_version: "3.14" - name: Build API docs run: | From 2157ae73ba7562413f5401f72f660f869995713c Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Wed, 3 Jun 2026 08:54:24 +0100 Subject: [PATCH 1178/1195] Update tests to reflact rename to "auto" --- tests/test_curve.py | 2 +- tests/test_kernelapp.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_curve.py b/tests/test_curve.py index 8f709cd11..613cf2ef4 100644 --- a/tests/test_curve.py +++ b/tests/test_curve.py @@ -220,7 +220,7 @@ def _make_app(tmp_path, *, enable_curve=False, **kwargs): kernel_name=kernel_name, kernel_spec_manager=KernelSpecManager(kernel_dirs=[str(kernels_dir)]), ) - km.transport_encryption = "enabled" + km.transport_encryption = "auto" km.pre_start_kernel() app = IPKernelApp(connection_file=connection_file_path, **kwargs) diff --git a/tests/test_kernelapp.py b/tests/test_kernelapp.py index 9f4044f92..ff654f06f 100644 --- a/tests/test_kernelapp.py +++ b/tests/test_kernelapp.py @@ -155,7 +155,7 @@ def test_init_sockets_curve_enabled_logs_debug(tmp_path): kernel_name=kernel_name, kernel_spec_manager=KernelSpecManager(kernel_dirs=[str(kernels_dir)]), ) - km.transport_encryption = "enabled" + km.transport_encryption = "auto" km.pre_start_kernel() app = IPKernelApp(connection_file=connection_file) From b0678baa936815e7b497cbbe3bacbc16f4029086 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Fri, 5 Jun 2026 20:38:18 +0100 Subject: [PATCH 1179/1195] Update `pyproject.toml` to depend on `jupyter_client` 8.9.0 rather than branch --- pyproject.toml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 58b414f30..9d2273df1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,7 +23,7 @@ dependencies = [ "ipython>=7.23.1", "comm>=0.1.1", "traitlets>=5.4.0", - "jupyter_client @ git+https://github.com/krassowski/jupyter_client.git@add-curve-encryption", + "jupyter_client>=8.9.0", "jupyter_core>=5.1,!=6.0.*", # For tk event loop support only. "nest_asyncio2>=1.7.0", @@ -71,9 +71,6 @@ cov = [ pyqt5 = ["pyqt5"] pyside6 = ["pyside6"] -[tool.hatch.metadata] -allow-direct-references = true - [tool.hatch.version] path = "ipykernel/_version.py" From cd6f460c5f09a4fba3cf7f57d2bc941060133cd5 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Sat, 6 Jun 2026 13:43:34 +0100 Subject: [PATCH 1180/1195] Fix typing due to new `jupyter_client` version --- ipykernel/inprocess/client.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ipykernel/inprocess/client.py b/ipykernel/inprocess/client.py index d18eec12e..f43951ec6 100644 --- a/ipykernel/inprocess/client.py +++ b/ipykernel/inprocess/client.py @@ -17,6 +17,7 @@ from jupyter_client.client import KernelClient from jupyter_client.clientabc import KernelClientABC +from jupyter_client.connect import KernelConnectionInfo from jupyter_core.utils import run_sync # IPython imports @@ -59,10 +60,10 @@ def _default_blocking_class(self): return BlockingInProcessKernelClient - def get_connection_info(self, session: bool = False) -> dict[str, int | str | bytes]: + def get_connection_info(self, session: bool = False) -> KernelConnectionInfo: """Get the connection info for the client.""" d = super().get_connection_info(session=session) - d["kernel"] = self.kernel # type:ignore[assignment] + d["kernel"] = self.kernel # type: ignore[typeddict-unknown-key] return d def start_channels(self, *args, **kwargs): From 6fc8b58d8c3d2a23d5cf18c528325aef7598bafb Mon Sep 17 00:00:00 2001 From: Akash Goel Date: Tue, 9 Jun 2026 05:40:14 -0700 Subject: [PATCH 1181/1195] Fix broken IPython Reference URL in help_links (#1526) --- ipykernel/ipkernel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/ipkernel.py b/ipykernel/ipkernel.py index 215129063..7ce9cc013 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -178,7 +178,7 @@ def __init__(self, **kwargs): }, { "text": "IPython Reference", - "url": "https://ipython.org/documentation.html", + "url": "https://ipython.readthedocs.io/en/stable/", }, { "text": "NumPy Reference", From 28e9cf822f3f3fd4f92c58db1213f7a54cf43a96 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 Jun 2026 10:02:25 +0200 Subject: [PATCH 1182/1195] chore: update pre-commit hooks (#1508) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 3 ++ .pre-commit-config.yaml | 40 +++++++++++++++++------ ipykernel/comm/comm.py | 4 +-- ipykernel/comm/manager.py | 2 +- ipykernel/eventloops.py | 2 +- ipykernel/kernelbase.py | 2 +- pyproject.toml | 4 ++- scripts/check_mypy_deps.py | 66 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 108 insertions(+), 15 deletions(-) create mode 100755 scripts/check_mypy_deps.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 98e683914..185f4febf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,6 +106,9 @@ jobs: - uses: actions/checkout@v6 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Check mypy deps are in sync with project deps + run: hatch run typing:check + - name: Run Linters run: | hatch run typing:test diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41f3fbe6d..38abb2417 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.37.0 + rev: 0.37.2 hooks: - id: check-github-workflows @@ -39,7 +39,7 @@ repos: types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.19.1" + rev: "v2.1.0" hooks: - id: mypy files: ipykernel @@ -47,12 +47,34 @@ repos: args: ["--install-types", "--non-interactive"] additional_dependencies: [ - "traitlets>=5.13", - "ipython>=8.16.1", - "jupyter_client>=8.5", - "appnope", + "appnope>=0.1.2", + "comm>=0.1.1", + "debugpy>=1.6.5", + "ipython>=7.23.1", + "jupyter_client>=8.9.0", + "jupyter_core>=5.1", + "matplotlib-inline>=0.1", + "nest_asyncio2>=1.7.0", + "packaging>=22", + "psutil>=5.7", + "pyzmq>=25", + "tornado>=6.4.1", + "traitlets>=5.4.0", ] + - repo: local + hooks: + - id: check-mypy-deps + name: mypy deps in sync with project deps + entry: python scripts/check_mypy_deps.py + language: python + pass_filenames: false + always_run: true + additional_dependencies: + - "pyyaml" + - "packaging" + - "tomli; python_version < '3.11'" + - repo: https://github.com/adamchainz/blacken-docs rev: "1.20.0" hooks: @@ -60,7 +82,7 @@ repos: additional_dependencies: [black==23.7.0] - repo: https://github.com/codespell-project/codespell - rev: "v2.4.1" + rev: "v2.4.2" hooks: - id: codespell args: ["-L", "sur,nd"] @@ -73,7 +95,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.4 + rev: v0.15.16 hooks: - id: ruff-check types_or: [python, jupyter] @@ -82,7 +104,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2026.03.02" + rev: "2026.04.04" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/ipykernel/comm/comm.py b/ipykernel/comm/comm.py index a1b659e9a..9be5e23d0 100644 --- a/ipykernel/comm/comm.py +++ b/ipykernel/comm/comm.py @@ -16,7 +16,7 @@ # this is the class that will be created if we do comm.create_comm -class BaseComm(comm.base_comm.BaseComm): # type:ignore[misc] +class BaseComm(comm.base_comm.BaseComm): """The base class for comms.""" kernel: Optional["Kernel"] = None @@ -90,7 +90,7 @@ def __init__( kernel = kwargs.pop("kernel", None) if target_name: kwargs["target_name"] = target_name - BaseComm.__init__(self, data=data, metadata=metadata, buffers=buffers, **kwargs) # type:ignore[call-arg] + BaseComm.__init__(self, data=data, metadata=metadata, buffers=buffers, **kwargs) # only re-add kernel if explicitly provided if had_kernel: kwargs["kernel"] = kernel diff --git a/ipykernel/comm/manager.py b/ipykernel/comm/manager.py index aaef027ce..092754946 100644 --- a/ipykernel/comm/manager.py +++ b/ipykernel/comm/manager.py @@ -14,7 +14,7 @@ logger = logging.getLogger("ipykernel.comm") -class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): # type:ignore[misc] +class CommManager(comm.base_comm.CommManager, traitlets.config.LoggingConfigurable): """A comm manager.""" kernel = traitlets.Instance("ipykernel.kernelbase.Kernel") diff --git a/ipykernel/eventloops.py b/ipykernel/eventloops.py index a767eb1a9..dd160674a 100644 --- a/ipykernel/eventloops.py +++ b/ipykernel/eventloops.py @@ -301,7 +301,7 @@ def _schedule_exit(delay): else: import asyncio - import nest_asyncio2 + import nest_asyncio2 # type: ignore[import-untyped] nest_asyncio2.apply() diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 27bd371c9..02a0dd405 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -441,7 +441,7 @@ async def dispatch_shell(self, msg, /, subshell_id: str | None = None): # Only abort execute requests if msg_type == "execute_request": if subshell_id is None: - aborting = self._aborting # type:ignore[unreachable] + aborting = self._aborting else: aborting = self.shell_channel_thread.manager.get_subshell_aborting(subshell_id) if aborting: diff --git a/pyproject.toml b/pyproject.toml index 9d2273df1..4df9c0a3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,6 +113,7 @@ dependencies = ["pre-commit"] detached = true [tool.hatch.envs.typing.scripts] test = "pre-commit run --all-files --hook-stage manual mypy" +check = "pre-commit run --all-files check-mypy-deps" [tool.hatch.envs.lint] dependencies = ["pre-commit"] @@ -120,7 +121,8 @@ detached = true [tool.hatch.envs.lint.scripts] build = [ "pre-commit run --all-files ruff-check", - "pre-commit run --all-files ruff-format" + "pre-commit run --all-files ruff-format", + "pre-commit run --all-files check-mypy-deps", ] [tool.mypy] diff --git a/scripts/check_mypy_deps.py b/scripts/check_mypy_deps.py new file mode 100755 index 000000000..1d75a2c87 --- /dev/null +++ b/scripts/check_mypy_deps.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python +"""Verify that the mypy hook's additional_dependencies in .pre-commit-config.yaml +includes all runtime dependencies declared in pyproject.toml. + +Run directly or via pre-commit (check-mypy-deps hook). +""" + +from __future__ import annotations + +import sys + +try: + import tomllib +except ImportError: + import tomli as tomllib # type: ignore[no-reuse-def] + +import yaml +from packaging.requirements import Requirement + + +def normalize(name: str) -> str: + return name.lower().replace("-", "_").replace(".", "_") + + +def main() -> int: + with open("pyproject.toml", "rb") as f: + pyproject = tomllib.load(f) + + with open(".pre-commit-config.yaml") as f: + pre_commit = yaml.safe_load(f) + + project_deps: list[str] = pyproject["project"]["dependencies"] + project_packages = {normalize(Requirement(dep).name) for dep in project_deps} + + mypy_additional: list[str] | None = None + for repo in pre_commit["repos"]: + for hook in repo.get("hooks", []): + if hook["id"] == "mypy": + mypy_additional = hook.get("additional_dependencies", []) + break + if mypy_additional is not None: + break + + if mypy_additional is None: + print("ERROR: mypy hook not found in .pre-commit-config.yaml") + return 1 + + mypy_packages = {normalize(Requirement(dep).name) for dep in mypy_additional} + + missing = project_packages - mypy_packages + if missing: + print( + "ERROR: The following project dependencies are missing from the mypy\n" + "hook's additional_dependencies in .pre-commit-config.yaml:\n" + ) + for pkg in sorted(missing): + print(f" {pkg}") + print("\nAdd them to the `additional_dependencies` list of the mypy hook.") + return 1 + + print("OK: mypy additional_dependencies covers all project runtime dependencies.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) From 5dbd5ce56fa17f89075f7d866d5dbe35ca543085 Mon Sep 17 00:00:00 2001 From: Carreau Date: Wed, 10 Jun 2026 08:41:13 +0000 Subject: [PATCH 1183/1195] Publish 7.3.0 SHA256 hashes: ipykernel-7.3.0-py3-none-any.whl: 897eb64da762549ef610698fca5e9675195ec6ac8ec7f19d81ce1ca20c876057 ipykernel-7.3.0.tar.gz: 9acaaaf97d16355166e4085afe9d225bfbdf2b7ef520f9df3be8f2b248275e09 --- CHANGELOG.md | 54 +++++++++++++++++++++++++++++++++++++++++-- ipykernel/_version.py | 2 +- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97c1f230b..c2f67fa9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,58 @@ +## 7.3.0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.2.0...28e9cf822f3f3fd4f92c58db1213f7a54cf43a96)) + +### New features added + +- feat(displayhook): add register_hook/unregister_hook to ZMQShellDisplayHook [#1522](https://github.com/ipython/ipykernel/pull/1522) ([@rgbkrk](https://github.com/rgbkrk), [@minrk](https://github.com/minrk)) +- Support ZMQ Curve for transport encryption [#1515](https://github.com/ipython/ipykernel/pull/1515) ([@krassowski](https://github.com/krassowski), [@Carreau](https://github.com/Carreau), [@minrk](https://github.com/minrk)) + +### Enhancements made + +- Support new iPython system_raise_on_error flag in ZMQInteractiveShell [#1496](https://github.com/ipython/ipykernel/pull/1496) ([@adityawasudeo](https://github.com/adityawasudeo), [@ianthomas23](https://github.com/ianthomas23)) +- pass cell_meta from do_execute to run_cell [#1475](https://github.com/ipython/ipykernel/pull/1475) ([@erawn](https://github.com/erawn), [@P4X-ng](https://github.com/P4X-ng), [@ianthomas23](https://github.com/ianthomas23)) + +### Bugs fixed + +- Fix broken IPython Reference URL in help_links [#1526](https://github.com/ipython/ipykernel/pull/1526) ([@goelakash](https://github.com/goelakash), [@Carreau](https://github.com/Carreau), [@JohanMabille](https://github.com/JohanMabille), [@krassowski](https://github.com/krassowski)) +- Mark cell execution as failing when formatter errors out [#1521](https://github.com/ipython/ipykernel/pull/1521) ([@krassowski](https://github.com/krassowski), [@Carreau](https://github.com/Carreau)) +- Fix ENOTSOCK traceback when iopub socket closes during shutdown [#1518](https://github.com/ipython/ipykernel/pull/1518) ([@Carreau](https://github.com/Carreau), [@minrk](https://github.com/minrk)) +- Bugfix: Ensure that we take a copy of the sys.modules before iterating over it [#1514](https://github.com/ipython/ipykernel/pull/1514) ([@pelson](https://github.com/pelson), [@Carreau](https://github.com/Carreau)) + +### Maintenance and upkeep improvements + +- build docs on latest python [#1525](https://github.com/ipython/ipykernel/pull/1525) ([@Carreau](https://github.com/Carreau)) +- Fix recent debugpy failures [#1524](https://github.com/ipython/ipykernel/pull/1524) ([@Carreau](https://github.com/Carreau)) +- Unpin Spinx now that typehints issue ought to be resolved [#1517](https://github.com/ipython/ipykernel/pull/1517) ([@krassowski](https://github.com/krassowski), [@Carreau](https://github.com/Carreau)) +- Fix downstream test: do not use editable install for `ipyparallel` [#1516](https://github.com/ipython/ipykernel/pull/1516) ([@krassowski](https://github.com/krassowski), [@Carreau](https://github.com/Carreau)) +- Linting fixes [#1509](https://github.com/ipython/ipykernel/pull/1509) ([@ianthomas23](https://github.com/ianthomas23)) +- chore: update pre-commit hooks [#1508](https://github.com/ipython/ipykernel/pull/1508) ([@Carreau](https://github.com/Carreau)) +- chore: update pre-commit hooks [#1502](https://github.com/ipython/ipykernel/pull/1502) ([@ianthomas23](https://github.com/ianthomas23)) +- Removed pinning of virtualenv [#1501](https://github.com/ipython/ipykernel/pull/1501) ([@JohanMabille](https://github.com/JohanMabille)) +- Fixed a flaky windows test [#1500](https://github.com/ipython/ipykernel/pull/1500) ([@adityawasudeo](https://github.com/adityawasudeo), [@JohanMabille](https://github.com/JohanMabille), [@ianthomas23](https://github.com/ianthomas23)) +- Switch from using nest-asyncio to nest-asyncio2 [#1499](https://github.com/ipython/ipykernel/pull/1499) ([@ianthomas23](https://github.com/ianthomas23)) +- Temporary pin virtualenv < 21 [#1498](https://github.com/ipython/ipykernel/pull/1498) ([@ianthomas23](https://github.com/ianthomas23)) +- Reintroduce changing base method to async [#1497](https://github.com/ipython/ipykernel/pull/1497) ([@ianthomas23](https://github.com/ianthomas23)) +- chore: update pre-commit hooks [#1495](https://github.com/ipython/ipykernel/pull/1495) ([@ianthomas23](https://github.com/ianthomas23)) + +### Other merged PRs + +- Bump the actions group across 1 directory with 2 updates [#1513](https://github.com/ipython/ipykernel/pull/1513) ([@Carreau](https://github.com/Carreau), [@minrk](https://github.com/minrk)) + +### Contributors to this release + +The following people contributed discussions, new ideas, code and documentation contributions, and review. +See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/use/#how-does-this-tool-define-contributions-in-the-reports). + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2026-02-06&to=2026-06-10&type=c)) + +@adityawasudeo ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aadityawasudeo+updated%3A2026-02-06..2026-06-10&type=Issues)) | @Carreau ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2026-02-06..2026-06-10&type=Issues)) | @erawn ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aerawn+updated%3A2026-02-06..2026-06-10&type=Issues)) | @goelakash ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Agoelakash+updated%3A2026-02-06..2026-06-10&type=Issues)) | @ianthomas23 ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2026-02-06..2026-06-10&type=Issues)) | @JohanMabille ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2026-02-06..2026-06-10&type=Issues)) | @krassowski ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2026-02-06..2026-06-10&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2026-02-06..2026-06-10&type=Issues)) | @P4X-ng ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AP4X-ng+updated%3A2026-02-06..2026-06-10&type=Issues)) | @pelson ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Apelson+updated%3A2026-02-06..2026-06-10&type=Issues)) | @rgbkrk ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Argbkrk+updated%3A2026-02-06..2026-06-10&type=Issues)) + + + ## 7.2.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/39eaf96a...1630c4f7d5365918c4f06cf3caee3c278b52afc2)) @@ -43,8 +95,6 @@ See [our definition of contributors](https://github-activity.readthedocs.io/en/l @arjxn-py ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aarjxn-py+updated%3A2025-10-27..2026-02-06&type=Issues)) | @Carreau ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2025-10-27..2026-02-06&type=Issues)) | @ccordoba12 ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2025-10-27..2026-02-06&type=Issues)) | @ianthomas23 ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-10-27..2026-02-06&type=Issues)) | @JohanMabille ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3AJohanMabille+updated%3A2025-10-27..2026-02-06&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2025-10-27..2026-02-06&type=Issues)) | @newville ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anewville+updated%3A2025-10-27..2026-02-06&type=Issues)) | @SylvainCorlay ([activity](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ASylvainCorlay+updated%3A2025-10-27..2026-02-06&type=Issues)) - - ## 7.2.0a1 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v7.2.0a0...220a3c6e8b24ffb3f8678925712ff3644aafb41e)) diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 4c4be1dc4..f415eceba 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -5,7 +5,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "7.2.0" +__version__ = "7.3.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" From a30399e77a84777032718744a5432585f9348e69 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Thu, 11 Jun 2026 13:56:08 +0100 Subject: [PATCH 1184/1195] Update the `supported_encryption` to use a list as per JEP latest state --- hatch_build.py | 2 +- ipykernel/kernelspec.py | 2 +- tests/test_kernelspec.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hatch_build.py b/hatch_build.py index 16689021c..48addb7e4 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -22,7 +22,7 @@ def initialize(self, version, build_data): if version == "standard": overrides["metadata"] = { "debugger": True, - "supported_encryption": "curve", + "supported_encryption": ["curve"], } argv = make_ipkernel_cmd(executable="python") diff --git a/ipykernel/kernelspec.py b/ipykernel/kernelspec.py index 4581b3e83..bdee4b02e 100644 --- a/ipykernel/kernelspec.py +++ b/ipykernel/kernelspec.py @@ -66,7 +66,7 @@ def get_kernel_dict( ), "display_name": "Python %i (ipykernel)" % sys.version_info[0], "language": "python", - "metadata": {"debugger": True, "supported_encryption": "curve"}, + "metadata": {"debugger": True, "supported_encryption": ["curve"]}, "kernel_protocol_version": "5.5", } diff --git a/tests/test_kernelspec.py b/tests/test_kernelspec.py index 06f46a584..744b77ec4 100644 --- a/tests/test_kernelspec.py +++ b/tests/test_kernelspec.py @@ -36,7 +36,7 @@ def assert_kernel_dict(d): assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0] assert d["language"] == "python" assert d["metadata"]["debugger"] is True - assert d["metadata"]["supported_encryption"] == "curve" + assert d["metadata"]["supported_encryption"] == ["curve"] assert d["kernel_protocol_version"] == "5.5" @@ -50,7 +50,7 @@ def assert_kernel_dict_with_profile(d): assert d["display_name"] == "Python %i (ipykernel)" % sys.version_info[0] assert d["language"] == "python" assert d["metadata"]["debugger"] is True - assert d["metadata"]["supported_encryption"] == "curve" + assert d["metadata"]["supported_encryption"] == ["curve"] assert d["kernel_protocol_version"] == "5.5" From fea5b1f6ca1ccdcbdce9d59a75fb64960075ec02 Mon Sep 17 00:00:00 2001 From: sahvx655-wq Date: Mon, 15 Jun 2026 14:16:19 +0530 Subject: [PATCH 1185/1195] create debugger tmp directory with 0o700 permissions --- ipykernel/debugger.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ipykernel/debugger.py b/ipykernel/debugger.py index b7dd4a259..ec4cf3e94 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -426,7 +426,7 @@ def start(self): if not self.debugpy_initialized: tmp_dir = get_tmp_directory() if not Path(tmp_dir).exists(): - Path(tmp_dir).mkdir(parents=True) + Path(tmp_dir).mkdir(mode=0o700, parents=True) host, port = self.debugpy_client.get_host_port() code = "import debugpy;" code += 'debugpy.listen(("' + host + '",' + port + "))" From 766122a698a700f1963f4a74e4c6897ea9970490 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2026 21:21:33 +0000 Subject: [PATCH 1186/1195] chore: update pre-commit hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/python-jsonschema/check-jsonschema: 0.37.2 → 0.37.3](https://github.com/python-jsonschema/check-jsonschema/compare/0.37.2...0.37.3) - [github.com/astral-sh/ruff-pre-commit: v0.15.16 → v0.15.17](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.16...v0.15.17) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 38abb2417..d24477176 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.37.2 + rev: 0.37.3 hooks: - id: check-github-workflows @@ -95,7 +95,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.16 + rev: v0.15.17 hooks: - id: ruff-check types_or: [python, jupyter] From 7aa623c8b66fa72830f54fd75195418e7a7abef3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 21:21:48 +0000 Subject: [PATCH 1187/1195] chore: update pre-commit hooks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.15.17 → v0.15.18](https://github.com/astral-sh/ruff-pre-commit/compare/v0.15.17...v0.15.18) - [github.com/scientific-python/cookie: 2026.04.04 → 2026.06.18](https://github.com/scientific-python/cookie/compare/2026.04.04...2026.06.18) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d24477176..43013230b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -95,7 +95,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.17 + rev: v0.15.18 hooks: - id: ruff-check types_or: [python, jupyter] @@ -104,7 +104,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2026.04.04" + rev: "2026.06.18" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] From 9627f5dd75d116db016d9c3e49519ff3e9ca5693 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2026 22:42:45 +0000 Subject: [PATCH 1188/1195] Bump actions/checkout from 6 to 7 in the actions group Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout). Updates `actions/checkout` from 6 to 7 - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v6...v7) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 20 ++++++++++---------- .github/workflows/downstream.yml | 10 +++++----- .github/workflows/nightly.yml | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 185f4febf..3774b4a84 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: @@ -94,7 +94,7 @@ jobs: needs: - build steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: jupyterlab/maintainer-tools/.github/actions/report-coverage@v1 with: fail_under: 80 @@ -103,7 +103,7 @@ jobs: name: Test Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - name: Check mypy deps are in sync with project deps @@ -119,7 +119,7 @@ jobs: check_release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -130,7 +130,7 @@ jobs: test_docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: @@ -156,7 +156,7 @@ jobs: python-version: ["3.10"] steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -179,7 +179,7 @@ jobs: timeout-minutes: 20 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: actions/setup-python@v6 with: @@ -205,7 +205,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -221,7 +221,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1 @@ -237,6 +237,6 @@ jobs: link_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index 88f4627c3..8d73da022 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -29,7 +29,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -44,7 +44,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -60,7 +60,7 @@ jobs: timeout-minutes: 20 steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 @@ -75,7 +75,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index deb9a54d5..01b95a8dc 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -17,7 +17,7 @@ jobs: python-version: ["3.12"] steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Base Setup uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 From 3f08f18ede2f13d28cd333ee0a08fe55884f0503 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Tue, 23 Jun 2026 09:52:22 +0100 Subject: [PATCH 1189/1195] bump tornado dependency to version 6.5.7 for security updates --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4df9c0a3a..2b46776f7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,7 @@ dependencies = [ "jupyter_core>=5.1,!=6.0.*", # For tk event loop support only. "nest_asyncio2>=1.7.0", - "tornado>=6.4.1", + "tornado>=6.5.7", "matplotlib-inline>=0.1", 'appnope>=0.1.2;platform_system=="Darwin"', "pyzmq>=25", From 128f7e275aac11cea7622fafc6885b5b63e317c4 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 Jul 2026 08:58:39 +0100 Subject: [PATCH 1190/1195] chore: update pre-commit hooks (#1536) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 43013230b..e39ba275e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.37.3 + rev: 0.37.4 hooks: - id: check-github-workflows @@ -95,7 +95,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.18 + rev: v0.15.20 hooks: - id: ruff-check types_or: [python, jupyter] From 5d56460599069860ee2b6d3d5a4ad31066acc00c Mon Sep 17 00:00:00 2001 From: yves-surrel Date: Fri, 17 Jul 2026 14:04:50 +0200 Subject: [PATCH 1191/1195] Make psutil optional at runtime (#1527) Co-authored-by: yves Co-authored-by: Ian Thomas --- ipykernel/kernelbase.py | 32 ++++++++++++++++++++++++++------ pyproject.toml | 2 +- tests/test_kernel_direct.py | 27 +++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 02a0dd405..3e9ce0f7c 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -36,7 +36,6 @@ # jupyter_client < 5, use local now() now = datetime.now -import psutil import zmq from IPython.core.error import StdinNotImplementedError from jupyter_client.session import Session @@ -62,6 +61,19 @@ from .iostream import OutStream from .utils import LazyDict, _async_in_context +psutil: t.Any | None +try: + import psutil as _psutil +except ImportError: + psutil = None +else: + psutil = _psutil + +if psutil is None: + _NO_SUCH_PROCESS: tuple[type[BaseException], ...] = () +else: + _NO_SUCH_PROCESS = (psutil.NoSuchProcess,) + _AWAITABLE_MESSAGE: str = ( "For consistency across implementations, it is recommended that `{func_name}`" " either be a coroutine function (`async def`) or return an awaitable object" @@ -97,7 +109,7 @@ class Kernel(SingletonConfigurable): # attribute to override with a GUI eventloop = Any(None) - processes: dict[str, psutil.Process] = {} + processes: dict[int, t.Any] = {} @observe("eventloop") def _update_eventloop(self, change): @@ -1172,13 +1184,18 @@ async def usage_request(self, stream, ident, parent): if not self.session: return reply_content = {"hostname": socket.gethostname(), "pid": os.getpid()} + if psutil is None: + reply_content["cpu_count"] = os.cpu_count() + reply_msg = self.session.send(stream, "usage_reply", reply_content, parent, ident) + self.log.debug("%s", reply_msg) + return + current_process = psutil.Process() all_processes = [current_process, *current_process.children(recursive=True)] # Ensure 1) self.processes is updated to only current subprocesses # and 2) we reuse processes when possible (needed for accurate CPU) self.processes = { - process.pid: self.processes.get(process.pid, process) # type:ignore[misc,call-overload] - for process in all_processes + process.pid: self.processes.get(process.pid, process) for process in all_processes } reply_content["kernel_cpu"] = sum( [ @@ -1196,7 +1213,7 @@ async def usage_request(self, stream, ident, parent): cpu_percent = psutil.cpu_percent() # https://psutil.readthedocs.io/en/latest/index.html?highlight=cpu#psutil.cpu_percent # The first time cpu_percent is called it will return a meaningless 0.0 value which you are supposed to ignore. - if cpu_percent is not None and cpu_percent != 0.0: # type:ignore[redundant-expr] + if cpu_percent is not None and cpu_percent != 0.0: reply_content["host_cpu_percent"] = cpu_percent reply_content["cpu_count"] = psutil.cpu_count(logical=True) reply_content["host_virtual_memory"] = dict(psutil.virtual_memory()._asdict()) @@ -1476,7 +1493,7 @@ def _signal_children(self, signum): p.kill() else: p.send_signal(signum) - except psutil.NoSuchProcess: + except _NO_SUCH_PROCESS: pass def _process_children(self): @@ -1486,6 +1503,9 @@ def _process_children(self): - including parents and self with killpg - including all children that may have forked-off a new group """ + if psutil is None: + return [] + kernel_process = psutil.Process() all_children = kernel_process.children(recursive=True) if os.name == "nt": diff --git a/pyproject.toml b/pyproject.toml index 2b46776f7..b3a8ae919 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,6 @@ dependencies = [ "matplotlib-inline>=0.1", 'appnope>=0.1.2;platform_system=="Darwin"', "pyzmq>=25", - "psutil>=5.7", "packaging>=22", ] @@ -59,6 +58,7 @@ test = [ "flaky", "ipyparallel", "pre-commit", + "psutil>=5.7", "pytest-asyncio>=0.23.5", "pytest-timeout" ] diff --git a/tests/test_kernel_direct.py b/tests/test_kernel_direct.py index f4a2e59b7..146ae3ead 100644 --- a/tests/test_kernel_direct.py +++ b/tests/test_kernel_direct.py @@ -4,6 +4,7 @@ # Distributed under the terms of the Modified BSD License. import os +import signal import warnings import pytest @@ -152,6 +153,32 @@ async def test_connect_request(kernel): await kernel.connect_request(kernel.shell_stream, "foo", {}) +async def test_usage_request_without_psutil(kernel, monkeypatch): + import ipykernel.kernelbase as kernelbase + + monkeypatch.setattr(kernelbase, "psutil", None) + reply = await kernel.test_control_message("usage_request", {}) + content = reply["content"] + + assert reply["header"]["msg_type"] == "usage_reply" + assert content["hostname"] + assert content["pid"] == os.getpid() + assert content["cpu_count"] == os.cpu_count() + assert "host_cpu_percent" not in content + assert "host_virtual_memory" not in content + assert "kernel_memory" not in content + + +async def test_child_process_fallbacks_without_psutil(kernel, monkeypatch): + import ipykernel.kernelbase as kernelbase + + monkeypatch.setattr(kernelbase, "psutil", None) + + assert kernel._process_children() == [] + kernel._signal_children(signal.SIGTERM) + await kernel._progressively_terminate_all_children() + + async def test_send_interrupt_children(kernel): kernel._send_interrupt_children() From bf3509d4edec0d6b56d0e67ffc4613333fc070f8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 17 Jul 2026 13:44:53 +0100 Subject: [PATCH 1192/1195] chore: update pre-commit hooks (#1538) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ian Thomas --- .pre-commit-config.yaml | 4 ++-- ipykernel/kernelbase.py | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e39ba275e..fcc8b96b1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -39,7 +39,7 @@ repos: types_or: [yaml, html, json] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v2.1.0" + rev: "v2.3.0" hooks: - id: mypy files: ipykernel @@ -95,7 +95,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.20 + rev: v0.15.21 hooks: - id: ruff-check types_or: [python, jupyter] diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index 3e9ce0f7c..62e4fa239 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -845,7 +845,7 @@ async def execute_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content else: - warnings.warn( + warnings.warn( # type:ignore[unreachable] _AWAITABLE_MESSAGE.format(func_name="do_execute", target=self.do_execute), PendingDeprecationWarning, stacklevel=1, @@ -907,7 +907,7 @@ async def complete_request(self, stream, ident, parent): if inspect.isawaitable(matches): matches = await matches else: - warnings.warn( + warnings.warn( # type:ignore[unreachable] _AWAITABLE_MESSAGE.format(func_name="do_complete", target=self.do_complete), PendingDeprecationWarning, stacklevel=1, @@ -941,7 +941,7 @@ async def inspect_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content else: - warnings.warn( + warnings.warn( # type:ignore[unreachable] _AWAITABLE_MESSAGE.format(func_name="do_inspect", target=self.do_inspect), PendingDeprecationWarning, stacklevel=1, @@ -966,7 +966,7 @@ async def history_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content else: - warnings.warn( + warnings.warn( # type:ignore[unreachable] _AWAITABLE_MESSAGE.format(func_name="do_history", target=self.do_history), PendingDeprecationWarning, stacklevel=1, @@ -1094,7 +1094,7 @@ async def shutdown_request(self, stream, ident, parent): if inspect.isawaitable(content): content = await content else: - warnings.warn( + warnings.warn( # type:ignore[unreachable] _AWAITABLE_MESSAGE.format(func_name="do_shutdown", target=self.do_shutdown), PendingDeprecationWarning, stacklevel=1, @@ -1133,7 +1133,7 @@ async def is_complete_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content else: - warnings.warn( + warnings.warn( # type:ignore[unreachable] _AWAITABLE_MESSAGE.format(func_name="do_is_complete", target=self.do_is_complete), PendingDeprecationWarning, stacklevel=1, @@ -1155,7 +1155,7 @@ async def debug_request(self, stream, ident, parent): if inspect.isawaitable(reply_content): reply_content = await reply_content else: - warnings.warn( + warnings.warn( # type:ignore[unreachable] _AWAITABLE_MESSAGE.format( func_name="do_debug_request", target=self.do_debug_request ), From 09c4871501423383c9dee0085d44e1ae1acc3f63 Mon Sep 17 00:00:00 2001 From: NewUserHa <32261870+NewUserHa@users.noreply.github.com> Date: Sat, 18 Jul 2026 01:14:22 +0800 Subject: [PATCH 1193/1195] Enable ProactorEventLoop on windows for ipykernel (#1469) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: BoykoNeov Co-authored-by: Claude Opus 4.8 --- ipykernel/iostream.py | 5 +++-- ipykernel/kernelapp.py | 38 -------------------------------------- ipykernel/thread.py | 26 +++++++++++++++++++++++++- ipykernel/zmqshell.py | 8 ++++++++ tests/conftest.py | 6 ------ tests/test_async.py | 2 +- 6 files changed, 37 insertions(+), 48 deletions(-) diff --git a/ipykernel/iostream.py b/ipykernel/iostream.py index 2c1094f35..a5b5b6c0b 100644 --- a/ipykernel/iostream.py +++ b/ipykernel/iostream.py @@ -22,9 +22,10 @@ import zmq from jupyter_client.session import extract_header -from tornado.ioloop import IOLoop from zmq.eventloop.zmqstream import ZMQStream +from .thread import make_selector_io_loop + # ----------------------------------------------------------------------------- # Globals # ----------------------------------------------------------------------------- @@ -67,7 +68,7 @@ def __init__(self, socket, pipe=False, session=False): self.background_socket = BackgroundSocket(self) self._master_pid = os.getpid() self._pipe_flag = pipe - self.io_loop = IOLoop(make_current=False) + self.io_loop = make_selector_io_loop() if pipe: self._setup_pipe_in() self._local = threading.local() diff --git a/ipykernel/kernelapp.py b/ipykernel/kernelapp.py index 9413b86d6..b6e8b59a1 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -692,43 +692,6 @@ def configure_tornado_logger(self): handler.setFormatter(formatter) logger.addHandler(handler) - def _init_asyncio_patch(self): - """set default asyncio policy to be compatible with tornado - - Tornado 6 (at least) is not compatible with the default - asyncio implementation on Windows - - Pick the older SelectorEventLoopPolicy on Windows - if the known-incompatible default policy is in use. - - Support for Proactor via a background thread is available in tornado 6.1, - but it is still preferable to run the Selector in the main thread - instead of the background. - - do this as early as possible to make it a low priority and overridable - - ref: https://github.com/tornadoweb/tornado/issues/2608 - - FIXME: if/when tornado supports the defaults in asyncio without threads, - remove and bump tornado requirement for py38. - Most likely, this will mean a new Python version - where asyncio.ProactorEventLoop supports add_reader and friends. - - """ - if sys.platform.startswith("win"): - import asyncio - - try: - from asyncio import WindowsProactorEventLoopPolicy, WindowsSelectorEventLoopPolicy - except ImportError: - pass - # not affected - else: - if type(asyncio.get_event_loop_policy()) is WindowsProactorEventLoopPolicy: - # WindowsProactorEventLoopPolicy is not compatible with tornado 6 - # fallback to the pre-3.8 default of Selector - asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy()) - def init_pdb(self): """Replace pdb with IPython's version that is interruptible. @@ -748,7 +711,6 @@ def init_pdb(self): @catch_config_error def initialize(self, argv=None): """Initialize the application.""" - self._init_asyncio_patch() super().initialize(argv) if self.subapp is not None: return diff --git a/ipykernel/thread.py b/ipykernel/thread.py index 3e1d4f07a..283ba9175 100644 --- a/ipykernel/thread.py +++ b/ipykernel/thread.py @@ -1,5 +1,7 @@ """Base class for threads.""" +import asyncio +import sys from threading import Thread from tornado.ioloop import IOLoop @@ -8,13 +10,35 @@ SHELL_CHANNEL_THREAD_NAME = "Shell channel" +def make_selector_io_loop() -> IOLoop: + """Create a non-current tornado ``IOLoop`` for an ipykernel service thread. + + ipykernel runs its service channels -- control, IOPub, the shell channel and + subshells -- on dedicated event loops in background threads. The process-wide + asyncio loop on Windows is a ``ProactorEventLoop`` (so the main user-code loop + can spawn asyncio subprocesses, see #1468/#1469), and Proactor has no native + ``add_reader``. Tornado therefore drives a Proactor loop's zmq sockets through + a helper "Tornado selector" thread. When a debugger suspends every thread at a + breakpoint on Python >= 3.12 (``sys.monitoring``), that un-exempt helper thread + freezes mid-wake and deadlocks the control/debug read path (#1469). + + These service loops never need Proactor's subprocess support, so we keep them + on a ``SelectorEventLoop``: it implements ``add_reader`` natively and needs no + helper thread. Only the main/user-code loop stays on Proactor. On non-Windows + platforms the default loop is already selector-based, so this is a no-op there. + """ + if sys.platform == "win32": + return IOLoop(make_current=False, asyncio_loop=asyncio.SelectorEventLoop()) # type: ignore[no-any-return] + return IOLoop(make_current=False) # type: ignore[no-any-return] + + class BaseThread(Thread): """Base class for threads.""" def __init__(self, **kwargs): """Initialize the thread.""" super().__init__(**kwargs) - self.io_loop = IOLoop(make_current=False) + self.io_loop = make_selector_io_loop() self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index a92bf7e49..67395e4e9 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -518,6 +518,14 @@ class ZMQInteractiveShell(InteractiveShell): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) + # Suppress Trio's signal handling warning on Windows with ProactorEventLoop + # This occurs when Trio is imported and finds signal handling already taken by Proactor + warnings.filterwarnings( + "ignore", + message=".*Trio's signal handling code might have collided.*", + category=RuntimeWarning, + ) + # tqdm has an incorrect detection of ZMQInteractiveShell when launch via # a scheduler that bypass IPKernelApp Think of JupyterHub cluster # spawners and co. as of end of Feb 2025, the maintainer has been diff --git a/tests/conftest.py b/tests/conftest.py index ad6c5830a..25befba35 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,5 @@ import asyncio import logging -import os from typing import no_type_check from unittest.mock import MagicMock @@ -38,11 +37,6 @@ resource.setrlimit(resource.RLIMIT_NOFILE, (soft, hard)) -# Enforce selector event loop on Windows. -if os.name == "nt": - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) # type:ignore - - class KernelMixin: log = logging.getLogger() diff --git a/tests/test_async.py b/tests/test_async.py index 4e9b9ad46..2c5b21ea9 100644 --- a/tests/test_async.py +++ b/tests/test_async.py @@ -35,7 +35,7 @@ def test_async_interrupt(asynclib, request): __import__(asynclib) except ImportError: pytest.skip("Requires %s" % asynclib) - request.addfinalizer(lambda: execute("%autoawait asyncio", KC)) + request.addfinalizer(lambda: execute("%autoawait " + asynclib, KC)) flush_channels(KC) msg_id, content = execute("%autoawait " + asynclib, KC) From e158bf06daaf7632f0dd494f0053a5ecd76a6168 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:18:43 +0100 Subject: [PATCH 1194/1195] Bump actions/setup-python from 6 to 7 in the actions group (#1540) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3774b4a84..e2713ec11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: - name: Checkout uses: actions/checkout@v7 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} @@ -181,7 +181,7 @@ jobs: steps: - uses: actions/checkout@v7 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: "3.10" From 342cf58bb104b6ba69d3e1321ca06a819b966a82 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 09:28:34 +0100 Subject: [PATCH 1195/1195] chore: update pre-commit hooks (#1539) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Ian Thomas --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fcc8b96b1..9aa4daacc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -82,7 +82,7 @@ repos: additional_dependencies: [black==23.7.0] - repo: https://github.com/codespell-project/codespell - rev: "v2.4.2" + rev: "v2.4.3" hooks: - id: codespell args: ["-L", "sur,nd"] @@ -95,7 +95,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.15.21 + rev: v0.15.22 hooks: - id: ruff-check types_or: [python, jupyter]