diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06cc35349..54a8b8249 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,16 +22,30 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ["3.9", "3.13"] - include: - - os: ubuntu-latest - python-version: "pypy-3.9" - - os: macos-latest - python-version: "3.10" - - os: ubuntu-latest - python-version: "3.11" - - os: ubuntu-latest - python-version: "3.12" + qt: + - qt5 + - qt6 + python-version: + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" + - "3.14" + # 3.14t needs a jupyter-core release + # - "3.14t" + - "pypy-3.11" + exclude: + # qt6 not supported on 3.14 yet + - python-version: "3.14" + qt: qt6 + - python-version: "3.13" + qt: qt5 + - python-version: "3.12" + qt: qt5 + - python-version: "3.11" + qt: qt5 + steps: - name: Checkout uses: actions/checkout@v4 @@ -40,6 +54,11 @@ jobs: with: python-version: ${{ matrix.python-version }} + - name: set qt env + run: | + echo "QT=${{ matrix.qt }}" >> $GITHUB_ENV + shell: bash + - name: Install hatch run: | python --version @@ -49,19 +68,19 @@ jobs: timeout-minutes: 15 if: ${{ !startsWith( matrix.python-version, 'pypy' ) && !startsWith(matrix.os, 'windows') }} run: | - hatch run cov:test --cov-fail-under 50 || hatch run test:test --lf + hatch run cov:test --cov-fail-under 50 - name: Run the tests on pypy timeout-minutes: 15 if: ${{ startsWith( matrix.python-version, 'pypy' ) }} run: | - hatch run test:nowarn || hatch run test:nowarn --lf + hatch run test:nowarn --ignore=tests/test_debugger.py -k "not closure" - name: Run the tests on Windows timeout-minutes: 15 - if: ${{ startsWith(matrix.os, 'windows') }} + if: ${{ !startsWith( matrix.python-version, 'pypy' ) && startsWith(matrix.os, 'windows') }} run: | - hatch run cov:nowarn || hatch run test:nowarn --lf + hatch run cov:nowarn - name: Check Launcher run: | @@ -144,7 +163,7 @@ jobs: - name: Run the tests timeout-minutes: 15 - run: pytest -W default -vv || pytest --vv -W default --lf + run: pytest -W default -vv test_miniumum_versions: name: Test Minimum Versions @@ -164,7 +183,7 @@ jobs: - name: Run the unit tests run: | - hatch -v run test:nowarn || hatch run test:nowarn --lf + hatch -v run test:nowarn test_prereleases: name: Test Prereleases @@ -179,7 +198,7 @@ jobs: dependency_type: pre - name: Run the tests run: | - hatch run test:nowarn || hatch run test:nowarn --lf + hatch run test:nowarn make_sdist: name: Make SDist diff --git a/.github/workflows/downstream.yml b/.github/workflows/downstream.yml index b47c0f06d..032ba0cff 100644 --- a/.github/workflows/downstream.yml +++ b/.github/workflows/downstream.yml @@ -147,20 +147,3 @@ jobs: run: | cd ${GITHUB_WORKSPACE}/../spyder-kernels xvfb-run --auto-servernum ${pythonLocation}/bin/python -m pytest -x -vv -s --full-trace --color=yes spyder_kernels - - downstream_check: # This job does nothing and is only used for the branch protection - if: always() - needs: - - nbclient - - ipywidgets - - jupyter_client - - ipyparallel - - jupyter_kernel_test - - spyder_kernels - - qtconsole - runs-on: ubuntu-latest - steps: - - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 - with: - jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/publish-changelog.yml b/.github/workflows/publish-changelog.yml index 60af4c5f1..c576a5487 100644 --- a/.github/workflows/publish-changelog.yml +++ b/.github/workflows/publish-changelog.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - uses: actions/create-github-app-token@v1 + - uses: actions/create-github-app-token@v2 id: app-token with: app-id: ${{ vars.APP_ID }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index 5295e776b..f6743a9bf 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -21,7 +21,7 @@ jobs: steps: - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - uses: actions/create-github-app-token@v1 + - uses: actions/create-github-app-token@v2 id: app-token with: app-id: ${{ vars.APP_ID }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cc2cfd9d8..e2ae0f758 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: check-case-conflict - id: check-ast @@ -22,12 +22,12 @@ repos: - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.4 + rev: 0.33.2 hooks: - id: check-github-workflows - repo: https://github.com/executablebooks/mdformat - rev: 0.7.17 + rev: 0.7.22 hooks: - id: mdformat additional_dependencies: @@ -55,13 +55,13 @@ repos: ] - repo: https://github.com/adamchainz/blacken-docs - rev: "1.16.0" + rev: "1.19.1" hooks: - id: blacken-docs additional_dependencies: [black==23.7.0] - repo: https://github.com/codespell-project/codespell - rev: "v2.2.6" + rev: "v2.4.1" hooks: - id: codespell args: ["-L", "sur,nd"] @@ -83,7 +83,7 @@ repos: types_or: [python, jupyter] - repo: https://github.com/scientific-python/cookie - rev: "2024.01.24" + rev: "2025.01.22" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 7ab2c8bf0..8e17caad0 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -3,7 +3,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.11" + python: "3.13" sphinx: configuration: docs/conf.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 84767e41b..e811d3409 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,99 @@ +## 6.31.0 + +IPykernel 6.31.0 fixes an issue where display outputs such as Matplotlib plots were not included when using `%notebook` magic to save sessions as `.ipynb` files. This is enabled using the traitlet `ZMQDisplayPublisher.store_display_history` which defaults to the previous behaviour of `False`. This is a minor release rather than a patch release due to the addition of the new traitlet. + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.30.1...01c1e8fe43047050f29d8728eabf4e4de14b624b)) + +### Enhancements made + +- Backport PR #1435: Store display outputs in history for `%notebook` magic [#1461](https://github.com/ipython/ipykernel/pull/1461) ([@Darshan808](https://github.com/Darshan808)) + +### Maintenance and upkeep improvements + +- Backport PR #1453: update tests for 3.14 [#1460](https://github.com/ipython/ipykernel/pull/1460) ([@minrk](https://github.com/minrk)) +- Backport PR #1444: Test on PyPy 3.11 instead of 3.10 [#1459](https://github.com/ipython/ipykernel/pull/1459) ([@cclauss](https://github.com/cclauss)) +- 6.x: Update PEP-639 license values in pyproject.toml [#1446](https://github.com/ipython/ipykernel/pull/1446) ([@bollwyvl](https://github.com/bollwyvl)) +- Backport PR #1411: Replace `@flaky.flaky` decorate with pytest fixture [#1421](https://github.com/ipython/ipykernel/pull/1421) ([@mgorny](https://github.com/mgorny)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2025-08-04&to=2025-10-20&type=c)) + +[@bollwyvl](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Abollwyvl+updated%3A2025-08-04..2025-10-20&type=Issues) | [@cclauss](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Acclauss+updated%3A2025-08-04..2025-10-20&type=Issues) | [@Darshan808](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ADarshan808+updated%3A2025-08-04..2025-10-20&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-08-04..2025-10-20&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2025-08-04..2025-10-20&type=Issues) | [@lumberbot-app](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Alumberbot-app+updated%3A2025-08-04..2025-10-20&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ameeseeksmachine+updated%3A2025-08-04..2025-10-20&type=Issues) | [@mgorny](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Amgorny+updated%3A2025-08-04..2025-10-20&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2025-08-04..2025-10-20&type=Issues) + + + +## 6.30.1 + +This is a bugfix release to fix a significant bug introduced in 6.30.0 that allowed control messages to be handled concurrently rather than sequentially which broke debugging in JupyterLab and VSCode. + +([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.30.0...357c908eab4ae97bb17c5dcabc7ee981df8ecb29)) + +### Bugs fixed + +- Correct use of asyncio.Lock to process a single control message at a time [#1416](https://github.com/ipython/ipykernel/pull/1416) ([@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- Backport: Remove links in changelog to github milestones that no longer exist [#1417](https://github.com/ipython/ipykernel/pull/1417) ([@ianthomas23](https://github.com/ianthomas23)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2025-07-21&to=2025-08-04&type=c)) + +[@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2025-07-21..2025-08-04&type=Issues) + +## 6.30.0 + +This release fixes three bugs but is primarily a maintenance release bringing support for Python 3.13 and updating dependencies. It does not include subshells which will be in the upcoming 7.0.0 release. Users and downstream libraries that wish to avoid subshells should pin to `ipykernel < 7`. + +([Full Changelog](https://github.com/ipython/ipykernel/compare/b1283b144...d9bd546a4dc49a41c3ad5fcc5d4a61f259973182)) + +### Enhancements made + +- Remove control queue [#1210](https://github.com/ipython/ipykernel/pull/1210) ([@ianthomas23](https://github.com/ianthomas23)) + +### Bugs fixed + +- Set shell idle when message skipped by "should_handle" in "dispatch_shell" [#1395](https://github.com/ipython/ipykernel/pull/1395) ([@dby-tmwctw](https://github.com/dby-tmwctw)) +- Fixed error accessing sys.stdout/sys.stderr when those are None [#1247](https://github.com/ipython/ipykernel/pull/1247) ([@gregory-shklover](https://github.com/gregory-shklover)) +- Allow datetime or str in test_sequential_control_messages [#1219](https://github.com/ipython/ipykernel/pull/1219) ([@ianthomas23](https://github.com/ianthomas23)) + +### Maintenance and upkeep improvements + +- 6.x backports [#1406](https://github.com/ipython/ipykernel/pull/1406) ([@ianthomas23](https://github.com/ianthomas23)) +- Update pre-commit and github actions [#1401](https://github.com/ipython/ipykernel/pull/1401) ([@ianthomas23](https://github.com/ianthomas23)) +- Test more python versions on 6.x branch [#1398](https://github.com/ipython/ipykernel/pull/1398) ([@davidbrochart](https://github.com/davidbrochart)) +- Backports and extra changes to fix CI on 6.x branch [#1390](https://github.com/ipython/ipykernel/pull/1390) ([@ianthomas23](https://github.com/ianthomas23)) +- Remove nose import. [#1368](https://github.com/ipython/ipykernel/pull/1368) ([@Carreau](https://github.com/Carreau)) +- Test more python versions [#1358](https://github.com/ipython/ipykernel/pull/1358) ([@davidbrochart](https://github.com/davidbrochart)) +- Fix expected text depending on IPython version. [#1354](https://github.com/ipython/ipykernel/pull/1354) ([@Carreau](https://github.com/Carreau)) +- Licence :: * trove classifiers are deprecated [#1348](https://github.com/ipython/ipykernel/pull/1348) ([@Carreau](https://github.com/Carreau)) +- Try to fix spyder kernel install [#1337](https://github.com/ipython/ipykernel/pull/1337) ([@Carreau](https://github.com/Carreau)) +- Remove test_check job [#1335](https://github.com/ipython/ipykernel/pull/1335) ([@Carreau](https://github.com/Carreau)) +- Don't rerun test with --lf it hides failures. [#1324](https://github.com/ipython/ipykernel/pull/1324) ([@Carreau](https://github.com/Carreau)) +- Remove link to numfocus for funding. [#1320](https://github.com/ipython/ipykernel/pull/1320) ([@Carreau](https://github.com/Carreau)) +- Remove downstream_check [#1318](https://github.com/ipython/ipykernel/pull/1318) ([@Carreau](https://github.com/Carreau)) +- Copy payloadpage.page from IPython [#1317](https://github.com/ipython/ipykernel/pull/1317) ([@Carreau](https://github.com/Carreau)) +- Remove base setup [#1299](https://github.com/ipython/ipykernel/pull/1299) ([@davidbrochart](https://github.com/davidbrochart)) +- Rely on intrsphinx_registry to keep intersphinx up to date. [#1290](https://github.com/ipython/ipykernel/pull/1290) ([@Carreau](https://github.com/Carreau)) +- Drop support for Python 3.8 [#1284](https://github.com/ipython/ipykernel/pull/1284) ([@ianthomas23](https://github.com/ianthomas23)) +- Start testing on 3.13 [#1277](https://github.com/ipython/ipykernel/pull/1277) ([@Carreau](https://github.com/Carreau)) +- Build docs on ubuntu [#1257](https://github.com/ipython/ipykernel/pull/1257) ([@blink1073](https://github.com/blink1073)) +- Avoid a DeprecationWarning on Python 3.13+ [#1248](https://github.com/ipython/ipykernel/pull/1248) ([@hroncok](https://github.com/hroncok)) +- Catch IPython 8.24 DeprecationWarnings [#1242](https://github.com/ipython/ipykernel/pull/1242) ([@s-t-e-v-e-n-k](https://github.com/s-t-e-v-e-n-k)) +- Add compat with pytest 8 [#1231](https://github.com/ipython/ipykernel/pull/1231) ([@blink1073](https://github.com/blink1073)) +- Set all min deps [#1229](https://github.com/ipython/ipykernel/pull/1229) ([@blink1073](https://github.com/blink1073)) + +### Contributors to this release + +([GitHub contributors page for this release](https://github.com/ipython/ipykernel/graphs/contributors?from=2024-07-01&to=2025-07-21&type=c)) + +[@Carreau](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3ACarreau+updated%3A2024-07-01..2025-07-21&type=Issues) | [@ccordoba12](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Accordoba12+updated%3A2024-07-01..2025-07-21&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adavidbrochart+updated%3A2024-07-01..2025-07-21&type=Issues) | [@dby-tmwctw](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Adby-tmwctw+updated%3A2024-07-01..2025-07-21&type=Issues) | [@gregory-shklover](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Agregory-shklover+updated%3A2024-07-01..2025-07-21&type=Issues) | [@ianthomas23](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aianthomas23+updated%3A2024-07-01..2025-07-21&type=Issues) | [@ivanov](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aivanov+updated%3A2024-07-01..2025-07-21&type=Issues) | [@jasongrout](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ajasongrout+updated%3A2024-07-01..2025-07-21&type=Issues) | [@krassowski](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Akrassowski+updated%3A2024-07-01..2025-07-21&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Ameeseeksmachine+updated%3A2024-07-01..2025-07-21&type=Issues) | [@minrk](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Aminrk+updated%3A2024-07-01..2025-07-21&type=Issues) | [@nathanmcavoy](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3Anathanmcavoy+updated%3A2024-07-01..2025-07-21&type=Issues) | [@s-t-e-v-e-n-k](https://github.com/search?q=repo%3Aipython%2Fipykernel+involves%3As-t-e-v-e-n-k+updated%3A2024-07-01..2025-07-21&type=Issues) + ## 6.29.5 ([Full Changelog](https://github.com/ipython/ipykernel/compare/v6.29.4...1e62d48298e353a9879fae99bc752f9bb48797ef)) @@ -12,7 +105,7 @@ ### Maintenance and upkeep improvements -- \[6.x\] Update Release Scripts [#1251](https://github.com/ipython/ipykernel/pull/1251) ([@blink1073](https://github.com/blink1073)) +- [6.x] Update Release Scripts [#1251](https://github.com/ipython/ipykernel/pull/1251) ([@blink1073](https://github.com/blink1073)) ### Contributors to this release @@ -20,8 +113,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)) @@ -73,7 +164,7 @@ ### Bugs fixed -- Fix: ipykernel_launcher, delete absolute sys.path\[0\] [#1206](https://github.com/ipython/ipykernel/pull/1206) ([@stdll00](https://github.com/stdll00)) +- Fix: ipykernel_launcher, delete absolute sys.path[0] [#1206](https://github.com/ipython/ipykernel/pull/1206) ([@stdll00](https://github.com/stdll00)) ### Maintenance and upkeep improvements @@ -341,7 +432,7 @@ ### Enhancements made -- Support control\<>iopub messages to e.g. unblock comm_msg from command execution [#1114](https://github.com/ipython/ipykernel/pull/1114) ([@tkrabel-db](https://github.com/tkrabel-db)) +- Support control\<>iopub messages to e.g. unblock comm_msg from command execution [#1114](https://github.com/ipython/ipykernel/pull/1114) ([@tkrabel-db](https://github.com/tkrabel-db)) - Add outstream hook similar to display publisher [#1110](https://github.com/ipython/ipykernel/pull/1110) ([@maartenbreddels](https://github.com/maartenbreddels)) ### Maintenance and upkeep improvements @@ -751,10 +842,10 @@ ### Maintenance and upkeep improvements -- \[pre-commit.ci\] pre-commit autoupdate [#989](https://github.com/ipython/ipykernel/pull/989) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#985](https://github.com/ipython/ipykernel/pull/985) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#989](https://github.com/ipython/ipykernel/pull/989) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#985](https://github.com/ipython/ipykernel/pull/985) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Add python logo in svg format [#984](https://github.com/ipython/ipykernel/pull/984) ([@steff456](https://github.com/steff456)) -- \[pre-commit.ci\] pre-commit autoupdate [#982](https://github.com/ipython/ipykernel/pull/982) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#982](https://github.com/ipython/ipykernel/pull/982) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -772,13 +863,13 @@ ### Maintenance and upkeep improvements -- \[pre-commit.ci\] pre-commit autoupdate [#978](https://github.com/ipython/ipykernel/pull/978) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#977](https://github.com/ipython/ipykernel/pull/977) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#976](https://github.com/ipython/ipykernel/pull/976) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#974](https://github.com/ipython/ipykernel/pull/974) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#971](https://github.com/ipython/ipykernel/pull/971) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#968](https://github.com/ipython/ipykernel/pull/968) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#966](https://github.com/ipython/ipykernel/pull/966) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#978](https://github.com/ipython/ipykernel/pull/978) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#977](https://github.com/ipython/ipykernel/pull/977) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#976](https://github.com/ipython/ipykernel/pull/976) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#974](https://github.com/ipython/ipykernel/pull/974) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#971](https://github.com/ipython/ipykernel/pull/971) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#968](https://github.com/ipython/ipykernel/pull/968) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#966](https://github.com/ipython/ipykernel/pull/966) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -796,9 +887,9 @@ ### Maintenance and upkeep improvements -- \[pre-commit.ci\] pre-commit autoupdate [#962](https://github.com/ipython/ipykernel/pull/962) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#961](https://github.com/ipython/ipykernel/pull/961) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#960](https://github.com/ipython/ipykernel/pull/960) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#962](https://github.com/ipython/ipykernel/pull/962) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#961](https://github.com/ipython/ipykernel/pull/961) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#960](https://github.com/ipython/ipykernel/pull/960) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -818,7 +909,7 @@ - Back to top-level tornado IOLoop [#958](https://github.com/ipython/ipykernel/pull/958) ([@minrk](https://github.com/minrk)) - Explicitly require pyzmq >= 17 [#957](https://github.com/ipython/ipykernel/pull/957) ([@minrk](https://github.com/minrk)) -- \[pre-commit.ci\] pre-commit autoupdate [#954](https://github.com/ipython/ipykernel/pull/954) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#954](https://github.com/ipython/ipykernel/pull/954) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -842,7 +933,7 @@ ### Maintenance and upkeep improvements - Fix sphinx 5.0 support [#951](https://github.com/ipython/ipykernel/pull/951) ([@blink1073](https://github.com/blink1073)) -- \[pre-commit.ci\] pre-commit autoupdate [#950](https://github.com/ipython/ipykernel/pull/950) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#950](https://github.com/ipython/ipykernel/pull/950) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -861,18 +952,18 @@ ### Maintenance and upkeep improvements -- \[pre-commit.ci\] pre-commit autoupdate [#945](https://github.com/ipython/ipykernel/pull/945) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#945](https://github.com/ipython/ipykernel/pull/945) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Clean up typings [#939](https://github.com/ipython/ipykernel/pull/939) ([@blink1073](https://github.com/blink1073)) -- \[pre-commit.ci\] pre-commit autoupdate [#938](https://github.com/ipython/ipykernel/pull/938) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#938](https://github.com/ipython/ipykernel/pull/938) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Clean up types [#933](https://github.com/ipython/ipykernel/pull/933) ([@blink1073](https://github.com/blink1073)) -- \[pre-commit.ci\] pre-commit autoupdate [#932](https://github.com/ipython/ipykernel/pull/932) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#932](https://github.com/ipython/ipykernel/pull/932) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Switch to hatch backend [#931](https://github.com/ipython/ipykernel/pull/931) ([@blink1073](https://github.com/blink1073)) -- \[pre-commit.ci\] pre-commit autoupdate [#928](https://github.com/ipython/ipykernel/pull/928) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#926](https://github.com/ipython/ipykernel/pull/926) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#928](https://github.com/ipython/ipykernel/pull/928) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#926](https://github.com/ipython/ipykernel/pull/926) ([@pre-commit-ci](https://github.com/pre-commit-ci)) - Allow enforce PR label workflow to add labels [#921](https://github.com/ipython/ipykernel/pull/921) ([@blink1073](https://github.com/blink1073)) -- \[pre-commit.ci\] pre-commit autoupdate [#920](https://github.com/ipython/ipykernel/pull/920) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#919](https://github.com/ipython/ipykernel/pull/919) ([@pre-commit-ci](https://github.com/pre-commit-ci)) -- \[pre-commit.ci\] pre-commit autoupdate [#917](https://github.com/ipython/ipykernel/pull/917) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#920](https://github.com/ipython/ipykernel/pull/920) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#919](https://github.com/ipython/ipykernel/pull/919) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#917](https://github.com/ipython/ipykernel/pull/917) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -897,7 +988,7 @@ - Add basic mypy support [#913](https://github.com/ipython/ipykernel/pull/913) ([@blink1073](https://github.com/blink1073)) - Clean up pre-commit [#911](https://github.com/ipython/ipykernel/pull/911) ([@blink1073](https://github.com/blink1073)) - Update setup.py [#909](https://github.com/ipython/ipykernel/pull/909) ([@tlinhart](https://github.com/tlinhart)) -- \[pre-commit.ci\] pre-commit autoupdate [#906](https://github.com/ipython/ipykernel/pull/906) ([@pre-commit-ci](https://github.com/pre-commit-ci)) +- [pre-commit.ci] pre-commit autoupdate [#906](https://github.com/ipython/ipykernel/pull/906) ([@pre-commit-ci](https://github.com/pre-commit-ci)) ### Contributors to this release @@ -1315,7 +1406,7 @@ - Add watchfd keyword to InProcessKernel OutStream initialization [#727](https://github.com/ipython/ipykernel/pull/727) ([@rayosborn](https://github.com/rayosborn)) - Fix typo in eventloops.py [#711](https://github.com/ipython/ipykernel/pull/711) ([@selasley](https://github.com/selasley)) -- \[bugfix\] fix in setup.py (comma before appnope) [#709](https://github.com/ipython/ipykernel/pull/709) ([@jstriebel](https://github.com/jstriebel)) +- [bugfix] fix in setup.py (comma before appnope) [#709](https://github.com/ipython/ipykernel/pull/709) ([@jstriebel](https://github.com/jstriebel)) ### Maintenance and upkeep improvements @@ -1644,8 +1735,6 @@ failures in nbconvert. 5.1.0 fixes some important regressions in 5.0, especially on Windows. -[5.1.0 on GitHub](https://github.com/ipython/ipykernel/milestones/5.1) - - Fix message-ordering bug that could result in out-of-order executions, especially on Windows [#356](https://github.com/ipython/ipykernel/pull/356) - Fix classifiers to indicate dropped Python 2 support @@ -1658,8 +1747,6 @@ failures in nbconvert. ### 5.0.0 -[5.0.0 on GitHub](https://github.com/ipython/ipykernel/milestones/5.0) - - Drop support for Python 2. `ipykernel` 5.0 requires Python >= 3.4 - Add support for IPython's asynchronous code execution [#323](https://github.com/ipython/ipykernel/pull/323) @@ -1667,8 +1754,6 @@ failures in nbconvert. ## 4.10 -[4.10 on GitHub](https://github.com/ipython/ipykernel/milestones/4.10) - - Fix compatibility with IPython 7.0 [#348](https://github.com/ipython/ipykernel/pull/348) - Fix compatibility in cases where sys.stdout can be None [#344](https://github.com/ipython/ipykernel/pull/344) @@ -1677,8 +1762,6 @@ failures in nbconvert. ### 4.9.0 -[4.9.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.9) - - Python 3.3 is no longer supported [#336](https://github.com/ipython/ipykernel/pull/336) - Flush stdout/stderr in KernelApp before replacing [#314](https://github.com/ipython/ipykernel/pull/314) @@ -1692,15 +1775,11 @@ failures in nbconvert. ### 4.8.2 -[4.8.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8.2) - - Fix compatibility issue with qt eventloop and pyzmq 17 [#307](https://github.com/ipython/ipykernel/pull/307). ### 4.8.1 -[4.8.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8.1) - - set zmq.ROUTER_HANDOVER socket option when available to workaround libzmq reconnect bug [#300](https://github.com/ipython/ipykernel/pull/300). - Fix sdists including absolute paths for kernelspec files, which @@ -1709,8 +1788,6 @@ failures in nbconvert. ### 4.8.0 -[4.8.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.8) - - Cleanly shutdown integrated event loops when shutting down the kernel. [#290](https://github.com/ipython/ipykernel/pull/290) - `%gui qt` now uses Qt 5 by default rather than Qt 4, following a @@ -1722,8 +1799,6 @@ failures in nbconvert. ### 4.7.0 -[4.7.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.7) - - Add event loop integration for `asyncio`. - Use the new IPython completer API. - Add support for displaying GIF images (mimetype `image/gif`). @@ -1737,8 +1812,6 @@ failures in nbconvert. ### 4.6.1 -[4.6.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.6.1) - - Fix eventloop-integration bug preventing Qt windows/widgets from displaying with ipykernel 4.6.0 and IPython ≥ 5.2. - Avoid deprecation warnings about naive datetimes when working with @@ -1746,8 +1819,6 @@ failures in nbconvert. ### 4.6.0 -[4.6.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.6) - - Add to API `DisplayPublisher.publish` two new fully backward-compatible keyword-args: @@ -1790,15 +1861,11 @@ failures in nbconvert. ### 4.5.2 -[4.5.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5.2) - - Fix bug when instantiating Comms outside of the IPython kernel (introduced in 4.5.1). ### 4.5.1 -[4.5.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5.1) - - Add missing `stream` parameter to overridden `getpass` - Remove locks from iopub thread, which could cause deadlocks during @@ -1809,8 +1876,6 @@ failures in nbconvert. ### 4.5.0 -[4.5 on GitHub](https://github.com/ipython/ipykernel/milestones/4.5) - - Use figure.dpi instead of savefig.dpi to set DPI for inline figures - Support ipympl matplotlib backend (requires IPython update as well to fully work) @@ -1822,15 +1887,11 @@ failures in nbconvert. ### 4.4.1 -[4.4.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.4.1) - - Fix circular import of matplotlib on Python 2 caused by the inline backend changes in 4.4.0. ### 4.4.0 -[4.4.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.4) - - Use [MPLBACKEND](http://matplotlib.org/devel/coding_guide.html?highlight=mplbackend#developing-a-new-backend) environment variable to tell matplotlib >= 1.5 use use the inline @@ -1864,8 +1925,6 @@ failures in nbconvert. ### 4.3.0 -[4.3.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.3) - - Publish all IO in a thread, via `IOPubThread`. This solves the problem of requiring `sys.stdout.flush` to be called in the notebook to produce output promptly during long-running cells. @@ -1888,22 +1947,16 @@ failures in nbconvert. ### 4.2.2 -[4.2.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2.2) - - Don't show interactive debugging info when kernel crashes - Fix handling of numerical types in json_clean - Testing fixes for output capturing ### 4.2.1 -[4.2.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2.1) - - Fix default display name back to "Python X" instead of "pythonX" ### 4.2.0 -[4.2 on GitHub](https://github.com/ipython/ipykernel/milestones/4.2) - - Support sending a full message in initial opening of comms (metadata, buffers were not previously allowed) - When using `ipython kernel install --name` to install the IPython @@ -1913,15 +1966,11 @@ failures in nbconvert. ### 4.1.1 -[4.1.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.1.1) - - Fix missing `ipykernel.__version__` on Python 2. - Fix missing `target_name` when opening comms from the frontend. ### 4.1.0 -[4.1 on GitHub](https://github.com/ipython/ipykernel/milestones/4.1) - - add `ipython kernel install` entrypoint for installing the IPython kernelspec - provisional implementation of `comm_info` request/reply for msgspec @@ -1929,6 +1978,4 @@ failures in nbconvert. ## 4.0 -[4.0 on GitHub](https://github.com/ipython/ipykernel/milestones/4.0) - 4.0 is the first release of ipykernel as a standalone package. diff --git a/ipykernel/_version.py b/ipykernel/_version.py index 792dff91b..064ab5efd 100644 --- a/ipykernel/_version.py +++ b/ipykernel/_version.py @@ -4,7 +4,7 @@ import re # Version string must appear intact for hatch versioning -__version__ = "6.29.5" +__version__ = "6.31.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" diff --git a/ipykernel/inprocess/ipkernel.py b/ipykernel/inprocess/ipkernel.py index 789b0bf8a..aecb3b102 100644 --- a/ipykernel/inprocess/ipkernel.py +++ b/ipykernel/inprocess/ipkernel.py @@ -91,8 +91,10 @@ def _abort_queues(self): def _input_request(self, prompt, ident, parent, password=False): # Flush output before making the request. self.raw_input_str = None - sys.stderr.flush() - sys.stdout.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() # Send the input request. content = json_clean(dict(prompt=prompt, password=password)) diff --git a/ipykernel/kernelbase.py b/ipykernel/kernelbase.py index cb440199e..78bc092ed 100644 --- a/ipykernel/kernelbase.py +++ b/ipykernel/kernelbase.py @@ -246,6 +246,9 @@ def _parent_header(self): # execution count we store in the shell. execution_count = 0 + # Asyncio lock to ensure only one control queue message is processed at a time. + _control_lock = Instance(asyncio.Lock) + msg_types = [ "execute_request", "complete_request", @@ -295,7 +298,7 @@ def __init__(self, **kwargs): async def dispatch_control(self, msg): # Ensure only one control message is processed at a time - async with asyncio.Lock(): + async with self._control_lock: await self.process_control(msg) async def process_control(self, msg): @@ -329,8 +332,10 @@ async def process_control(self, msg): except Exception: self.log.error("Exception in control handler:", exc_info=True) # noqa: G201 - sys.stdout.flush() - sys.stderr.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() self._publish_status_and_flush("idle", "control", self.control_stream) def should_handle(self, stream, msg, idents): @@ -404,8 +409,10 @@ async def dispatch_shell(self, msg): except Exception: self.log.debug("Unable to signal in post_handler_hook:", exc_info=True) - sys.stdout.flush() - sys.stderr.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() self._publish_status_and_flush("idle", "shell", self.shell_stream) def pre_handler_hook(self): @@ -536,6 +543,10 @@ def schedule_dispatch(self, dispatch, *args): # ensure the eventloop wakes up self.io_loop.add_callback(lambda: None) + async def _create_control_lock(self): + # This can be removed when minimum python increases to 3.10 + self._control_lock = asyncio.Lock() + def start(self): """register dispatchers for streams""" self.io_loop = ioloop.IOLoop.current() @@ -545,6 +556,14 @@ def start(self): if self.control_stream: self.control_stream.on_recv(self.dispatch_control, copy=False) + if self.control_thread and sys.version_info < (3, 10): + # Before Python 3.10 we need to ensure the _control_lock is created in the + # thread that uses it. When our minimum python is 3.10 we can remove this + # and always use the else below, or just assign it where it is declared. + self.control_thread.io_loop.add_callback(self._create_control_lock) + else: + self._control_lock = asyncio.Lock() + if self.shell_stream: self.shell_stream.on_recv( partial( @@ -748,8 +767,10 @@ async def execute_request(self, stream, ident, parent): reply_content = await reply_content # Flush output before sending the reply. - sys.stdout.flush() - sys.stderr.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() # FIXME: on rare occasions, the flush doesn't seem to make it to the # clients... This seems to mitigate the problem, but we definitely need # to better understand what's going on. @@ -1083,8 +1104,10 @@ async def apply_request(self, stream, ident, parent): # pragma: no cover reply_content, result_buf = self.do_apply(content, bufs, msg_id, md) # flush i/o - sys.stdout.flush() - sys.stderr.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() md = self.finish_metadata(parent, md, reply_content) if not self.session: @@ -1258,8 +1281,10 @@ def raw_input(self, prompt=""): def _input_request(self, prompt, ident, parent, password=False): # Flush output before making the request. - sys.stderr.flush() - sys.stdout.flush() + if sys.stdout is not None: + sys.stdout.flush() + if sys.stderr is not None: + sys.stderr.flush() # flush the stdin socket, to purge stale replies while True: diff --git a/ipykernel/trio_runner.py b/ipykernel/trio_runner.py index 45f738acb..a641feba8 100644 --- a/ipykernel/trio_runner.py +++ b/ipykernel/trio_runner.py @@ -29,7 +29,7 @@ def initialize(self, kernel, io_loop): bg_thread.start() def interrupt(self, signum, frame): - """Interuppt the runner.""" + """Interrupt the runner.""" if self._cell_cancel_scope: self._cell_cancel_scope.cancel() else: diff --git a/ipykernel/zmqshell.py b/ipykernel/zmqshell.py index 4fa850735..8845da237 100644 --- a/ipykernel/zmqshell.py +++ b/ipykernel/zmqshell.py @@ -20,7 +20,7 @@ from pathlib import Path from threading import local -from IPython.core import page, payloadpage +from IPython.core import page from IPython.core.autocall import ZMQExitAutocall from IPython.core.displaypub import DisplayPublisher from IPython.core.error import UsageError @@ -33,12 +33,17 @@ from IPython.utils.process import arg_split, system # type:ignore[attr-defined] from jupyter_client.session import Session, extract_header from jupyter_core.paths import jupyter_runtime_dir -from traitlets import Any, CBool, CBytes, Dict, Instance, Type, default, observe +from traitlets import Any, Bool, CBool, CBytes, Dict, Instance, Type, default, observe from ipykernel import connect_qtconsole, get_connection_file, get_connection_info from ipykernel.displayhook import ZMQShellDisplayHook from ipykernel.jsonutil import encode_images, json_clean +try: + from IPython.core.history import HistoryOutput +except ImportError: + HistoryOutput = None # type: ignore[assignment,misc] + # ----------------------------------------------------------------------------- # Functions and classes # ----------------------------------------------------------------------------- @@ -52,6 +57,11 @@ class ZMQDisplayPublisher(DisplayPublisher): parent_header = Dict({}) topic = CBytes(b"display_data") + store_display_history = Bool( + False, + help="If set to True, store display outputs in the history manager. Default is False.", + ).tag(config=True) + # thread_local: # An attribute used to ensure the correct output message # is processed. See ipykernel Issue 113 for a discussion. @@ -100,6 +110,21 @@ def publish( update : bool, optional, keyword-only If True, send an update_display_data message instead of display_data. """ + if ( + self.store_display_history + and self.shell is not None + and hasattr(self.shell, "history_manager") + and HistoryOutput is not None + ): + # Reference: github.com/ipython/ipython/pull/14998 + exec_count = self.shell.execution_count + if getattr(self.shell.display_pub, "_in_post_execute", False): + exec_count -= 1 + outputs = getattr(self.shell.history_manager, "outputs", None) + if outputs is not None: + outputs.setdefault(exec_count, []).append( + HistoryOutput(output_type="display_data", bundle=data) + ) self._flush_streams() if metadata is None: metadata = {} @@ -508,10 +533,38 @@ def init_environment(self): env["PAGER"] = "cat" env["GIT_PAGER"] = "cat" + def payloadpage_page(self, strg, start=0, screen_lines=0, pager_cmd=None): + """Print a string, piping through a pager. + + This version ignores the screen_lines and pager_cmd arguments and uses + IPython's payload system instead. + + Parameters + ---------- + strg : str or mime-dict + Text to page, or a mime-type keyed dict of already formatted data. + start : int + Starting line at which to place the display. + """ + + # Some routines may auto-compute start offsets incorrectly and pass a + # negative value. Offset to 0 for robustness. + start = max(0, start) + + data = strg if isinstance(strg, dict) else {"text/plain": strg} + + payload = dict( + source="page", + data=data, + start=start, + ) + assert self.payload_manager is not None + self.payload_manager.write_payload(payload) + def init_hooks(self): """Initialize hooks.""" super().init_hooks() - self.set_hook("show_in_pager", page.as_hook(payloadpage.page), 99) + self.set_hook("show_in_pager", page.as_hook(self.payloadpage_page), 99) def init_data_pub(self): """Delay datapub init until request, for deprecation warnings""" diff --git a/pyproject.toml b/pyproject.toml index b11d572c9..f2077971f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [build-system] -requires = ["hatchling>=1.4", "jupyter_client>=6"] +requires = ["hatchling>=1.22", "jupyter_client>=6"] build-backend = "hatchling.build" [project] name = "ipykernel" dynamic = ["version"] authors = [{name = "IPython Development Team", email = "ipython-dev@scipy.org"}] -license = {file = "LICENSE"} +license = "BSD-3-Clause" readme = "README.md" description = "IPython Kernel for Jupyter" keywords = ["Interactive", "Interpreter", "Shell", "Web"] @@ -38,7 +38,6 @@ dependencies = [ [project.urls] Homepage = "https://ipython.org" Documentation = "https://ipykernel.readthedocs.io" -Funding = "https://numfocus.org/donate" Source = "https://github.com/ipython/ipykernel" Tracker = "https://github.com/ipython/ipykernel/issues" @@ -102,13 +101,10 @@ features = ["test", "cov"] test = "python -m pytest -vv --cov ipykernel --cov-branch --cov-report term-missing:skip-covered {args}" nowarn = "test -W default {args}" -[[tool.hatch.envs.cov.matrix]] -qt = ["qt5", "qt6"] - [tool.hatch.envs.cov.overrides] -matrix.qt.features = [ - { value = "pyqt5", if = ["qt5"] }, - { value = "pyside6", if = ["qt6"] }, +env.QT.features = [ + { value = "pyqt5", if = ["qt5"] }, + { value = "pyside6", if = ["qt6"] }, ] [tool.hatch.envs.typing] diff --git a/tests/inprocess/test_kernelmanager.py b/tests/inprocess/test_kernelmanager.py index d3ee99913..15e985b2b 100644 --- a/tests/inprocess/test_kernelmanager.py +++ b/tests/inprocess/test_kernelmanager.py @@ -4,7 +4,6 @@ import unittest import pytest -from flaky import flaky from ipykernel.inprocess.manager import InProcessKernelManager @@ -21,7 +20,7 @@ def tearDown(self): if self.km.has_kernel: self.km.shutdown_kernel() - @flaky + @pytest.mark.flaky() def test_interface(self): """Does the in-process kernel manager implement the basic KM interface?""" km = self.km diff --git a/tests/test_debugger.py b/tests/test_debugger.py index fa64d2d09..2d8dc765e 100644 --- a/tests/test_debugger.py +++ b/tests/test_debugger.py @@ -2,7 +2,7 @@ import pytest -from .utils import TIMEOUT, get_reply, new_kernel +from .utils import TIMEOUT, get_replies, get_reply, new_kernel seq = 0 @@ -15,11 +15,8 @@ debugpy = None -def wait_for_debug_request(kernel, command, arguments=None, full_reply=False): - """Carry out a debug request and return the reply content. - - It does not check if the request was successful. - """ +def prepare_debug_request(kernel, command, arguments=None): + """Prepare a debug request but do not send it.""" global seq seq += 1 @@ -32,6 +29,15 @@ def wait_for_debug_request(kernel, command, arguments=None, full_reply=False): "arguments": arguments or {}, }, ) + return msg + + +def wait_for_debug_request(kernel, command, arguments=None, full_reply=False): + """Carry out a debug request and return the reply content. + + It does not check if the request was successful. + """ + msg = prepare_debug_request(kernel, command, arguments) kernel.control_channel.send(msg) reply = get_reply(kernel, msg["header"]["msg_id"], channel="control") return reply if full_reply else reply["content"] @@ -448,3 +454,96 @@ def my_test(): # Compare local and global variable assert global_var["value"] == local_var["value"] and global_var["type"] == local_var["type"] # noqa: PT018 + + +def test_debug_requests_sequential(kernel_with_debug): + # Issue https://github.com/ipython/ipykernel/issues/1412 + # Control channel requests should be executed sequentially not concurrently. + code = """def f(a, b): + c = a + b + return c + +f(2, 3)""" + + r = wait_for_debug_request(kernel_with_debug, "dumpCell", {"code": code}) + if debugpy: + source = r["body"]["sourcePath"] + else: + assert r == {} + source = "some path" + + wait_for_debug_request( + kernel_with_debug, + "setBreakpoints", + { + "breakpoints": [{"line": 2}], + "source": {"path": source}, + "sourceModified": False, + }, + ) + + wait_for_debug_request(kernel_with_debug, "debugInfo") + wait_for_debug_request(kernel_with_debug, "configurationDone") + kernel_with_debug.execute(code) + + if not debugpy: + # Cannot stop on breakpoint if debugpy not installed + return + + # Wait for stop on breakpoint + msg: dict = {"msg_type": "", "content": {}} + while msg.get("msg_type") != "debug_event" or msg["content"].get("event") != "stopped": + msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) + + stacks = wait_for_debug_request(kernel_with_debug, "stackTrace", {"threadId": 1})["body"][ + "stackFrames" + ] + + scopes = wait_for_debug_request(kernel_with_debug, "scopes", {"frameId": stacks[0]["id"]})[ + "body" + ]["scopes"] + + # Get variablesReference for both Locals and Globals. + locals_ref = next(filter(lambda s: s["name"] == "Locals", scopes))["variablesReference"] + globals_ref = next(filter(lambda s: s["name"] == "Globals", scopes))["variablesReference"] + + msgs = [] + for ref in [locals_ref, globals_ref]: + msgs.append( + prepare_debug_request(kernel_with_debug, "variables", {"variablesReference": ref}) + ) + + # Send messages in quick succession. + for msg in msgs: + kernel_with_debug.control_channel.send(msg) + + replies = get_replies(kernel_with_debug, [msg["msg_id"] for msg in msgs], channel="control") + + # Check debug variable returns are correct. + locals = replies[0]["content"] + assert locals["success"] + variables = locals["body"]["variables"] + var = next(filter(lambda v: v["name"] == "a", variables)) + assert var["type"] == "int" + assert var["value"] == "2" + var = next(filter(lambda v: v["name"] == "b", variables)) + assert var["type"] == "int" + assert var["value"] == "3" + + globals = replies[1]["content"] + assert globals["success"] + variables = globals["body"]["variables"] + + names = [v["name"] for v in variables] + assert "function variables" in names + assert "special variables" in names + + # Check status iopub messages alternate between busy and idle. + execution_states = [] + while len(execution_states) < 8: + msg = kernel_with_debug.get_iopub_msg(timeout=TIMEOUT) + if msg["msg_type"] == "status": + execution_states.append(msg["content"]["execution_state"]) + assert execution_states.count("busy") == 4 + assert execution_states.count("idle") == 4 + assert execution_states == ["busy", "idle"] * 4 diff --git a/tests/test_embed_kernel.py b/tests/test_embed_kernel.py index ff97edfa5..8e70f8b4c 100644 --- a/tests/test_embed_kernel.py +++ b/tests/test_embed_kernel.py @@ -12,7 +12,6 @@ from subprocess import PIPE, Popen import pytest -from flaky import flaky from jupyter_client.blocking.client import BlockingKernelClient from jupyter_core import paths @@ -91,7 +90,7 @@ def connection_file_ready(connection_file): fid.close() -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_embed_kernel_basic(): """IPython.embed_kernel() is basically functional""" cmd = "\n".join( @@ -127,7 +126,7 @@ def test_embed_kernel_basic(): assert "10" in text -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_embed_kernel_namespace(): """IPython.embed_kernel() inherits calling namespace""" cmd = "\n".join( @@ -166,7 +165,7 @@ def test_embed_kernel_namespace(): assert not content["found"] -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_embed_kernel_reentrant(): """IPython.embed_kernel() can be called multiple times""" cmd = "\n".join( diff --git a/tests/test_eventloop.py b/tests/test_eventloop.py index 69acc0ea2..76cadd7be 100644 --- a/tests/test_eventloop.py +++ b/tests/test_eventloop.py @@ -124,12 +124,16 @@ def test_cocoa_loop(kernel): loop_cocoa(kernel) -@pytest.mark.skipif( - len(qt_guis_avail) == 0, reason="No viable version of PyQt or PySide installed." -) -def test_qt_enable_gui(kernel, capsys): - gui = qt_guis_avail[0] - +@pytest.mark.parametrize("gui", qt_guis_avail) +def test_qt_enable_gui(gui, kernel, capsys): + if os.getenv("GITHUB_ACTIONS", None) == "true" and gui == "qt5": + pytest.skip("Qt5 and GitHub action crash CPython") + if gui == "qt6" and sys.version_info < (3, 10): + pytest.skip( + "qt6 fails on 3.9 with AttributeError: module 'PySide6.QtPrintSupport' has no attribute 'QApplication'" + ) + if sys.platform == "linux" and gui == "qt6" and os.getenv("GITHUB_ACTIONS", None) == "true": + pytest.skip("qt6 fails on github CI with missing libEGL.so.1") enable_gui(gui, kernel) # We store the `QApplication` instance in the kernel. diff --git a/tests/test_kernel.py b/tests/test_kernel.py index 88f02ae9a..661c59b54 100644 --- a/tests/test_kernel.py +++ b/tests/test_kernel.py @@ -17,7 +17,6 @@ import IPython import psutil import pytest -from flaky import flaky from IPython.paths import locate_profile from .utils import ( @@ -210,10 +209,14 @@ def test_sys_path_profile_dir(): assert "" in sys_path -@flaky(max_runs=3) +# the subprocess print tests fail in pytest, +# but manual tests in notebooks work fine... + + +@pytest.mark.flaky(max_runs=3) @pytest.mark.skipif( - sys.platform == "win32" or (sys.platform == "darwin" and sys.version_info >= (3, 8)), - reason="subprocess prints fail on Windows and MacOS Python 3.8+", + sys.platform in {"win32", "darwin"} or sys.version_info >= (3, 14), + reason="test doesn't reliably reproduce subprocess output capture", ) def test_subprocess_print(): """printing from forked mp.Process""" @@ -242,7 +245,7 @@ def test_subprocess_print(): _check_master(kc, expected=True, stream="stderr") -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_subprocess_noprint(): """mp.Process without print doesn't trigger iostream mp_mode""" with kernel() as kc: @@ -265,10 +268,10 @@ def test_subprocess_noprint(): _check_master(kc, expected=True, stream="stderr") -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) @pytest.mark.skipif( - sys.platform == "win32" or (sys.platform == "darwin" and sys.version_info >= (3, 8)), - reason="subprocess prints fail on Windows and MacOS Python 3.8+", + sys.platform in {"win32", "darwin"} or sys.version_info >= (3, 14), + reason="test doesn't reliably reproduce subprocess output capture", ) def test_subprocess_error(): """error in mp.Process doesn't crash""" diff --git a/tests/test_start_kernel.py b/tests/test_start_kernel.py index 71f4bdc0a..b9276e33b 100644 --- a/tests/test_start_kernel.py +++ b/tests/test_start_kernel.py @@ -2,7 +2,6 @@ from textwrap import dedent import pytest -from flaky import flaky from .test_embed_kernel import setup_kernel @@ -12,7 +11,7 @@ pytest.skip("skipping tests on windows", allow_module_level=True) -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_ipython_start_kernel_userns(): import IPython @@ -51,7 +50,7 @@ def test_ipython_start_kernel_userns(): assert EXPECTED in text -@flaky(max_runs=3) +@pytest.mark.flaky(max_runs=3) def test_ipython_start_kernel_no_userns(): # Issue #4188 - user_ns should be passed to shell as None, not {} cmd = dedent( diff --git a/tests/test_zmq_shell.py b/tests/test_zmq_shell.py index dfd22dec0..c15fb2219 100644 --- a/tests/test_zmq_shell.py +++ b/tests/test_zmq_shell.py @@ -22,6 +22,11 @@ ZMQInteractiveShell, ) +try: + from IPython.core.history import HistoryOutput +except ImportError: + HistoryOutput = None # type: ignore[assignment,misc] + class NoReturnDisplayHook: """ @@ -209,6 +214,35 @@ def test_unregister_hook(self): second = self.disp_pub.unregister_hook(hook) assert not bool(second) + @unittest.skipIf(HistoryOutput is None, "HistoryOutput not available") + def test_display_stored_in_history(self): + """ + Test that published display data gets stored in shell history + for %notebook magic support, and not stored when disabled. + """ + for enable in [False, True]: + # Mock shell with history manager + mock_shell = MagicMock() + mock_shell.execution_count = 1 + mock_shell.history_manager.outputs = dict() + mock_shell.display_pub._in_post_execute = False + + self.disp_pub.shell = mock_shell + self.disp_pub.store_display_history = enable + + data = {"text/plain": "test output"} + self.disp_pub.publish(data) + + if enable: + # Check that output was stored in history + stored_outputs = mock_shell.history_manager.outputs[1] + assert len(stored_outputs) == 1 + assert stored_outputs[0].output_type == "display_data" + assert stored_outputs[0].bundle == data + else: + # Should not store anything in history + assert mock_shell.history_manager.outputs == {} + def test_magics(tmp_path): context = zmq.Context() diff --git a/tests/utils.py b/tests/utils.py index a0880df13..0444c1b4f 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -62,6 +62,24 @@ def get_reply(kc, msg_id, timeout=TIMEOUT, channel="shell"): return reply +def get_replies(kc, msg_ids: list[str], timeout=TIMEOUT, channel="shell"): + # Get replies which may arrive in any order as they may be running on different subshells. + # Replies are returned in the same order as the msg_ids, not in the order of arrival. + count = 0 + replies = [None] * len(msg_ids) + while count < len(msg_ids): + get_msg = getattr(kc, f"get_{channel}_msg") + reply = get_msg(timeout=timeout) + try: + msg_id = reply["parent_header"]["msg_id"] + replies[msg_ids.index(msg_id)] = reply + count += 1 + except ValueError: + # Allow debugging ignored replies + print(f"Ignoring reply not to any of {msg_ids}: {reply}") + return replies + + def execute(code="", kc=None, **kwargs): """wrapper for doing common steps for validating an execution request""" from .test_message_spec import validate_message @@ -137,7 +155,7 @@ def stop_global_kernel(): def new_kernel(argv=None): """Context manager for a new kernel in a subprocess - Should only be used for tests where the kernel must not be re-used. + Should only be used for tests where the kernel must not be reused. Returns -------