Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-ipykernel for openSUSE:Factory checked in at 2024-01-21 23:07:29 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-ipykernel (Old) and /work/SRC/openSUSE:Factory/.python-ipykernel.new.16006 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-ipykernel" Sun Jan 21 23:07:29 2024 rev:45 rq:1140287 version:6.29.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-ipykernel/python-ipykernel.changes 2023-12-21 23:38:47.074912191 +0100 +++ /work/SRC/openSUSE:Factory/.python-ipykernel.new.16006/python-ipykernel.changes 2024-01-21 23:07:40.108001142 +0100 @@ -1,0 +2,17 @@ +Sun Jan 21 10:45:42 UTC 2024 - Ben Greiner <c...@bnavigator.de> + +- Update to 6.29.0 + * Always set debugger to true in kernelspec #1191 (@ianthomas23) + * Revert "Enable ProactorEventLoop on windows for ipykernel" + #1194 (@blink1073) + * Make outputs go to correct cell when generated in + threads/asyncio #1186 (@krassowski) + * Pin pytest-asyncio to 0.23.2 #1189 (@ianthomas23) +- Update to 6.28.0 + * Enable ProactorEventLoop on windows for ipykernel #1184 + (@NewUserHa) + * Adds a flag in debug_info for the copyToGlobals support #1099 + (@brichet) + * Support python 3.12 #1185 (@blink1073) + +------------------------------------------------------------------- Old: ---- ipykernel-6.27.1.tar.gz New: ---- ipykernel-6.29.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-ipykernel.spec ++++++ --- /var/tmp/diff_new_pack.TDT0gs/_old 2024-01-21 23:07:40.676021846 +0100 +++ /var/tmp/diff_new_pack.TDT0gs/_new 2024-01-21 23:07:40.676021846 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-ipykernel # -# Copyright (c) 2023 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-ipykernel -Version: 6.27.1 +Version: 6.29.0 Release: 0 Summary: IPython Kernel for Jupyter License: BSD-3-Clause @@ -51,7 +51,7 @@ BuildRequires: %{python_module nest-asyncio} BuildRequires: %{python_module packaging} BuildRequires: %{python_module psutil} -BuildRequires: %{python_module pyzmq >= 20} +BuildRequires: %{python_module pyzmq >= 24} BuildRequires: %{python_module tornado >= 6.1} BuildRequires: %{python_module traitlets >= 5.1.0} BuildRequires: %{python_module jupyter-core >= 5.1 or (%python-jupyter-core >= 4.12 with %python-jupyter-core < 5.0)} @@ -64,7 +64,7 @@ Requires: python-nest-asyncio Requires: python-packaging Requires: python-psutil -Requires: python-pyzmq >= 20 +Requires: python-pyzmq >= 24 Requires: python-tornado >= 6.1 Requires: python-traitlets >= 5.4.0 Requires: (python-jupyter-core >= 5.1 or (python-jupyter-core >= 4.12 with python-jupyter-core < 5.0)) @@ -90,6 +90,7 @@ %prep %autosetup -p1 -n ipykernel-%{version} sed -i -e 's/, "--color=yes"//' pyproject.toml +sed -i -e '/ignore:.* current event loop:DeprecationWarning/ a \ "ignore:pytest-asyncio detected an unclosed event loop:DeprecationWarning",' pyproject.toml %build %pyproject_wheel ++++++ ipykernel-6.27.1.tar.gz -> ipykernel-6.29.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/.github/dependabot.yml new/ipykernel-6.29.0/.github/dependabot.yml --- old/ipykernel-6.27.1/.github/dependabot.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/.github/dependabot.yml 2020-02-02 01:00:00.000000000 +0100 @@ -4,7 +4,15 @@ directory: "/" schedule: interval: "weekly" + groups: + actions: + patterns: + - "*" - package-ecosystem: "pip" directory: "/" schedule: interval: "weekly" + groups: + actions: + patterns: + - "*" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/.github/workflows/ci.yml new/ipykernel-6.29.0/.github/workflows/ci.yml --- old/ipykernel-6.27.1/.github/workflows/ci.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/.github/workflows/ci.yml 2020-02-02 01:00:00.000000000 +0100 @@ -22,16 +22,16 @@ 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 @@ - 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/.github/workflows/downstream.yml new/ipykernel-6.29.0/.github/workflows/downstream.yml --- old/ipykernel-6.27.1/.github/workflows/downstream.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/.github/workflows/downstream.yml 2020-02-02 01:00:00.000000000 +0100 @@ -92,7 +92,7 @@ - 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 @@ - 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" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/.pre-commit-config.yaml new/ipykernel-6.29.0/.pre-commit-config.yaml --- old/ipykernel-6.27.1/.pre-commit-config.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/.pre-commit-config.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -22,7 +22,7 @@ - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.1 + rev: 0.27.3 hooks: - id: check-github-workflows @@ -34,13 +34,13 @@ [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.0" + rev: "v1.8.0" hooks: - id: mypy files: ipykernel @@ -74,7 +74,7 @@ - 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 @@ 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]"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/CHANGELOG.md new/ipykernel-6.29.0/CHANGELOG.md --- old/ipykernel-6.27.1/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100 @@ -2,6 +2,58 @@ <!-- <START NEW CHANGELOG ENTRY> --> +## 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) + +<!-- <END NEW CHANGELOG ENTRY> --> + +## 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 +68,6 @@ [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2023-11-21..2023-11-27&type=Issues) -<!-- <END NEW CHANGELOG ENTRY> --> - ## 6.27.0 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.26.0...465d34483103d23f471a4795fe5fabb9cf7ac3f5)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/PKG-INFO new/ipykernel-6.29.0/PKG-INFO --- old/ipykernel-6.27.1/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ipykernel -Version: 6.27.1 +Version: 6.29.0 Summary: IPython Kernel for Jupyter Project-URL: Homepage, https://ipython.org Author-email: IPython Development Team <ipython-...@scipy.org> @@ -42,10 +42,6 @@ Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 Requires-Python: >=3.8 Requires-Dist: appnope; platform_system == 'Darwin' Requires-Dist: comm>=0.1.1 @@ -57,7 +53,7 @@ Requires-Dist: nest-asyncio Requires-Dist: packaging Requires-Dist: psutil -Requires-Dist: pyzmq>=20 +Requires-Dist: pyzmq>=24 Requires-Dist: tornado>=6.1 Requires-Dist: traitlets>=5.4.0 Provides-Extra: cov @@ -82,7 +78,7 @@ Requires-Dist: flaky; extra == 'test' Requires-Dist: ipyparallel; extra == 'test' Requires-Dist: pre-commit; extra == 'test' -Requires-Dist: pytest-asyncio; extra == 'test' +Requires-Dist: pytest-asyncio==0.23.2; extra == 'test' Requires-Dist: pytest-cov; extra == 'test' Requires-Dist: pytest-timeout; extra == 'test' Requires-Dist: pytest>=7.0; extra == 'test' @@ -110,7 +106,7 @@ and then from the root directory ```bash -pytest ipykernel +pytest ``` ## Running tests with coverage @@ -120,7 +116,7 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/README.md new/ipykernel-6.29.0/README.md --- old/ipykernel-6.27.1/README.md 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/README.md 2020-02-02 01:00:00.000000000 +0100 @@ -20,7 +20,7 @@ and then from the root directory ```bash -pytest ipykernel +pytest ``` ## Running tests with coverage @@ -30,7 +30,7 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/ipykernel/_version.py new/ipykernel-6.29.0/ipykernel/_version.py --- old/ipykernel-6.27.1/ipykernel/_version.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/ipykernel/_version.py 2020-02-02 01:00:00.000000000 +0100 @@ -5,7 +5,7 @@ from typing import List # Version string must appear intact for hatch versioning -__version__ = "6.27.1" +__version__ = "6.29.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/ipykernel/debugger.py new/ipykernel-6.29.0/ipykernel/debugger.py --- old/ipykernel-6.27.1/ipykernel/debugger.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/ipykernel/debugger.py 2020-02-02 01:00:00.000000000 +0100 @@ -605,6 +605,7 @@ "stoppedThreads": list(self.stopped_threads), "richRendering": True, "exceptionPaths": ["Python Exceptions"], + "copyToGlobals": True, }, } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/ipykernel/iostream.py new/ipykernel-6.29.0/ipykernel/iostream.py --- old/ipykernel-6.27.1/ipykernel/iostream.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/ipykernel/iostream.py 2020-02-02 01:00:00.000000000 +0100 @@ -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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ """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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/ipykernel/ipkernel.py new/ipykernel-6.29.0/ipykernel/ipkernel.py --- old/ipykernel-6.27.1/ipykernel/ipkernel.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/ipykernel/ipkernel.py 2020-02-02 01:00:00.000000000 +0100 @@ -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 @@ 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 @@ # 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/ipykernel/kernelbase.py new/ipykernel-6.29.0/ipykernel/kernelbase.py --- old/ipykernel-6.27.1/ipykernel/kernelbase.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/ipykernel/kernelbase.py 2020-02-02 01:00:00.000000000 +0100 @@ -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 __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: @@ -283,6 +291,11 @@ 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 +737,8 @@ 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 +754,6 @@ 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 +763,9 @@ "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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/ipykernel/kernelspec.py new/ipykernel-6.29.0/ipykernel/kernelspec.py --- old/ipykernel-6.27.1/ipykernel/kernelspec.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/ipykernel/kernelspec.py 2020-02-02 01:00:00.000000000 +0100 @@ -66,7 +66,7 @@ "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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/pyproject.toml new/ipykernel-6.29.0/pyproject.toml --- old/ipykernel-6.27.1/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 @@ -17,10 +17,6 @@ "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 @@ "tornado>=6.1", "matplotlib-inline>=0.1", 'appnope;platform_system=="Darwin"', - "pyzmq>=20", + "pyzmq>=24", "psutil", "packaging", ] @@ -57,7 +53,7 @@ "flaky", "ipyparallel", "pre-commit", - "pytest-asyncio", + "pytest-asyncio==0.23.2", "pytest-timeout" ] cov = [ @@ -175,6 +171,9 @@ "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] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/tests/test_debugger.py new/ipykernel-6.29.0/tests/test_debugger.py --- old/ipykernel-6.27.1/tests/test_debugger.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/tests/test_debugger.py 2020-02-02 01:00:00.000000000 +0100 @@ -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 @@ "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 @@ 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 @@ "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 @@ 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 @@ 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 @@ 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 @@ 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 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 @@ {"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 @@ 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 @@ 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 @@ # 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 @@ # 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": diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.27.1/tests/test_kernel.py new/ipykernel-6.29.0/tests/test_kernel.py --- old/ipykernel-6.27.1/tests/test_kernel.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.29.0/tests/test_kernel.py 2020-02-02 01:00:00.000000000 +0100 @@ -58,6 +58,106 @@ _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"""