diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 233aabbd..70b3bf40 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.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 @@ -150,6 +150,7 @@ jobs: uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 with: dependency_type: minimum + python_version: "3.9" - name: List installed packages run: | diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 00000000..499f4356 --- /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/CHANGELOG.md b/CHANGELOG.md index a4a8d0a0..09342c32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,53 @@ +## 7.0.0a0 + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.3...314cc49da6e7d69d74f4741d4ea6568e926d1819)) + +### Enhancements made + +- 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)) +- Remove control queue [#1210](https://github.com/ipython/ipykernel/pull/1210) ([@ianthomas23](https://github.com/ianthomas23)) +- Replace Tornado with AnyIO [#1079](https://github.com/ipython/ipykernel/pull/1079) ([@davidbrochart](https://github.com/davidbrochart)) + +### Bugs fixed + +- Fix eventloop integration with anyio [#1265](https://github.com/ipython/ipykernel/pull/1265) ([@ianthomas23](https://github.com/ianthomas23)) +- Explicitly close memory object streams [#1253](https://github.com/ipython/ipykernel/pull/1253) ([@ianthomas23](https://github.com/ianthomas23)) +- 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)) +- Correctly handle with_cell_id in async do_execute [#1227](https://github.com/ipython/ipykernel/pull/1227) ([@ianthomas23](https://github.com/ianthomas23)) +- Do not import debugger/debugpy unless needed [#1223](https://github.com/ipython/ipykernel/pull/1223) ([@krassowski](https://github.com/krassowski)) +- Allow datetime or str in test_sequential_control_messages [#1219](https://github.com/ipython/ipykernel/pull/1219) ([@ianthomas23](https://github.com/ianthomas23)) +- Fix side effect import for pickleutil [#1217](https://github.com/ipython/ipykernel/pull/1217) ([@blink1073](https://github.com/blink1073)) + +### Maintenance and upkeep improvements + +- 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)) +- 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)) +- Update version to 7.0.0 [#1241](https://github.com/ipython/ipykernel/pull/1241) ([@mlucool](https://github.com/mlucool)) +- 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)) +- Update Release Scripts [#1221](https://github.com/ipython/ipykernel/pull/1221) ([@blink1073](https://github.com/blink1073)) + +### Documentation improvements + +- 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)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-02-26&to=2024-10-22&type=c)) + +[@agronholm](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aagronholm+updated%3A2024-02-26..2024-10-22&type=Issues) | [@blink1073](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ablink1073+updated%3A2024-02-26..2024-10-22&type=Issues) | [@bluss](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abluss+updated%3A2024-02-26..2024-10-22&type=Issues) | [@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2024-02-26..2024-10-22&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-02-26..2024-10-22&type=Issues) | [@gregory-shklover](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Agregory-shklover+updated%3A2024-02-26..2024-10-22&type=Issues) | [@hroncok](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ahroncok+updated%3A2024-02-26..2024-10-22&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-02-26..2024-10-22&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2024-02-26..2024-10-22&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-02-26..2024-10-22&type=Issues) | [@maartenbreddels](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amaartenbreddels+updated%3A2024-02-26..2024-10-22&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-02-26..2024-10-22&type=Issues) | [@mlucool](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amlucool+updated%3A2024-02-26..2024-10-22&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-02-26..2024-10-22&type=Issues) + + + ## 6.29.5 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.4...1e62d48298e353a9879fae99bc752f9bb48797ef)) @@ -20,8 +67,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)) @@ -67,8 +112,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/docs/conf.py b/docs/conf.py index 4bb59932..38a724b5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,7 @@ import os import shutil from pathlib import Path -from typing import Any, Dict, List +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 @@ -70,7 +70,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 +159,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 +226,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/examples/embedding/inprocess_qtconsole.py b/examples/embedding/inprocess_qtconsole.py index 18ce2863..7a976a31 100644 --- a/examples/embedding/inprocess_qtconsole.py +++ b/examples/embedding/inprocess_qtconsole.py @@ -1,54 +1,24 @@ """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/ipykernel/_version.py b/ipykernel/_version.py index 8a7bc44e..5907d150 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -2,16 +2,15 @@ store the current version info of the server. """ import re -from typing import List # Version string must appear intact for hatch versioning -__version__ = "7.0.0" +__version__ = "7.0.0a0" # Build up version_info tuple for backwards compatibility 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 8680793f..780d1801 100644 --- a/ipykernel/debugger.py +++ b/ipykernel/debugger.py @@ -130,7 +130,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/inprocess/channels.py b/ipykernel/inprocess/channels.py index 378416dc..4c01c5bc 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/ipkernel.py b/ipykernel/ipkernel.py index db83d986..48efa6cd 100644 --- a/ipykernel/ipkernel.py +++ b/ipykernel/ipkernel.py @@ -1,5 +1,7 @@ """The IPython kernel implementation""" +from __future__ import annotations + import builtins import gc import getpass @@ -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 .comm.comm import BaseComm from .comm.manager import CommManager @@ -46,7 +48,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): @@ -84,7 +86,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 @@ -353,7 +359,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 @@ -559,7 +565,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 @@ -755,7 +761,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/kernelapp.py b/ipykernel/kernelapp.py index 394f52a4..55efaa8e 100644 --- a/ipykernel/kernelapp.py +++ b/ipykernel/kernelapp.py @@ -657,7 +657,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 99358f9b..1c623c08 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -274,10 +274,9 @@ async def process_control_message(self, msg=None): assert self.control_thread is None or threading.current_thread() == self.control_thread msg = msg or await self.control_socket.recv_multipart() - copy = not isinstance(msg[0], zmq.Message) - idents, msg = self.session.feed_identities(msg, copy=copy) + idents, msg = self.session.feed_identities(msg) try: - msg = self.session.deserialize(msg, content=True, copy=copy) + msg = self.session.deserialize(msg, content=True) except Exception: self.log.error("Invalid Control Message", exc_info=True) # noqa: G201 return @@ -375,15 +374,12 @@ async def shell_channel_thread_main(self): try: while True: - msg = await self.shell_socket.recv_multipart() - - # Deserialize whole message just to get subshell_id. + msg = await self.shell_socket.recv_multipart(copy=False) + # deserialize only the header to get subshell_id # Keep original message to send to subshell_id unmodified. - # Ideally only want to deserialize message once. - copy = not isinstance(msg[0], zmq.Message) - _, msg2 = self.session.feed_identities(msg, copy=copy) + _, msg2 = self.session.feed_identities(msg, copy=False) try: - msg3 = self.session.deserialize(msg2, content=False, copy=copy) + 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. @@ -412,7 +408,7 @@ async def shell_main(self, subshell_id: str | None): else: assert subshell_id is None assert threading.current_thread() == threading.main_thread() - socket = self.shell_socket + socket = None async with create_task_group() as tg: tg.start_soon(self.process_shell, socket) @@ -1210,9 +1206,7 @@ def do_clear(self): def _topic(self, topic): """prefixed topic for IOPub messages""" - base = "kernel.%s" % self.ident - - return (f"{base}.{topic}").encode() + return (f"kernel.{self.ident}.{topic}").encode() _aborting = Bool(False) diff --git a/ipykernel/pickleutil.py b/ipykernel/pickleutil.py index 6f156594..4ffa5262 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/ipykernel/thread.py b/ipykernel/thread.py index a63011de..40509ece 100644 --- a/ipykernel/thread.py +++ b/ipykernel/thread.py @@ -17,7 +17,7 @@ def __init__(self, **kwargs): self.pydev_do_not_trace = True self.is_pydev_daemon_thread = True self.__stop = Event() - self._tasks_and_args: t.List[t.Tuple[t.Any, t.Any]] = [] + self._tasks_and_args: list[tuple[t.Any, t.Any]] = [] def add_task(self, task: t.Any, *args: t.Any) -> None: # May only add tasks before the thread is started. diff --git a/pyproject.toml b/pyproject.toml index aeaeef46..e1d7b1d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3", ] -requires-python = ">=3.8" +requires-python = ">=3.9" dependencies = [ "debugpy>=1.8.1", "ipython>=7.23.1", @@ -187,6 +187,9 @@ filterwarnings= [ # https://github.com/python-trio/trio/issues/3053 "ignore:The `hash` argument is deprecated in favor of `unsafe_hash` and will be removed in or after August 2025.", + + # ignore unclosed sqlite in traits + "ignore:unclosed database in = (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 +267,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():