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 2023-06-04 00:12:32 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-ipykernel (Old) and /work/SRC/openSUSE:Factory/.python-ipykernel.new.15902 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-ipykernel" Sun Jun 4 00:12:32 2023 rev:37 rq:1090502 version:6.23.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-ipykernel/python-ipykernel.changes 2023-04-04 21:27:51.331725423 +0200 +++ /work/SRC/openSUSE:Factory/.python-ipykernel.new.15902/python-ipykernel.changes 2023-06-04 00:12:42.273570604 +0200 @@ -1,0 +2,11 @@ +Fri Jun 2 12:42:05 UTC 2023 - Ben Greiner <c...@bnavigator.de> + +- Update to 6.23.1 + * Avoid echoing onto a captured FD #1111 (@minrk) +- Release 6.23.0 + * Support control<>iopub messages to e.g. unblock comm_msg from + command execution #1114 (@tkrabel-db) + * Add outstream hook similar to display publisher #1110 + (@maartenbreddels) + +------------------------------------------------------------------- Old: ---- ipykernel-6.22.0.tar.gz New: ---- ipykernel-6.23.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-ipykernel.spec ++++++ --- /var/tmp/diff_new_pack.RJ7VWM/_old 2023-06-04 00:12:42.933574550 +0200 +++ /var/tmp/diff_new_pack.RJ7VWM/_new 2023-06-04 00:12:42.937574573 +0200 @@ -17,7 +17,7 @@ Name: python-ipykernel -Version: 6.22.0 +Version: 6.23.1 Release: 0 Summary: IPython Kernel for Jupyter License: BSD-3-Clause ++++++ ipykernel-6.22.0.tar.gz -> ipykernel-6.23.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/.github/workflows/ci.yml new/ipykernel-6.23.1/.github/workflows/ci.yml --- old/ipykernel-6.22.0/.github/workflows/ci.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/.github/workflows/ci.yml 2020-02-02 01:00:00.000000000 +0100 @@ -57,17 +57,24 @@ 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 @@ -143,6 +150,11 @@ 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 @@ -190,7 +202,7 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/.pre-commit-config.yaml new/ipykernel-6.23.1/.pre-commit-config.yaml --- old/ipykernel-6.22.0/.pre-commit-config.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/.pre-commit-config.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -21,7 +21,7 @@ - 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 @@ - 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.263 hooks: - id: ruff args: ["--fix"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/.readthedocs.yaml new/ipykernel-6.23.1/.readthedocs.yaml --- old/ipykernel-6.22.0/.readthedocs.yaml 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/.readthedocs.yaml 2020-02-02 01:00:00.000000000 +0100 @@ -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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/CHANGELOG.md new/ipykernel-6.23.1/CHANGELOG.md --- old/ipykernel-6.22.0/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/CHANGELOG.md 2020-02-02 01:00:00.000000000 +0100 @@ -2,6 +2,45 @@ <!-- <START NEW CHANGELOG ENTRY> --> +## 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) + +<!-- <END NEW CHANGELOG ENTRY> --> + +## 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 +57,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) -<!-- <END NEW CHANGELOG ENTRY> --> - ## 6.21.3 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.21.2...e46f75b93c388886f4b6ba32182e29c3cc486984)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/PKG-INFO new/ipykernel-6.23.1/PKG-INFO --- old/ipykernel-6.22.0/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/PKG-INFO 2020-02-02 01:00:00.000000000 +0100 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: ipykernel -Version: 6.22.0 +Version: 6.23.1 Summary: IPython Kernel for Jupyter Project-URL: Homepage, https://ipython.org Author-email: IPython Development Team <ipython-...@scipy.org> @@ -91,7 +91,6 @@ # IPython Kernel for Jupyter [](https://github.com/ipython/ipykernel/actions/workflows/ci.yml/badge.svg?query=branch%3Amain++) -[](https://codecov.io/gh/ipython/ipykernel) [](http://ipykernel.readthedocs.io/en/latest/?badge=latest) This package provides the IPython kernel for Jupyter. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/README.md new/ipykernel-6.23.1/README.md --- old/ipykernel-6.22.0/README.md 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/README.md 2020-02-02 01:00:00.000000000 +0100 @@ -1,7 +1,6 @@ # IPython Kernel for Jupyter [](https://github.com/ipython/ipykernel/actions/workflows/ci.yml/badge.svg?query=branch%3Amain++) -[](https://codecov.io/gh/ipython/ipykernel) [](http://ipykernel.readthedocs.io/en/latest/?badge=latest) This package provides the IPython kernel for Jupyter. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/codecov.yml new/ipykernel-6.23.1/codecov.yml --- old/ipykernel-6.22.0/codecov.yml 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/codecov.yml 1970-01-01 01:00:00.000000000 +0100 @@ -1,9 +0,0 @@ -coverage: - status: - project: - default: - target: auto - threshold: 1 - patch: - default: - target: 0% diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/_version.py new/ipykernel-6.23.1/ipykernel/_version.py --- old/ipykernel-6.22.0/ipykernel/_version.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/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.22.0" +__version__ = "6.23.1" # Build up version_info tuple for backwards compatibility pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)" @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/comm/comm.py new/ipykernel-6.23.1/ipykernel/comm/comm.py --- old/ipykernel-6.22.0/ipykernel/comm/comm.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/comm/comm.py 2020-02-02 01:00:00.000000000 +0100 @@ -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 @@ 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 @@ 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, ) @@ -80,6 +87,7 @@ "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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/connect.py new/ipykernel-6.23.1/ipykernel/connect.py --- old/ipykernel-6.22.0/ipykernel/connect.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/connect.py 2020-02-02 01:00:00.000000000 +0100 @@ -117,7 +117,7 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/control.py new/ipykernel-6.23.1/ipykernel/control.py --- old/ipykernel-6.22.0/ipykernel/control.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/control.py 2020-02-02 01:00:00.000000000 +0100 @@ -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: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/gui/gtk3embed.py new/ipykernel-6.23.1/ipykernel/gui/gtk3embed.py --- old/ipykernel-6.22.0/ipykernel/gui/gtk3embed.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/gui/gtk3embed.py 2020-02-02 01:00:00.000000000 +0100 @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/gui/gtkembed.py new/ipykernel-6.23.1/ipykernel/gui/gtkembed.py --- old/ipykernel-6.22.0/ipykernel/gui/gtkembed.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/gui/gtkembed.py 2020-02-02 01:00:00.000000000 +0100 @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/iostream.py new/ipykernel-6.23.1/ipykernel/iostream.py --- old/ipykernel-6.22.0/ipykernel/iostream.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/iostream.py 2020-02-02 01:00:00.000000000 +0100 @@ -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 @@ -134,7 +135,8 @@ 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() @@ -362,7 +364,7 @@ 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 @@ -402,22 +404,43 @@ self.echo = None self._isatty = bool(isatty) self._should_watch = False + self._local = local() 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): @@ -430,7 +453,7 @@ 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) @@ -452,7 +475,13 @@ """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) @@ -524,11 +553,19 @@ # 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, ) @@ -600,3 +637,49 @@ 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/ipkernel.py new/ipykernel-6.23.1/ipykernel/ipkernel.py --- old/ipykernel-6.22.0/ipykernel/ipkernel.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/ipkernel.py 2020-02-02 01:00:00.000000000 +0100 @@ -704,5 +704,6 @@ warnings.warn( "Kernel is a deprecated alias of ipykernel.ipkernel.IPythonKernel", DeprecationWarning, + stacklevel=2, ) super().__init__(*args, **kwargs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/kernelspec.py new/ipykernel-6.23.1/ipykernel/kernelspec.py --- old/ipykernel-6.22.0/ipykernel/kernelspec.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/kernelspec.py 2020-02-02 01:00:00.000000000 +0100 @@ -227,7 +227,7 @@ ) 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/parentpoller.py new/ipykernel-6.23.1/ipykernel/parentpoller.py --- old/ipykernel-6.22.0/ipykernel/parentpoller.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/parentpoller.py 2020-02-02 01:00:00.000000000 +0100 @@ -115,6 +115,7 @@ """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-...@scipy.org""" + ipython-...@scipy.org""", + stacklevel=2, ) return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/pickleutil.py new/ipykernel-6.23.1/ipykernel/pickleutil.py --- old/ipykernel-6.22.0/ipykernel/pickleutil.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/pickleutil.py 2020-02-02 01:00:00.000000000 +0100 @@ -300,7 +300,7 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/pylab/backend_inline.py new/ipykernel-6.23.1/ipykernel/pylab/backend_inline.py --- old/ipykernel-6.22.0/ipykernel/pylab/backend_inline.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/pylab/backend_inline.py 2020-02-02 01:00:00.000000000 +0100 @@ -11,4 +11,5 @@ "`ipykernel.pylab.backend_inline` is deprecated, directly " "use `matplotlib_inline.backend_inline`", DeprecationWarning, + stacklevel=2, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/pylab/config.py new/ipykernel-6.23.1/ipykernel/pylab/config.py --- old/ipykernel-6.22.0/ipykernel/pylab/config.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/pylab/config.py 2020-02-02 01:00:00.000000000 +0100 @@ -10,4 +10,5 @@ warnings.warn( "`ipykernel.pylab.config` is deprecated, directly use `matplotlib_inline.config`", DeprecationWarning, + stacklevel=2, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/serialize.py new/ipykernel-6.23.1/ipykernel/serialize.py --- old/ipykernel-6.22.0/ipykernel/serialize.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/serialize.py 2020-02-02 01:00:00.000000000 +0100 @@ -122,7 +122,7 @@ """ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/tests/test_io.py new/ipykernel-6.23.1/ipykernel/tests/test_io.py --- old/ipykernel-6.22.0/ipykernel/tests/test_io.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/tests/test_io.py 2020-02-02 01:00:00.000000000 +0100 @@ -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 @@ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/tests/test_pickleutil.py new/ipykernel-6.23.1/ipykernel/tests/test_pickleutil.py --- old/ipykernel-6.22.0/ipykernel/tests/test_pickleutil.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/tests/test_pickleutil.py 2020-02-02 01:00:00.000000000 +0100 @@ -16,7 +16,7 @@ def loads(obj): - return uncan(pickle.loads(obj)) + return uncan(pickle.loads(obj)) # noqa def test_no_closure(): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/trio_runner.py new/ipykernel-6.23.1/ipykernel/trio_runner.py --- old/ipykernel-6.22.0/ipykernel/trio_runner.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/trio_runner.py 2020-02-02 01:00:00.000000000 +0100 @@ -22,7 +22,7 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel/zmqshell.py new/ipykernel-6.23.1/ipykernel/zmqshell.py --- old/ipykernel-6.22.0/ipykernel/zmqshell.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel/zmqshell.py 2020-02-02 01:00:00.000000000 +0100 @@ -363,7 +363,7 @@ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/ipykernel_launcher.py new/ipykernel-6.23.1/ipykernel_launcher.py --- old/ipykernel-6.22.0/ipykernel_launcher.py 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/ipykernel_launcher.py 2020-02-02 01:00:00.000000000 +0100 @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/ipykernel-6.22.0/pyproject.toml new/ipykernel-6.23.1/pyproject.toml --- old/ipykernel-6.22.0/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 +++ new/ipykernel-6.23.1/pyproject.toml 2020-02-02 01:00:00.000000000 +0100 @@ -91,6 +91,7 @@ [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}" @@ -116,7 +117,7 @@ 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.263"] detached = true [tool.hatch.envs.lint.scripts] style = [ @@ -191,6 +192,8 @@ ] [tool.coverage.run] +relative_files = true +source = ["ipykernel"] omit = [ "ipykernel/tests/*", "ipykernel/datapub.py", @@ -296,7 +299,11 @@ # 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. +# 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", "S603"] [tool.interrogate] ignore-init-module=true