[
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    groups:\n      actions:\n        patterns:\n          - \"*\"\n  - package-ecosystem: \"pip\"\n    directory: \"/\"\n    schedule:\n      interval: \"daily\"\n      time: \"04:00\"\n    groups:\n      actions:\n        patterns:\n          - \"*\"\n"
  },
  {
    "path": ".github/workflows/check-release.yml",
    "content": "name: Check Release\non:\n  push:\n    branches: [\"*\"]\n  pull_request:\n    branches: [\"*\"]\n  release:\n    types: [published]\n  schedule:\n    - cron: \"0 0 * * *\"\n\njobs:\n  check_release:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n      - name: Install Dependencies\n        shell: bash\n        run: |\n          pip install -e .\n      - name: Check Release\n        uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/main.yml",
    "content": "name: CI\n\non:\n  push:\n    branches: [\"main\"]\n  pull_request:\n  workflow_dispatch:\n  schedule:\n    - cron: \"0 8 * * *\"\n\ndefaults:\n  run:\n    shell: bash -eux {0}\n\njobs:\n  test_lint:\n    name: Test Lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n      - name: Run Linters\n        run: |\n          hatch run typing:test\n          hatch run lint:build\n          pipx run interrogate -v .\n          pipx run doc8 --max-line-length=200\n\n  test_docs:\n    name: Test Docs\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n      - run: hatch run docs:build\n\n  build:\n    name: Build, test and code coverage\n    runs-on: ${{ matrix.os }}\n    timeout-minutes: 30\n    strategy:\n      fail-fast: false\n      matrix:\n        os: [\"ubuntu-latest\", \"macos-latest\", \"windows-latest\"]\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\", \"3.14\"]\n        include:\n          - os: ubuntu-latest\n            python-version: \"pypy-3.11\"\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - name: Base Setup\n        uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n      - name: Run the tests\n        run: hatch run cov:test || hatch run test:test --lf\n      - uses: jupyterlab/maintainer-tools/.github/actions/upload-coverage@v1\n\n  coverage:\n    runs-on: ubuntu-latest\n    needs:\n      - build\n    steps:\n      - uses: actions/checkout@v4\n      - uses: jupyterlab/maintainer-tools/.github/actions/report-coverage@v1\n\n  test_minimum_versions:\n    name: Test Minimum Versions\n    timeout-minutes: 20\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n        with:\n          dependency_type: minimum\n      - name: Run the unit tests\n        run: |\n          hatch run test:nowarn || hatch -v run test:nowarn --lf\n\n  test_prereleases:\n    name: Test Prereleases\n    runs-on: ubuntu-latest\n    timeout-minutes: 20\n    steps:\n      - uses: actions/checkout@v4\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n        with:\n          dependency_type: pre\n      - name: Run the tests\n        run: |\n          hatch run test:nowarn || hatch run test:nowarn --lf\n\n  make_sdist:\n    name: Make SDist\n    runs-on: ubuntu-latest\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v4\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n      - uses: jupyterlab/maintainer-tools/.github/actions/make-sdist@v1\n\n  test_sdist:\n    runs-on: ubuntu-latest\n    needs: [make_sdist]\n    name: Install from SDist and Test\n    timeout-minutes: 20\n    steps:\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n      - uses: jupyterlab/maintainer-tools/.github/actions/test-sdist@v1\n        with:\n          package_spec: .\n          test_command: hatch run test:test || hatch run test:test --lf\n\n  check_links:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n      - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1\n\n  check_release:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n      - name: Check Release\n        uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n\n  tests_check: # This job does nothing and is only used for the branch protection\n    if: always()\n    needs:\n      - coverage\n      - test_lint\n      - test_docs\n      - test_minimum_versions\n      - test_prereleases\n      - check_links\n      - check_release\n      - test_sdist\n    runs-on: ubuntu-latest\n    steps:\n      - name: Decide whether the needed jobs succeeded or failed\n        uses: re-actors/alls-green@release/v1\n        with:\n          jobs: ${{ toJSON(needs) }}\n"
  },
  {
    "path": ".github/workflows/prep-release.yml",
    "content": "name: \"Step 1: Prep Release\"\non:\n  workflow_dispatch:\n    inputs:\n      version_spec:\n        description: \"New Version Specifier\"\n        default: \"next\"\n        required: false\n      branch:\n        description: \"The branch to target\"\n        required: false\n      post_version_spec:\n        description: \"Post Version Specifier\"\n        required: false\n      silent:\n        description: \"Set a placeholder in the changelog and don't publish the release.\"\n        required: false\n        type: boolean\n      since:\n        description: \"Use PRs with activity since this date or git reference\"\n        required: false\n      since_last_stable:\n        description: \"Use PRs with activity since the last stable git tag\"\n        required: false\n        type: boolean\njobs:\n  prep_release:\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    steps:\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n\n      - name: Prep Release\n        id: prep-release\n        uses: jupyter-server/jupyter_releaser/.github/actions/prep-release@v2\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          version_spec: ${{ github.event.inputs.version_spec }}\n          silent: ${{ github.event.inputs.silent }}\n          post_version_spec: ${{ github.event.inputs.post_version_spec }}\n          target: ${{ github.event.inputs.target }}\n          branch: ${{ github.event.inputs.branch }}\n          since: ${{ github.event.inputs.since }}\n          since_last_stable: ${{ github.event.inputs.since_last_stable }}\n\n      - name: \"** Next Step **\"\n        run: |\n          echo \"Optional): Review Draft Release: ${{ steps.prep-release.outputs.release_url }}\"\n"
  },
  {
    "path": ".github/workflows/publish-changelog.yml",
    "content": "name: \"Publish Changelog\"\non:\n  release:\n    types: [published]\n\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: \"The branch to target\"\n        required: false\n\njobs:\n  publish_changelog:\n    runs-on: ubuntu-latest\n    environment: release\n    steps:\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n\n      - uses: actions/create-github-app-token@v1\n        id: app-token\n        with:\n          app-id: ${{ vars.APP_ID }}\n          private-key: ${{ secrets.APP_PRIVATE_KEY }}\n\n      - name: Publish changelog\n        id: publish-changelog\n        uses: jupyter-server/jupyter_releaser/.github/actions/publish-changelog@v2\n        with:\n          token: ${{ steps.app-token.outputs.token }}\n          branch: ${{ github.event.inputs.branch }}\n\n      - name: \"** Next Step **\"\n        run: |\n          echo \"Merge the changelog update PR: ${{ steps.publish-changelog.outputs.pr_url }}\"\n"
  },
  {
    "path": ".github/workflows/publish-release.yml",
    "content": "name: \"Step 2: Publish Release\"\non:\n  workflow_dispatch:\n    inputs:\n      branch:\n        description: \"The target branch\"\n        required: false\n      release_url:\n        description: \"The URL of the draft GitHub release\"\n        required: false\n      steps_to_skip:\n        description: \"Comma separated list of steps to skip\"\n        required: false\n\njobs:\n  publish_release:\n    runs-on: ubuntu-latest\n    environment: release\n    permissions:\n      id-token: write\n    steps:\n      - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1\n\n      - uses: actions/create-github-app-token@v1\n        id: app-token\n        with:\n          app-id: ${{ vars.APP_ID }}\n          private-key: ${{ secrets.APP_PRIVATE_KEY }}\n\n      - name: Populate Release\n        id: populate-release\n        uses: jupyter-server/jupyter_releaser/.github/actions/populate-release@v2\n        with:\n          token: ${{ steps.app-token.outputs.token }}\n          branch: ${{ github.event.inputs.branch }}\n          release_url: ${{ github.event.inputs.release_url }}\n          steps_to_skip: ${{ github.event.inputs.steps_to_skip }}\n\n      - name: Finalize Release\n        id: finalize-release\n        uses: jupyter-server/jupyter_releaser/.github/actions/finalize-release@v2\n        with:\n          token: ${{ steps.app-token.outputs.token }}\n          release_url: ${{ steps.populate-release.outputs.release_url }}\n\n      - name: \"** Next Step **\"\n        if: ${{ success() }}\n        run: |\n          echo \"Verify the final release\"\n          echo ${{ steps.finalize-release.outputs.release_url }}\n\n      - name: \"** Failure Message **\"\n        if: ${{ failure() }}\n        run: |\n          echo \"Failed to Publish the Draft Release Url:\"\n          echo ${{ steps.populate-release.outputs.release_url }}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\npip-wheel-metadata/\nshare/python-wheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# Autogenerated docs\ndocs/reference/config_options.rst\ndocs/changelog.md\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.nox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n*.py,cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\ndb.sqlite3-journal\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\ndocs/changelog.md\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# IPython\nprofile_default/\nipython_config.py\n\n# pyenv\n.python-version\n\n# pipenv\n#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.\n#   However, in case of collaboration, if having platform-specific dependencies or dependencies\n#   having no cross-platform support, pipenv may install dependencies that don't work, or not\n#   install all needed dependencies.\nPipfile\nPipfile.lock\n\n# PEP 582; used by e.g. github.com/David-OConnor/pyflow\n__pypackages__/\n\n# Celery stuff\ncelerybeat-schedule\ncelerybeat.pid\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n.dmypy.json\ndmypy.json\n\n# Pyre type checker\n.pyre/\n\n# Pycharm stuff\n.idea/\n\n# VSCode\n.vscode\n"
  },
  {
    "path": ".pre-commit-config.yaml",
    "content": "ci:\n  autoupdate_schedule: monthly\n  autoupdate_commit_msg: \"chore: update pre-commit hooks\"\n\nrepos:\n  - repo: https://github.com/pre-commit/pre-commit-hooks\n    rev: v4.5.0\n    hooks:\n      - id: check-case-conflict\n      - id: check-ast\n      - id: check-docstring-first\n      - id: check-executables-have-shebangs\n      - id: check-added-large-files\n      - id: check-case-conflict\n      - id: check-merge-conflict\n      - id: check-json\n      - id: check-toml\n      - id: check-yaml\n      - id: debug-statements\n      - id: end-of-file-fixer\n      - id: trailing-whitespace\n\n  - repo: https://github.com/python-jsonschema/check-jsonschema\n    rev: 0.27.4\n    hooks:\n      - id: check-github-workflows\n\n  - repo: https://github.com/executablebooks/mdformat\n    rev: 0.7.17\n    hooks:\n      - id: mdformat\n        additional_dependencies:\n          [mdformat-gfm, mdformat-frontmatter, mdformat-footnote]\n\n  - repo: https://github.com/pre-commit/mirrors-prettier\n    rev: \"v4.0.0-alpha.8\"\n    hooks:\n      - id: prettier\n        types_or: [yaml, html, json]\n\n  - repo: https://github.com/adamchainz/blacken-docs\n    rev: \"1.16.0\"\n    hooks:\n      - id: blacken-docs\n        additional_dependencies: [black==23.7.0]\n\n  - repo: https://github.com/codespell-project/codespell\n    rev: \"v2.2.6\"\n    hooks:\n      - id: codespell\n        args: [\"-L\", \"sur,nd\"]\n\n  - repo: https://github.com/pre-commit/pygrep-hooks\n    rev: \"v1.10.0\"\n    hooks:\n      - id: rst-backticks\n      - id: rst-directive-colons\n      - id: rst-inline-touching-normal\n\n  - repo: https://github.com/pre-commit/mirrors-mypy\n    rev: \"v1.19.0\"\n    hooks:\n      - id: mypy\n        files: \"^nbclient\"\n        stages: [manual]\n        args: [\"--install-types\", \"--non-interactive\"]\n        additional_dependencies:\n          [\n            \"traitlets>=5.13\",\n            \"jupyter_core>=5.3.2\",\n            \"jupyter_client\",\n            \"nbformat\",\n          ]\n\n  - repo: https://github.com/astral-sh/ruff-pre-commit\n    rev: v0.2.0\n    hooks:\n      - id: ruff\n        types_or: [python, jupyter]\n        args: [\"--fix\", \"--show-fixes\"]\n        exclude: \"^tests/files/.*.ipynb\"\n      - id: ruff-format\n        types_or: [python, jupyter]\n        exclude: \"^tests/files/.*.ipynb\"\n\n  - repo: https://github.com/scientific-python/cookie\n    rev: \"2024.01.24\"\n    hooks:\n      - id: sp-repo-review\n        additional_dependencies: [\"repo-review[cli]\"]\n"
  },
  {
    "path": ".readthedocs.yml",
    "content": "# .readthedocs.yml\n# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\nsphinx:\n  configuration: docs/conf.py\n\nformats: all\n\npython:\n  install:\n    - method: pip\n      path: .\n      extra_requirements:\n        - docs\n\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.11\"\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changes in NBClient {#changelog}\n\n<!-- <START NEW CHANGELOG ENTRY> -->\n\n## 0.10.4\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.10.3...af9b77a952b78b2bd548945471114315d202afbf))\n\n### Merged PRs\n\n- Allow `display_id` to be `None` [#338](https://github.com/jupyter/nbclient/pull/338) ([@davidbrochart](https://github.com/davidbrochart), [@YannickJadoul](https://github.com/YannickJadoul), [@slayoo](https://github.com/slayoo))\n\n### Contributors to this release\n\nThe following people contributed discussions, new ideas, code and documentation contributions, and review.\nSee [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports).\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2025-12-19&to=2025-12-23&type=c))\n\n@davidbrochart ([activity](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2025-12-19..2025-12-23&type=Issues)) | @slayoo ([activity](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Aslayoo+updated%3A2025-12-19..2025-12-23&type=Issues)) | @YannickJadoul ([activity](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3AYannickJadoul+updated%3A2025-12-19..2025-12-23&type=Issues))\n\n<!-- <END NEW CHANGELOG ENTRY> -->\n\n## 0.10.3\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.10.2...b42ad03acc0bb1ed26db65ab72ac617679cbbb62))\n\n### Merged PRs\n\n- Drop Python 3.9, test 3.14 and pypy-3.11 [#337](https://github.com/jupyter/nbclient/pull/337) ([@davidbrochart](https://github.com/davidbrochart))\n- correct execution count in test for ipython 9.8 and above [#335](https://github.com/jupyter/nbclient/pull/335) ([@drorspei](https://github.com/drorspei), [@davidbrochart](https://github.com/davidbrochart))\n- Update contribution with pytest instructions, remove tox. [#331](https://github.com/jupyter/nbclient/pull/331) ([@dgrahn](https://github.com/dgrahn), [@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\nThe following people contributed discussions, new ideas, code and documentation contributions, and review.\nSee [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports).\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2024-12-19&to=2025-12-19&type=c))\n\n@davidbrochart ([activity](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2024-12-19..2025-12-19&type=Issues)) | @dgrahn ([activity](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adgrahn+updated%3A2024-12-19..2025-12-19&type=Issues)) | @drorspei ([activity](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adrorspei+updated%3A2024-12-19..2025-12-19&type=Issues))\n\n## 0.10.2\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.10.1...5a68cb361412d540e23fbc444cda75ede73c16d1))\n\n### Merged PRs\n\n- Drop Python 3.8, test PyPy 3.10 [#323](https://github.com/jupyter/nbclient/pull/323) ([@davidbrochart](https://github.com/davidbrochart))\n- Gracefully handle explicit transient=None [#322](https://github.com/jupyter/nbclient/pull/322) ([@callmephilip](https://github.com/callmephilip))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2024-11-29&to=2024-12-19&type=c))\n\n[@callmephilip](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Acallmephilip+updated%3A2024-11-29..2024-12-19&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2024-11-29..2024-12-19&type=Issues)\n\n## 0.10.1\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.10.0...e6df5842d471047afcb45595d95a39be549896e4))\n\n### Maintenance and upkeep improvements\n\n- Run docs on ubuntu [#314](https://github.com/jupyter/nbclient/pull/314) ([@blink1073](https://github.com/blink1073))\n\n### Other merged PRs\n\n- avoid deprecation warning for py313 [#320](https://github.com/jupyter/nbclient/pull/320) ([@lucascolley](https://github.com/lucascolley))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2024-03-13&to=2024-11-29&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2024-03-13..2024-11-29&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2024-03-13..2024-11-29&type=Issues) | [@lucascolley](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Alucascolley+updated%3A2024-03-13..2024-11-29&type=Issues)\n\n## 0.10.0\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.9.1...3286ae09f41d04fd3354519582750775abc034e5))\n\n### Enhancements made\n\n- Optionally write out executed notebook in jupyter-execute [#307](https://github.com/jupyter/nbclient/pull/307) ([@wpk-nist-gov](https://github.com/wpk-nist-gov))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2024-03-12&to=2024-03-12&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2024-03-12..2024-03-12&type=Issues) | [@wpk-nist-gov](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Awpk-nist-gov+updated%3A2024-03-12..2024-03-12&type=Issues)\n\n## 0.9.1\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.9.0...6f6aa8cb1a853c81975fcc48fa5cfcc3d37bcddd))\n\n### Maintenance and upkeep improvements\n\n- Update Release Scripts [#309](https://github.com/jupyter/nbclient/pull/309) ([@blink1073](https://github.com/blink1073))\n- Pin to Pytest 7 [#308](https://github.com/jupyter/nbclient/pull/308) ([@blink1073](https://github.com/blink1073))\n\n### Other merged PRs\n\n- chore: update pre-commit hooks [#305](https://github.com/jupyter/nbclient/pull/305) ([@pre-commit-ci](https://github.com/pre-commit-ci))\n- chore: update pre-commit hooks [#304](https://github.com/jupyter/nbclient/pull/304) ([@pre-commit-ci](https://github.com/pre-commit-ci))\n- chore: update pre-commit hooks [#303](https://github.com/jupyter/nbclient/pull/303) ([@pre-commit-ci](https://github.com/pre-commit-ci))\n- Bump actions/checkout from 3 to 4 [#302](https://github.com/jupyter/nbclient/pull/302) ([@dependabot](https://github.com/dependabot))\n- chore: update pre-commit hooks [#300](https://github.com/jupyter/nbclient/pull/300) ([@pre-commit-ci](https://github.com/pre-commit-ci))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2023-11-07&to=2024-03-12&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2023-11-07..2024-03-12&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adependabot+updated%3A2023-11-07..2024-03-12&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Apre-commit-ci+updated%3A2023-11-07..2024-03-12&type=Issues)\n\n## 0.9.0\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.8.0...31cf1e751935628b2ce8b88b7c00e5b53e9dcfd6))\n\n### Maintenance and upkeep improvements\n\n- Use jupyter releaser [#301](https://github.com/jupyter/nbclient/pull/301) ([@blink1073](https://github.com/blink1073))\n- Clean up lint and move tests out of source [#299](https://github.com/jupyter/nbclient/pull/299) ([@blink1073](https://github.com/blink1073))\n- Adopt ruff format [#298](https://github.com/jupyter/nbclient/pull/298) ([@blink1073](https://github.com/blink1073))\n- Update typings for mypy 1.6 [#297](https://github.com/jupyter/nbclient/pull/297) ([@blink1073](https://github.com/blink1073))\n- Adopt sp-repo-review [#295](https://github.com/jupyter/nbclient/pull/295) ([@blink1073](https://github.com/blink1073))\n- Fix lint error [#289](https://github.com/jupyter/nbclient/pull/289) ([@blink1073](https://github.com/blink1073))\n\n### Other merged PRs\n\n- Bump actions/checkout from 3 to 4 [#293](https://github.com/jupyter/nbclient/pull/293) ([@dependabot](https://github.com/dependabot))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2023-05-22&to=2023-11-07&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2023-05-22..2023-11-07&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adependabot+updated%3A2023-05-22..2023-11-07&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Apre-commit-ci+updated%3A2023-05-22..2023-11-07&type=Issues)\n\n## 0.8.0\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.7.4...cb7b4f7f409bbd06d55cc339afdcdea79da0e199))\n\n### Maintenance and upkeep improvements\n\n- Bump min version support [#287](https://github.com/jupyter/nbclient/pull/287) ([@blink1073](https://github.com/blink1073))\n\n### Other merged PRs\n\n- Bump actions/checkout from 2 to 3 [#275](https://github.com/jupyter/nbclient/pull/275) ([@dependabot](https://github.com/dependabot))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2023-04-25&to=2023-05-22&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2023-04-25..2023-05-22&type=Issues) | [@dependabot](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adependabot+updated%3A2023-04-25..2023-05-22&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Apre-commit-ci+updated%3A2023-04-25..2023-05-22&type=Issues)\n\n## 0.7.4\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.7.3...20b7d4b6eef33ccd1bbd8d346a7a75522ac67d75))\n\n### Enhancements made\n\n- include stream output in CellExecutionError [#282](https://github.com/jupyter/nbclient/pull/282) ([@minrk](https://github.com/minrk))\n\n### Bugs fixed\n\n- avoid duplicate 'Exception: message' in CellExecutionError [#283](https://github.com/jupyter/nbclient/pull/283) ([@minrk](https://github.com/minrk))\n\n### Maintenance and upkeep improvements\n\n- Use local coverage [#281](https://github.com/jupyter/nbclient/pull/281) ([@blink1073](https://github.com/blink1073))\n\n### Other merged PRs\n\n- Send KeyboardInterrupt a little later in test_run_all_notebooks\\[Interrupt.ipynb-opts6\\] [#285](https://github.com/jupyter/nbclient/pull/285) ([@kxxt](https://github.com/kxxt))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2023-04-03&to=2023-04-25&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2023-04-03..2023-04-25&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2023-04-03..2023-04-25&type=Issues) | [@kxxt](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Akxxt+updated%3A2023-04-03..2023-04-25&type=Issues) | [@minrk](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Aminrk+updated%3A2023-04-03..2023-04-25&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Apre-commit-ci+updated%3A2023-04-03..2023-04-25&type=Issues)\n\n## 0.7.3\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.7.2...aa62bc79274d264e9b9d70a139c9506a740b5d77))\n\n### Maintenance and upkeep improvements\n\n- Fix test stability [#276](https://github.com/jupyter/nbclient/pull/276) ([@blink1073](https://github.com/blink1073))\n- Clean up license [#274](https://github.com/jupyter/nbclient/pull/274) ([@dcsaba89](https://github.com/dcsaba89))\n- Update codecov link [#271](https://github.com/jupyter/nbclient/pull/271) ([@blink1073](https://github.com/blink1073))\n- Add spelling and docstring enforcement [#269](https://github.com/jupyter/nbclient/pull/269) ([@blink1073](https://github.com/blink1073))\n- Adopt ruff and address lint [#267](https://github.com/jupyter/nbclient/pull/267) ([@blink1073](https://github.com/blink1073))\n\n### Other merged PRs\n\n- Add coalesce_streams [#279](https://github.com/jupyter/nbclient/pull/279) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-11-29&to=2023-04-03&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2022-11-29..2023-04-03&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-11-29..2023-04-03&type=Issues) | [@dcsaba89](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adcsaba89+updated%3A2022-11-29..2023-04-03&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Apre-commit-ci+updated%3A2022-11-29..2023-04-03&type=Issues)\n\n## 0.7.2\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.7.1...e6f8b9f7001f9988a29bb011a0f6052987e6507a))\n\n### Merged PRs\n\n- Allow space after In [#264](https://github.com/jupyter/nbclient/pull/264) ([@davidbrochart](https://github.com/davidbrochart))\n- Fix jupyter_core pinning [#263](https://github.com/jupyter/nbclient/pull/263) ([@davidbrochart](https://github.com/davidbrochart))\n- Update README, add Python 3.11 [#260](https://github.com/jupyter/nbclient/pull/260) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-11-29&to=2022-11-29&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-11-29..2022-11-29&type=Issues)\n\n## 0.7.1\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.7.0...168340e8313e63fd9e037280f98ed22d47e2231b))\n\n### Maintenance and upkeep improvements\n\n- CI Refactor [#257](https://github.com/jupyter/nbclient/pull/257) ([@blink1073](https://github.com/blink1073))\n\n### Other merged PRs\n\n- Remove nest-asyncio [#259](https://github.com/jupyter/nbclient/pull/259) ([@davidbrochart](https://github.com/davidbrochart))\n- Add upper bound to dependencies [#258](https://github.com/jupyter/nbclient/pull/258) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-10-06&to=2022-11-29&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2022-10-06..2022-11-29&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-10-06..2022-11-29&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Apre-commit-ci+updated%3A2022-10-06..2022-11-29&type=Issues)\n\n## 0.7.0\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.6.8...449f17d0374f43694d2203d216c97dd4ac7f2c0e))\n\n### Maintenance and upkeep improvements\n\n- Cleanup CI [#254](https://github.com/jupyter/nbclient/pull/254) ([@blink1073](https://github.com/blink1073))\n- Handle client 8 support [#253](https://github.com/jupyter/nbclient/pull/253) ([@blink1073](https://github.com/blink1073))\n\n### Other merged PRs\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-09-09&to=2022-10-06&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2022-09-09..2022-10-06&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Apre-commit-ci+updated%3A2022-09-09..2022-10-06&type=Issues)\n\n## 0.6.8\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.6.7...f7d72b2c6937fc30add18b7413f89b691d1710be))\n\n### Merged PRs\n\n- Fix tests compatibility with IPython 8.5.0 [#251](https://github.com/jupyter/nbclient/pull/251) ([@frenzymadness](https://github.com/frenzymadness))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-08-23&to=2022-09-09&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-08-23..2022-09-09&type=Issues) | [@frenzymadness](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Afrenzymadness+updated%3A2022-08-23..2022-09-09&type=Issues)\n\n## 0.6.7\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.6.6...979fb908dc133cc80a698c74d9b3d9d8af6c7bde))\n\n### Merged PRs\n\n- Fix tests for ipywidgets 8 [#246](https://github.com/jupyter/nbclient/pull/246) ([@frenzymadness](https://github.com/frenzymadness))\n- \\[pre-commit.ci\\] pre-commit autoupdate [#236](https://github.com/jupyter/nbclient/pull/236) ([@pre-commit-ci](https://github.com/pre-commit-ci))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-07-01&to=2022-08-23&type=c))\n\n[@frenzymadness](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Afrenzymadness+updated%3A2022-07-01..2022-08-23&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Apre-commit-ci+updated%3A2022-07-01..2022-08-23&type=Issues)\n\n## 0.6.6\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.6.5...b4a7cebf0238d4fbe814e19afbee8df3f610e80d))\n\n### Merged PRs\n\n- Start new client if needed in blocking setup_kernel [#241](https://github.com/jupyter/nbclient/pull/241) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-06-30&to=2022-07-01&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-06-30..2022-07-01&type=Issues)\n\n## 0.6.5\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.6.4...6aed8bec58d69004d3b6687c8bf589f175630f8d))\n\n### Merged PRs\n\n- Start new client if needed [#239](https://github.com/jupyter/nbclient/pull/239) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-05-31&to=2022-06-30&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-05-31..2022-06-30&type=Issues)\n\n## 0.6.4\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.6.3...01465b8d8597efa81f54f713ad3944fe963ab453))\n\n### Merged PRs\n\n- Make sure kernel is cleaned up in case an error occurred while starting kernel client [#234](https://github.com/jupyter/nbclient/pull/234) ([@CiprianAnton](https://github.com/CiprianAnton))\n- Suppress most warnings in tests [#232](https://github.com/jupyter/nbclient/pull/232) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-05-09&to=2022-05-31&type=c))\n\n[@CiprianAnton](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3ACiprianAnton+updated%3A2022-05-09..2022-05-31&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-05-09..2022-05-31&type=Issues)\n\n## 0.6.3\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.6.2...61d36ce423b00231833c737f59041f33d72a7bb3))\n\n### Bugs fixed\n\n- Clean up docs and typings [#230](https://github.com/jupyter/nbclient/pull/230) ([@blink1073](https://github.com/blink1073))\n\n### Documentation improvements\n\n- Clean up docs and typings [#230](https://github.com/jupyter/nbclient/pull/230) ([@blink1073](https://github.com/blink1073))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-05-03&to=2022-05-09&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2022-05-03..2022-05-09&type=Issues) | [@chrisjsewell](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Achrisjsewell+updated%3A2022-05-03..2022-05-09&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-05-03..2022-05-09&type=Issues) | [@meeseeksmachine](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ameeseeksmachine+updated%3A2022-05-03..2022-05-09&type=Issues)\n\n## 0.6.2\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.6.1...bd36f50a299fb2e0656386ec487c2bbc67a9a1c4))\n\n### Merged PRs\n\n- Fix documentation generation [#228](https://github.com/jupyter/nbclient/pull/228) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-05-03&to=2022-05-03&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-05-03..2022-05-03&type=Issues)\n\n## 0.6.1\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.6.0...571a65faa7b86bb647567373a529d9d8df38dd2f))\n\n### Merged PRs\n\n- \\[pre-commit.ci\\] pre-commit autoupdate [#225](https://github.com/jupyter/nbclient/pull/225) ([@pre-commit-ci](https://github.com/pre-commit-ci))\n- Add error_on_interrupt trait [#224](https://github.com/jupyter/nbclient/pull/224) ([@davidbrochart](https://github.com/davidbrochart))\n- Fix typo [#223](https://github.com/jupyter/nbclient/pull/223) ([@davidbrochart](https://github.com/davidbrochart))\n- Add on_cell_executed hook [#222](https://github.com/jupyter/nbclient/pull/222) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-04-12&to=2022-05-03&type=c))\n\n[@brichet](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Abrichet+updated%3A2022-04-12..2022-05-03&type=Issues) | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-04-12..2022-05-03&type=Issues) | [@pre-commit-ci](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Apre-commit-ci+updated%3A2022-04-12..2022-05-03&type=Issues)\n\n## 0.6.0\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.5.13...295e0eee4a6b9c5c0ee0d490b4c4058a95c6cb79))\n\n### Maintenance and upkeep improvements\n\n- Fix typings and update mypy settings [#220](https://github.com/jupyter/nbclient/pull/220) ([@blink1073](https://github.com/blink1073))\n- Add missing dep on testpath [#219](https://github.com/jupyter/nbclient/pull/219) ([@blink1073](https://github.com/blink1073))\n- Add more pre-commit hooks and update flake8 [#218](https://github.com/jupyter/nbclient/pull/218) ([@blink1073](https://github.com/blink1073))\n\n### Documentation improvements\n\n- Clean up docs handling [#216](https://github.com/jupyter/nbclient/pull/216) ([@blink1073](https://github.com/blink1073))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-03-11&to=2022-04-12&type=c))\n\n[@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Ablink1073+updated%3A2022-03-11..2022-04-12&type=Issues)\n\n## 0.5.13\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.5.12...af2315aefbd8d08c1d6a473c289beef1e8ebbecb))\n\n### Merged PRs\n\n- Drop ipython_genutils [#209](https://github.com/jupyter/nbclient/pull/209) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-03-06&to=2022-03-11&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-03-06..2022-03-11&type=Issues)\n\n## 0.5.12\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.5.11...d20e29e803e5a22379f7a1356e7cf55d4649e9cb))\n\n### Merged PRs\n\n- Require traitlets>=5.0.0 [#204](https://github.com/jupyter/nbclient/pull/204) ([@davidbrochart](https://github.com/davidbrochart))\n- Extend the ignored part of IPython outputs [#202](https://github.com/jupyter/nbclient/pull/202) ([@frenzymadness](https://github.com/frenzymadness))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-02-14&to=2022-03-06&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-02-14..2022-03-06&type=Issues) | [@frenzymadness](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Afrenzymadness+updated%3A2022-02-14..2022-03-06&type=Issues)\n\n## 0.5.11\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.5.10...050c7da89a98159e6361b1ad0dbefd215db5f816))\n\n### Merged PRs\n\n- Pin ipython\\<8 in tests [#198](https://github.com/jupyter/nbclient/pull/198) ([@davidbrochart](https://github.com/davidbrochart))\n- Clear execution metadata, prefer msg header date when recording times [#195](https://github.com/jupyter/nbclient/pull/195) ([@kevin-bates](https://github.com/kevin-bates))\n- Client hooks [#188](https://github.com/jupyter/nbclient/pull/188) ([@devintang3](https://github.com/devintang3))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2022-01-13&to=2022-02-14&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2022-01-13..2022-02-14&type=Issues) | [@devintang3](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adevintang3+updated%3A2022-01-13..2022-02-14&type=Issues) | [@kevin-bates](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Akevin-bates+updated%3A2022-01-13..2022-02-14&type=Issues)\n\n## 0.5.10\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.5.9...e82c5d8d064ac1097f4e12f387b4c47ea5c576ff))\n\n### Merged PRs\n\n- Fix ipywidgets version in tests [#192](https://github.com/jupyter/nbclient/pull/192) ([@martinRenou](https://github.com/martinRenou))\n- Compatibility with IPython 8 where tracebacks are different [#190](https://github.com/jupyter/nbclient/pull/190) ([@frenzymadness](https://github.com/frenzymadness))\n- Drop tox [#187](https://github.com/jupyter/nbclient/pull/187) ([@davidbrochart](https://github.com/davidbrochart))\n- Update README [#185](https://github.com/jupyter/nbclient/pull/185) ([@davidbrochart](https://github.com/davidbrochart))\n- Drop python3.6, test python3.10 [#184](https://github.com/jupyter/nbclient/pull/184) ([@davidbrochart](https://github.com/davidbrochart))\n- Fix typos [#182](https://github.com/jupyter/nbclient/pull/182) ([@kianmeng](https://github.com/kianmeng))\n- Use codecov Github action v2 [#168](https://github.com/jupyter/nbclient/pull/168) ([@takluyver](https://github.com/takluyver))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2021-11-19&to=2022-01-13&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2021-11-19..2022-01-13&type=Issues) | [@frenzymadness](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Afrenzymadness+updated%3A2021-11-19..2022-01-13&type=Issues) | [@kianmeng](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Akianmeng+updated%3A2021-11-19..2022-01-13&type=Issues) | [@martinRenou](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3AmartinRenou+updated%3A2021-11-19..2022-01-13&type=Issues) | [@takluyver](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Atakluyver+updated%3A2021-11-19..2022-01-13&type=Issues)\n\n## 0.5.9\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/v0.5.8...0146681d7ffd62cbc675c8d1463a2b016a3d3008))\n\n### Merged PRs\n\n- Remove jupyter-run, keep jupyter-execute [#180](https://github.com/jupyter/nbclient/pull/180) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2021-11-12&to=2021-11-19&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2021-11-12..2021-11-19&type=Issues)\n\n## 0.5.8\n\nNo merged PRs\n\n## 0.5.7\n\n([Full Changelog](https://github.com/jupyter/nbclient/compare/0.5.6...d86c404536fb443898b631acaf29ce7ad88b06d9))\n\n### Merged PRs\n\n- Prepare for use with Jupyter Releaser [#175](https://github.com/jupyter/nbclient/pull/175) ([@davidbrochart](https://github.com/davidbrochart))\n\n### Contributors to this release\n\n([GitHub contributors page for this release](https://github.com/jupyter/nbclient/graphs/contributors?from=2021-11-12&to=2021-11-12&type=c))\n\n[@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fnbclient+involves%3Adavidbrochart+updated%3A2021-11-12..2021-11-12&type=Issues)\n\n## 0.5.6\n\n- Changed `jupyter execute` to `jupyter run` [#173](https://github.com/jupyter/nbclient/pull/173) ([@palewire](https://github.com/palewire))\n- Move IPYKERNEL_CELL_NAME from tox to pytest [#172](https://github.com/jupyter/nbclient/pull/172) ([@frenzymadness](https://github.com/frenzymadness))\n\n## 0.5.5\n\n- Added CLI to README [#170](https://github.com/jupyter/nbclient/pull/170) ([@palewire](https://github.com/palewire))\n- Add \"jupyter execute\" command-line interface [#165](https://github.com/jupyter/nbclient/pull/165) ([@palewire](https://github.com/palewire))\n- Fix: updating buffers overwrote previous buffers [#169](https://github.com/jupyter/nbclient/pull/169) ([@maartenbreddels](https://github.com/maartenbreddels))\n- Fix tests for ipykernel without debugpy [#166](https://github.com/jupyter/nbclient/pull/166) ([@frenzymadness](https://github.com/frenzymadness))\n- gitignore Pipfile [#164](https://github.com/jupyter/nbclient/pull/164) ([@palewire](https://github.com/palewire))\n- Fixed CONTRIBUTING.md link [#163](https://github.com/jupyter/nbclient/pull/163) ([@palewire](https://github.com/palewire))\n- Fix typo [#162](https://github.com/jupyter/nbclient/pull/162) ([@The-Compiler](https://github.com/The-Compiler))\n- Move format & lint to pre-commit [#161](https://github.com/jupyter/nbclient/pull/161) ([@chrisjsewell](https://github.com/chrisjsewell))\n- Add `skip-execution` cell tag functionality [#151](https://github.com/jupyter/nbclient/pull/151) ([@chrisjsewell](https://github.com/chrisjsewell))\n\n## 0.5.4\n\n- Replace `km.cleanup` with `km.cleanup_resources` [#152](https://github.com/jupyter/nbclient/pull/152) ([@davidbrochart](https://github.com/davidbrochart))\n- Use async generator backport only on old python [#154](https://github.com/jupyter/nbclient/pull/154) ([@mkoeppe](https://github.com/mkoeppe))\n- Support parsing of IPython dev version [#150](https://github.com/jupyter/nbclient/pull/150) ([@cphyc](https://github.com/cphyc))\n- Set `IPYKERNEL_CELL_NAME = <IPY-INPUT>` [#147](https://github.com/jupyter/nbclient/pull/147) ([@davidbrochart](https://github.com/davidbrochart))\n- Print useful error message on exception [#142](https://github.com/jupyter/nbclient/pull/142) ([@certik](https://github.com/certik))\n\n## 0.5.3\n\n- Fix ipykernel's `stop_on_error` value to take into account `raises-exception` tag and `force_raise_errors` [#137](https://github.com/jupyter/nbclient/pull/137)\n\n## 0.5.2\n\n- Set minimum python version supported to 3.6.1 to avoid 3.6.0 issues\n- CellExecutionError is now unpickleable\n- Added testing for python 3.9\n- Changed travis tests to github actions\n- Documentation referencing an old model instead of NotebookClient was fixed\n- `allow_error_names` option was added for a more specific scope of `allow_errors` to be applied\n\n## 0.5.1\n\n- Update kernel client class JIT if it's the synchronous version\n- Several documentation fixes / improvements\n\n## 0.5.0\n\n- Move `language_info` retrieval before cell execution [#102](https://github.com/jupyter/nbclient/pull/102)\n- HistoryManager setting for ipython kernels no longer applies twice (fix for 5.0 trailets release)\n- Improved error handling around language_info missing\n- `(async_)start_new_kernel_client` is now split into `(async_)start_new_kernel` and `(async_)start_new_kernel_client`\n\n## 0.4.2 - 0.4.3\n\nThese patch releases were removed due to backwards incompatible changes that should have been a minor release.\nIf you were using these versions for the couple days they were up, move to 0.5.0 and you shouldn't have any issues.\n\n## 0.4.1\n\n- Python type hinting added to most interfaces! [#83](https://github.com/jupyter/nbclient/pull/83)\n- Several documentation fixes and improvements were made [#86](https://github.com/jupyter/nbclient/pull/86)\n- An asynchronous heart beat check was added to correctly raise a DeadKernelError when kernels die unexpectantly [#90](https://github.com/jupyter/nbclient/pull/90)\n\n## 0.4.0\n\n### Major Changes\n\n- Use KernelManager's graceful shutdown rather than KILLing kernels [#64](https://github.com/jupyter/nbclient/pull/64)\n- Mimic an Output widget at the frontend so that the Output widget behaves correctly [#68](https://github.com/jupyter/nbclient/pull/68)\n- Nested asyncio is automatic, and works with Tornado [#71](https://github.com/jupyter/nbclient/pull/71)\n- `async_execute` now has a `reset_kc` argument to control if the client is reset upon execution request [#53](https://github.com/jupyter/nbclient/pull/53)\n\n### Fixes\n\n- Fix `OSError: [WinError 6] The handle is invalid` for windows/python\\<3.7 [#77](https://github.com/jupyter/nbclient/pull/77)\n- Async wrapper Exceptions no longer loose their caused exception information [#65](https://github.com/jupyter/nbclient/pull/65)\n- `extra_arguments` are now configurable by config settings [#66](https://github.com/jupyter/nbclient/pull/66)\n\n### Operational\n\n- Cross-OS testing now run on PRs via Github Actions [#63](https://github.com/jupyter/nbclient/pull/63)\n\n## 0.3.1\n\n### Fixes\n\n- Check that a kernel manager exists before cleaning up the kernel [#61](https://github.com/jupyter/nbclient/pull/61)\n- Force client class to be async when kernel manager is MultiKernelManager [#55](https://github.com/jupyter/nbclient/pull/55)\n- Replace pip install with conda install in Binder [#54](https://github.com/jupyter/nbclient/pull/54)\n\n## 0.3.0\n\n### Major Changes\n\n- The `(async_)start_new_kernel_client` method now supports starting a new client when its kernel manager (`self.km`) is a `MultiKernelManager`. The method now returns the kernel id in addition to the kernel client. If the kernel manager was a `KernelManager`, the returned kernel id is `None`. [#51](https://github.com/jupyter/nbclient/pull/51)\n- Added sphinx-book-theme for documentation. Added a CircleCI job to let us preview the built documentation in a PR. [#50](https://github.com/jupyter/nbclient/pull/50)\n- Added `reset_kc` option to `reset_execution_trackers`, so that the kernel client can be reset and a new one created in calls to `(async_)execute` [#44](https://github.com/jupyter/nbclient/pull/44)\n\n### Docs\n\n- Fixed documentation [#46](https://github.com/jupyter/nbclient/pull/46) [#47](https://github.com/jupyter/nbclient/pull/47)\n- Added documentation status badge to the README\n- Removed conda from documentation build\n\n## 0.2.0\n\n### Major Changes\n\n- Async support is now available on the client. Methods that support async have an `async_` prefix and can be awaited [#10](https://github.com/jupyter/nbclient/pull/10) [#35](https://github.com/jupyter/nbclient/pull/35) [#37](https://github.com/jupyter/nbclient/pull/37) [#38](https://github.com/jupyter/nbclient/pull/38)\n- Dropped support for Python 3.5 due to async compatibility issues [#34](https://github.com/jupyter/nbclient/pull/34)\n- Notebook documents now include the [new kernel timing fields](https://github.com/jupyter/nbformat/pull/144) [#32](https://github.com/jupyter/nbclient/pull/32)\n\n### Fixes\n\n- Memory and process leaks from nbclient should now be fixed [#34](https://github.com/jupyter/nbclient/pull/34)\n- Notebook execution exceptions now include error information in addition to the message [#41](https://github.com/jupyter/nbclient/pull/41)\n\n### Docs\n\n- Added [binder examples](https://mybinder.org/v2/gh/jupyter/nbclient/master?filepath=binder%2Frun_nbclient.ipynb) / tests [#7](https://github.com/jupyter/nbclient/pull/7)\n- Added changelog to docs [#22](https://github.com/jupyter/nbclient/pull/22)\n- Doc typo fixes [#27](https://github.com/jupyter/nbclient/pull/27) [#30](https://github.com/jupyter/nbclient/pull/30)\n\n## 0.1.0\n\n- Initial release -- moved out of nbconvert 6.0.0-a0\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nWe follow the [Jupyter Contribution Workflow](https://jupyter.readthedocs.io/en/latest/contributing/content-contributor.html) and the [IPython Contributing Guide](https://github.com/ipython/ipython/blob/master/CONTRIBUTING.md).\n\n## Code formatting\n\nUse the [pre-commit](https://pre-commit.com/) tool to format and lint the codebase:\n\n```console\n# to apply to only staged files\n$ pre-commit run\n# to run against all files\n$ pre-commit run --all-files\n# to install so that it is run before commits\n$ pre-commit install\n```\n\n## Testing\n\nTests can be run through [`hatch`](https://hatch.pypa.io/) which will automatically manage test environments and dependencies.\n\n```console\n# to run all tests\n$ hatch run test:test\n\n# to run with coverage (used by CI)\n$ hatch run cov:test\n```\n\n## Documentation\n\nNbClient needs some PRs to copy over documentation!\n\n## Releasing\n\nIf you are going to release a version of `nbclient` you should also be capable\nof testing it and building the docs.\n\nPlease follow the instructions in [Testing](#testing) and [Documentation](#documentation) if\nyou are unfamiliar with how to do so.\n\nThe rest of the release process can be found in [these release instructions](./RELEASING.md).\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD 3-Clause License\n\nCopyright (c) 2020-, Jupyter Development Team\n\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyter/nbclient/main?filepath=binder%2Frun_nbclient.ipynb)\n[![Build Status](https://github.com/jupyter/nbclient/workflows/CI/badge.svg)](https://github.com/jupyter/nbclient/actions)\n[![Documentation Status](https://readthedocs.org/projects/nbclient/badge/?version=latest)](https://nbclient.readthedocs.io/en/latest/?badge=latest)\n[![Python 3.7](https://img.shields.io/badge/python-3.7-blue.svg)](https://www.python.org/downloads/release/python-370/)\n[![Python 3.8](https://img.shields.io/badge/python-3.8-blue.svg)](https://www.python.org/downloads/release/python-380/)\n[![Python 3.9](https://img.shields.io/badge/python-3.9-blue.svg)](https://www.python.org/downloads/release/python-390/)\n[![Python 3.10](https://img.shields.io/badge/python-3.10-blue.svg)](https://www.python.org/downloads/release/python-3100/)\n[![Python 3.11](https://img.shields.io/badge/python-3.11-blue.svg)](https://www.python.org/downloads/release/python-3110/)\n[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)\n\n# nbclient\n\n**NBClient** lets you **execute** notebooks.\n\nA client library for programmatic notebook execution, **NBClient** is a tool for running Jupyter Notebooks in\ndifferent execution contexts, including the command line.\n\n## Interactive Demo\n\nTo demo **NBClient** interactively, click this Binder badge to start the demo:\n\n[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyter/nbclient/main?filepath=binder%2Frun_nbclient.ipynb)\n\n## Installation\n\nIn a terminal, run:\n\n```\npython3 -m pip install nbclient\n```\n\n## Documentation\n\nSee [ReadTheDocs](https://nbclient.readthedocs.io/en/latest/) for more in-depth details about the project and the\n[API Reference](https://nbclient.readthedocs.io/en/latest/reference/index.html).\n\n## Python Version Support\n\nThis library currently supports Python 3.6+ versions. As minor Python\nversions are officially sunset by the Python org, nbclient will similarly\ndrop support in the future.\n\n## Origins\n\nThis library used to be part of the [nbconvert](https://nbconvert.readthedocs.io/en/latest/) project. NBClient extracted nbconvert's `ExecutePreprocessor`into its own library for easier updating and importing by downstream libraries and applications.\n\n## Relationship to JupyterClient\n\nNBClient and JupyterClient are distinct projects.\n\n`jupyter_client` is a client library for the jupyter protocol. Specifically, `jupyter_client` provides the Python API\nfor starting, managing and communicating with Jupyter kernels.\n\nWhile, nbclient allows notebooks to be run in different execution contexts.\n\n## About the Jupyter Development Team\n\nThe Jupyter Development Team is the set of all contributors to the Jupyter project.\nThis includes all of the Jupyter subprojects.\n\nThe core team that coordinates development on GitHub can be found here:\nhttps://github.com/jupyter/.\n\n## Our Copyright Policy\n\nJupyter uses a shared copyright model. Each contributor maintains copyright\nover their contributions to Jupyter. But, it is important to note that these\ncontributions are typically only changes to the repositories. Thus, the Jupyter\nsource code, in its entirety is not the copyright of any single person or\ninstitution.  Instead, it is the collective copyright of the entire Jupyter\nDevelopment Team.  If individual contributors want to maintain a record of what\nchanges/contributions they have specific copyright on, they should indicate\ntheir copyright in the commit message of the change, when they commit the\nchange to one of the Jupyter repositories.\n\nWith this in mind, the following banner should be used in any source code file\nto indicate the copyright and license terms:\n\n```\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\n```\n"
  },
  {
    "path": "RELEASING.md",
    "content": "# Releasing\n\n## Using `jupyter_releaser`\n\nThe recommended way to make a release is to use [`jupyter_releaser`](https://github.com/jupyter-server/jupyter_releaser#checklist-for-adoption).\n\n## Manual Release\n\n### Prerequisites\n\n- First check that the CHANGELOG.md is up to date for the next release version\n- Install packaging requirements: `pip install tbump build tomlkit==0.7.0`\n\n### Bump version\n\n- `export version=<NEW_VERSION>`\n- `tbump ${version} --no-push`\n\n### Push to GitHub\n\n```bash\ngit push upstream && git push upstream --tags\n```\n\n### Push to PyPI\n\n```bash\nrm -rf dist/*\nrm -rf build/*\npython -m build .\ntwine upload dist/*\n```\n"
  },
  {
    "path": "binder/empty_notebook.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Show a pandas dataframe\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import numpy as np\\n\",\n    \"import pandas as pd\\n\",\n    \"import scrapbook as sb\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"data = pd.DataFrame(np.random.randn(20, 2), columns=[\\\"a\\\", \\\"b\\\"])\\n\",\n    \"data\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Use scrapbook to store this data in the notebook\\n\",\n    \"sb.glue(\\\"dataframe\\\", data.to_dict())\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Make a matplotlib plot\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import matplotlib.pyplot as plt\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# Make and display a plot\\n\",\n    \"fig, ax = plt.subplots()\\n\",\n    \"ax.scatter(data[\\\"a\\\"], data[\\\"b\\\"])\\n\",\n    \"sb.glue(\\\"plot\\\", fig, \\\"display\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.3\"\n  },\n  \"widgets\": {\n   \"application/vnd.jupyter.widget-state+json\": {\n    \"state\": {},\n    \"version_major\": 2,\n    \"version_minor\": 0\n   }\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "binder/environment.yml",
    "content": "name: nbclient\nchannels:\n  - conda-forge\ndependencies:\n  - numpy\n  - pandas\n  - matplotlib\n  - scrapbook\n  - nbformat\n  - nbclient\n"
  },
  {
    "path": "binder/run_nbclient.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import nbformat as nbf\\n\",\n    \"import pandas as pd\\n\",\n    \"import scrapbook as sb\\n\",\n    \"\\n\",\n    \"import nbclient\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Background\\n\",\n    \"\\n\",\n    \"This notebook uses `nbclient` to read and execute an *empty* notebook.\\n\",\n    \"The empty notebook generates some fake data, makes a plot, and stores\\n\",\n    \"both the data and the plot inside the notebook using the\\n\",\n    \"[scrapbook package](https://github.com/nteract/scrapbook). We will\\n\",\n    \"then be able to access the generated contents of the notebook here.\\n\",\n    \"\\n\",\n    \"You can see the empty notebook by clicking this button:\\n\",\n    \"\\n\",\n    \"<a href=\\\"empty_notebook.ipynb\\\"><button>Empty notebook</button></a>\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Read and execute the empty notebook\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# We use nbformat to represent our empty notebook in-memory\\n\",\n    \"nb = nbf.read(\\\"./empty_notebook.ipynb\\\", nbf.NO_CONVERT)\\n\",\n    \"\\n\",\n    \"# Execute our in-memory notebook, which will now have outputs\\n\",\n    \"nb = nbclient.execute(nb)\"\n   ]\n  },\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Inspect the new notebook for its contents\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# First we'll convert our nbformat NotebokNote into a *scrapbook* NotebookNode\\n\",\n    \"nb = sb.read_notebook(nb)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# We can access the dataframe that was created and glued into the empty notebook\\n\",\n    \"pd.DataFrame.from_dict(nb.scraps.get(\\\"dataframe\\\").data).head()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# We can also access the generated plot by \\\"re-gluing\\\" the notebook here\\n\",\n    \"nb.reglue(\\\"plot\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.3\"\n  },\n  \"widgets\": {\n   \"application/vnd.jupyter.widget-state+json\": {\n    \"state\": {},\n    \"version_major\": 2,\n    \"version_minor\": 0\n   }\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Minimal makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nSPHINXPROJ    = nbclient\nSOURCEDIR     = .\nBUILDDIR      = _build\n\n# Put it first so that \"make\" without argument is like \"make help\".\nhelp:\n\t@$(SPHINXBUILD) -M help \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n\n.PHONY: help Makefile\n\n# Catch-all target: route all unknown targets to Sphinx using the new\n# \"make mode\" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).\n%: Makefile\n\t@$(SPHINXBUILD) -M $@ \"$(SOURCEDIR)\" \"$(BUILDDIR)\" $(SPHINXOPTS) $(O)\n"
  },
  {
    "path": "docs/UPDATE.md",
    "content": "TODO: Figure out make options needed for non-api changes\n\n```\nsphinx-apidoc -f -o reference ../nbclient\n```\n"
  },
  {
    "path": "docs/_static/custom.css",
    "content": "img.logo {\n  width:100%\n}\n\n.right-next {\n    float: right;\n    max-width: 45%;\n    overflow: auto;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n}\n\n.right-next::after{\n    content: ' »';\n}\n\n.left-prev {\n    float: left;\n    max-width: 45%;\n    overflow: auto;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n}\n\n.left-prev::before{\n    content: '« ';\n}\n\n.prev-next-bottom {\n  margin-top: 3em;\n}\n\n.prev-next-top {\n  margin-bottom: 1em;\n}\n"
  },
  {
    "path": "docs/autogen_config.py",
    "content": "#!/usr/bin/env python\n\"\"\"\nautogen_config.py\n\nCreate config_options.rst, a Sphinx documentation source file.\nDocuments the options that may be set in nbconvert's configuration file,\njupyter_nbconvert_config.py.\n\n\"\"\"\nimport os\nimport os.path\n\nfrom nbclient.cli import NbClientApp\n\nheader = \"\"\"\\\n\n.. This is an automatically generated file.\n.. do not modify by hand.\n\n.. _other-full-config:\n\nConfig file and command line options\n====================================\nJupyter ``nbclient`` can be run with a variety of command line arguments.\nA list of available options can be found below in the :ref:`options section\n<options>`.\n\n.. _options:\n\nOptions\n-------\nThis list of options can be generated by running the following and hitting\nenter::\n\n  $ jupyter execute --help-all\n\n\"\"\"\n\ntry:\n    indir = os.path.dirname(__file__)\nexcept NameError:\n    indir = os.getcwd()\n\ndestination = os.path.join(indir, \"reference/config_options.rst\")\n\nwith open(destination, \"w\") as f:\n    app = NbClientApp()\n    f.write(header)\n    f.write(app.document_config_options())\n"
  },
  {
    "path": "docs/client.rst",
    "content": "Executing notebooks\n===================\n\n.. module:: nbclient.client.guide\n\nJupyter notebooks are often saved with output cells that have been cleared.\nNBClient provides a convenient way to execute the input cells of an\n.ipynb notebook file and save the results, both input and output cells,\nas a .ipynb file.\n\nIn this section we show how to execute a ``.ipynb`` notebook\ndocument saving the result in notebook format. If you need to export\nnotebooks to other formats, such as reStructured Text or Markdown (optionally\nexecuting them) see `nbconvert <https://nbconvert.readthedocs.io/en/latest/>`_.\n\nExecuting notebooks can be very helpful, for example, to run all notebooks\nin Python library in one step, or as a way to automate the data analysis in\nprojects involving more than one notebook.\n\nUsing the Python API interface\n------------------------------\n\nThis section will illustrate the Python API interface.\n\nExample\n~~~~~~~\n\nLet's start with a complete quick example, leaving detailed explanations\nto the following sections.\n\n**Import**: First we import nbformat and the :class:`NotebookClient`\nclass::\n\n    import nbformat\n    from nbclient import NotebookClient\n\n**Load**: Assuming that ``notebook_filename`` contains the path to a notebook,\nwe can load it with::\n\n    nb = nbformat.read(notebook_filename, as_version=4)\n\n**Configure**: Next, we configure the notebook execution mode::\n\n    client = NotebookClient(nb, timeout=600, kernel_name='python3', resources={'metadata': {'path': 'notebooks/'}})\n\nWe specified two (optional) arguments ``timeout`` and ``kernel_name``, which\ndefine respectively the cell execution timeout and the execution kernel.\nUsually you don't need to set these options, but these and other options are\navailable to control execution context. Note that ``path`` specifies\nin which folder to execute the notebook.\n\n**Execute/Run**: To actually run the notebook we call the method\n``execute``::\n\n    client.execute()\n\nHopefully, we will not get any errors during the notebook execution\n(see the last section for error handling). This notebook will\nnow have its cell outputs populated with the result of running\neach cell.\n\n**Save**: Finally, save the resulting notebook with::\n\n    nbformat.write(nb, 'executed_notebook.ipynb')\n\nThat's all. Your executed notebook will be saved in the current folder\nin the file ``executed_notebook.ipynb``.\n\nExecution arguments (traitlets)\n-------------------------------\n\nThe arguments passed to :class:`NotebookClient` are configuration options\ncalled `traitlets <https://traitlets.readthedocs.io/en/stable>`_.\nThere are many cool things about traitlets. For example,\nthey enforce the input type, and they can be accessed/modified as\nclass attributes.\n\nLet's now discuss in more detail the two traitlets we used.\n\nThe ``timeout`` traitlet defines the maximum time (in seconds) each notebook\ncell is allowed to run, if the execution takes longer an exception will be\nraised. The default is 30 s, so in cases of long-running cells you may want to\nspecify an higher value. The ``timeout`` option can also be set to ``None``\nor ``-1`` to remove any restriction on execution time.\n\nThe second traitlet, ``kernel_name``, allows specifying the name of the kernel\nto be used for the execution. By default, the kernel name is obtained from the\nnotebook metadata. The traitlet ``kernel_name`` allows specifying a\nuser-defined kernel, overriding the value in the notebook metadata. A common\nuse case is that of a Python 2/3 library which includes documentation/testing\nnotebooks. These notebooks will specify either a python2 or python3 kernel in\ntheir metadata (depending on the kernel used the last time the notebook was\nsaved). In reality, these notebooks will work on both Python 2 and Python 3,\nand, for testing, it is important to be able to execute them programmatically\non both versions. Here the traitlet ``kernel_name`` helps simplify and\nmaintain consistency: we can just run a notebook twice, specifying first\n\"python2\" and then \"python3\" as the kernel name.\n\nHooks before and after notebook or cell execution\n-------------------------------------------------\nThere are several configurable hooks that allow the user to execute code before and\nafter a notebook or a cell is executed. Each one is configured with a function that will be called in its\nrespective place in the execution pipeline.\nEach is described below:\n\n**Notebook-level hooks**: These hooks are called with a single extra parameter:\n\n- ``notebook=NotebookNode``: the current notebook being executed.\n\nHere is the available hooks:\n\n- ``on_notebook_start`` will run when the notebook client is initialized, before any execution has happened.\n- ``on_notebook_complete`` will run when the notebook client has finished executing, after kernel cleanup.\n- ``on_notebook_error`` will run when the notebook client has encountered an exception before kernel cleanup.\n\n**Cell-level hooks**: These hooks are called with at least two parameters:\n\n- ``cell=NotebookNode``: a reference to the current cell.\n- ``cell_index=int``: the index of the cell in the current notebook's list of cells.\n\nHere are the available hooks:\n\n- ``on_cell_start`` will run for all cell types before the cell is executed.\n- ``on_cell_execute`` will run right before the code cell is executed.\n- ``on_cell_complete`` will run after execution, if the cell is executed with no errors.\n- ``on_cell_executed`` will run right after the code cell is executed.\n- ``on_cell_error`` will run if there is an error during cell execution.\n\n``on_cell_executed`` and ``on_cell_error`` are called with an extra parameter ``execute_reply=dict``.\n\n\nHandling errors and exceptions\n------------------------------\n\nIn the previous sections we saw how to save an executed notebook, assuming\nthere are no execution errors. But, what if there are errors?\n\nExecution until first error\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\nAn error during the notebook execution, by default, will stop the execution\nand raise a ``CellExecutionError``. Conveniently, the source cell causing\nthe error and the original error name and message are also printed.\nAfter an error, we can still save the notebook as before::\n\n    nbformat.write(nb, 'executed_notebook.ipynb')\n\nThe saved notebook contains the output up until the failing cell,\nand includes a full stack-trace and error (which can help debugging).\n\nHandling errors\n~~~~~~~~~~~~~~~\nA useful pattern to execute notebooks while handling errors is the following::\n\n    from nbclient.exceptions import CellExecutionError\n\n    try:\n        client.execute()\n    except CellExecutionError:\n        msg = 'Error executing the notebook \"%s\".\\n\\n' % notebook_filename\n        msg += 'See notebook \"%s\" for the traceback.' % notebook_filename_out\n        print(msg)\n        raise\n    finally:\n        nbformat.write(nb, notebook_filename_out)\n\nThis will save the executed notebook regardless of execution errors.\nIn case of errors, however, an additional message is printed and the\n``CellExecutionError`` is raised. The message directs the user to\nthe saved notebook for further inspection.\n\nExecute and save all errors\n~~~~~~~~~~~~~~~~~~~~~~~~~~~\nAs a last scenario, it is sometimes useful to execute notebooks which raise\nexceptions, for example to show an error condition. In this case, instead of\nstopping the execution on the first error, we can keep executing the notebook\nusing the traitlet ``allow_errors`` (default is False). With\n``allow_errors=True``, the notebook is executed until the end, regardless of\nany error encountered during the execution. The output notebook, will contain\nthe stack-traces and error messages for **all** the cells raising exceptions.\n\nWidget state\n------------\n\nIf your notebook contains any\n`Jupyter Widgets <https://github.com/jupyter-widgets/ipywidgets/>`_,\nthe state of all the widgets can be stored in the notebook's metadata.\nThis allows rendering of the live widgets on for instance nbviewer, or when\nconverting to html.\n\nWe can tell nbclient to not store the state using the ``store_widget_state``\nargument::\n\n    client = NotebookClient(nb, store_widget_state=False)\n\nThis widget rendering is not performed against a browser during execution, so\nonly widget default states or states manipulated via user code will be\ncalculated during execution. ``%%javascript`` cells will execute upon notebook\nrendering, enabling complex interactions to function as expected when viewed by\na UI.\n\nIf you can't view widget results after execution, you may need to select\n:menuselection:`Trust Notebook` under the :menuselection:`File` menu.\n\nUsing a command-line interface\n------------------------------\n\nThis section will illustrate how to run notebooks from your terminal. It supports the most basic use case. For more sophisticated execution options, consider the `papermill <https://pypi.org/project/papermill/>`_ library.\n\nThis library's command line tool is available by running ``jupyter execute``. It expects notebooks as input arguments and accepts optional flags to modify the default behavior.\n\nRunning a notebook is this easy.::\n\n    jupyter execute notebook.ipynb\n\nYou can pass more than one notebook as well.::\n\n    jupyter execute notebook.ipynb notebook2.ipynb\n\nBy default, notebook errors will be raised and printed into the terminal. You can suppress them by passing the ``--allow-errors`` flag.::\n\n    jupyter execute notebook.ipynb --allow-errors\n\nOther options allow you to modify the timeout length and dictate the kernel in use. A full set of options is available via the help command.::\n\n    jupyter execute --help\n\n    An application used to execute notebook files (*.ipynb)\n\n    Options\n    =======\n    The options below are convenience aliases to configurable class-options,\n    as listed in the \"Equivalent to\" description-line of the aliases.\n    To see all configurable class-options for some <cmd>, use:\n        <cmd> --help-all\n\n    --allow-errors\n        Errors are ignored and execution is continued until the end of the notebook.\n        Equivalent to: [--NbClientApp.allow_errors=True]\n    --timeout=<Int>\n        The time to wait (in seconds) for output from executions. If a cell\n        execution takes longer, a TimeoutError is raised. ``-1`` will disable the\n        timeout.\n        Default: None\n        Equivalent to: [--NbClientApp.timeout]\n    --startup_timeout=<Int>\n        The time to wait (in seconds) for the kernel to start. If kernel startup\n        takes longer, a RuntimeError is raised.\n        Default: 60\n        Equivalent to: [--NbClientApp.startup_timeout]\n    --kernel_name=<Unicode>\n        Name of kernel to use to execute the cells. If not set, use the kernel_spec\n        embedded in the notebook.\n        Default: ''\n        Equivalent to: [--NbClientApp.kernel_name]\n\n    To see all available configurables, use `--help-all`.\n"
  },
  {
    "path": "docs/conf.py",
    "content": "#!/usr/bin/env python3\n#\n# nbclient documentation build configuration file, created by\n# sphinx-quickstart on Mon Jan 26 16:00:00 2020.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\n#\nimport os\nimport shutil\nimport sys\n\nimport nbclient\n\nsys.path.insert(0, os.path.abspath(\"..\"))\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#\n# needs_sphinx = '1.0'\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.intersphinx\",\n    \"sphinx.ext.mathjax\",\n    \"sphinx.ext.napoleon\",\n    # 'autodoc_traits',  # TODO\n    \"myst_parser\",\n]\n\ntry:\n    import enchant  # type:ignore  # noqa\n\n    extensions += [\"sphinxcontrib.spelling\"]\nexcept ImportError:\n    pass\n\nautodoc_mock_imports = [\"pytest\", \"nbconvert\", \"testpath\"]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = [\"_templates\"]\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n#\nsource_suffix = [\".rst\", \".md\"]\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# General information about the project.\nproject = \"nbclient\"\ncopyright = \"2020, Project Jupyter\"  # noqa\nauthor = \"Project Jupyter\"\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n\n# The short X.Y version.\nversion = \".\".join(nbclient.__version__.split(\".\")[0:2])\n\n# The full version, including alpha/beta/rc tags.\nrelease = nbclient.__version__\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line foexitr these cases.\nlanguage = \"en\"\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This patterns also effect to html_static_path and html_extra_path\nexclude_patterns = [\"_build\", \"Thumbs.db\", \".DS_Store\", \"UPDATE.md\"]\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = \"sphinx\"\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = False\n\ndefault_role = \"any\"\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = \"sphinx_book_theme\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#\n\nhtml_theme_options = {\n    \"path_to_docs\": \"docs\",\n    \"repository_url\": \"https://github.com/jupyter/nbclient\",\n    \"repository_branch\": \"master\",\n    \"use_edit_page_button\": True,\n    \"navigation_with_keys\": False,\n}\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = [\"_static\"]\n\n# Custom sidebar templates, must be a dictionary that maps document names\n# to template names.\n# html_sidebars = {}\n\nhtml_title = \"nbclient\"\n\n# -- Options for HTMLHelp output ------------------------------------------\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"nclientdoc\"\n\n\n# -- Options for LaTeX output ---------------------------------------------\n\n# latex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#\n# 'papersize': 'letterpaper',\n# The font size ('10pt', '11pt' or '12pt').\n#\n# 'pointsize': '10pt',\n# Additional stuff for the LaTeX preamble.\n#\n# 'preamble': '',\n# Latex figure (float) alignment\n#\n# 'figure_align': 'htbp',\n# }\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [(master_doc, \"nbclient.tex\", \"nbclient Documentation\", \"jupyter team\", \"manual\")]\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [(master_doc, \"nbclient\", \"nbclient Documentation\", [author], 1)]\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (\n        master_doc,\n        \"nbclient\",\n        \"nbclient Documentation\",\n        author,\n        \"nbclient\",\n        \"One line description of project.\",\n        \"Miscellaneous\",\n    )\n]\n\n# Example configuration for intersphinx: refer to the Python standard library.\nintersphinx_mapping = {\"python\": (\"https://docs.python.org/\", None)}\n\n\ndef setup(app):\n    here = os.path.abspath(os.path.dirname(__file__))\n    dest = os.path.join(here, \"changelog.md\")\n    shutil.copy(os.path.join(here, \"..\", \"CHANGELOG.md\"), dest)\n\n    autogen_config = os.path.join(here, \"autogen_config.py\")\n    prev_dir = os.getcwd()\n    os.chdir(here)\n    with open(autogen_config) as f:\n        exec(compile(f.read(), autogen_config, \"exec\"), {})  # noqa\n    os.chdir(prev_dir)\n"
  },
  {
    "path": "docs/index.rst",
    "content": "Welcome to nbclient\n===================\n\n.. image:: https://img.shields.io/github/stars/jupyter/nbclient?label=stars&style=social\n   :alt: GitHub stars\n   :target: https://github.com/jupyter/nbclient\n.. image:: https://github.com/jupyter/nbclient/workflows/CI/badge.svg\n   :alt: GitHub Actions\n   :target: https://github.com/jupyter/nbclient/actions\n.. image:: https://codecov.io/github/jupyter/nbclient/coverage.svg?branch=master\n   :alt: CodeCov\n   :target: https://codecov.io/gh/jupyter/nbclient\n\n---\n\n**NBClient** lets you **execute** notebooks.\n\nA client library for programmatic notebook execution, **NBClient** is a tool for running Jupyter Notebooks in\ndifferent execution contexts, including the command line. NBClient was spun out of `nbconvert <https://nbconvert.readthedocs.io/en/latest/>`_'s\nformer ``ExecutePreprocessor``.\n\nDemo\n----\n\nTo demo **NBClient** interactively, click the Binder link below:\n\n.. image:: https://mybinder.org/badge_logo.svg\n    :target: https://mybinder.org/v2/gh/jupyter/nbclient/master?filepath=binder%2Frun_nbclient.ipynb\n\nOrigins\n-------\n\nThis library used to be part of `nbconvert <https://nbconvert.readthedocs.io/en/latest/>`_ and was extracted into its ownlibrary for easier updating and importing by downstream libraries and applications.\n\nPython Version Support\n----------------------\n\nThis library currently supports python 3.6+ versions. As minor python\nversions are officially sunset by the python org, nbclient will similarly\ndrop support in the future.\n\nDocumentation\n-------------\n\nThese pages guide you through the installation and usage of nbclient.\n\n.. toctree::\n   :maxdepth: 1\n   :caption: Documentation\n\n   installation\n   client\n   changelog\n\n\nAPI Reference\n-------------\n\nIf you are looking for information about a specific function, class, or method,\nthis documentation section will help you.\n\n.. toctree::\n   :maxdepth: 3\n   :caption: Table of Contents\n\n   reference/index.rst\n\nIndices and tables\n------------------\n\n* :ref:`genindex`\n* :ref:`modindex`\n* :ref:`search`\n"
  },
  {
    "path": "docs/installation.rst",
    "content": "Installation\n============\n\nInstalling nbclient\n-------------------\n\nFrom the command line:\n\n.. code-block:: bash\n\n   python3 -m pip install nbclient\n\n\n.. seealso::\n\n   `Installing Jupyter <https://jupyter.readthedocs.io/en/latest/install.html>`__\n     NBClient is part of the Jupyter ecosystem.\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\npushd %~dp0\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset SOURCEDIR=.\nset BUILDDIR=_build\nset SPHINXPROJ=nbclient\n\nif \"%1\" == \"\" goto help\n\n%SPHINXBUILD% >NUL 2>NUL\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\n%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%\ngoto end\n\n:help\n%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%\n\n:end\npopd\n"
  },
  {
    "path": "docs/reference/index.rst",
    "content": "Reference\n=========\n\nThis part of the documentation lists the full API reference of all public classes and functions.\n\n.. toctree::\n   :maxdepth: 2\n\n   nbclient\n   config_options\n   modules\n"
  },
  {
    "path": "docs/reference/modules.rst",
    "content": "nbclient\n========\n\n.. toctree::\n   :maxdepth: 4\n\n   nbclient\n"
  },
  {
    "path": "docs/reference/nbclient.rst",
    "content": "nbclient package\n================\n\nSubpackages\n-----------\n\nSubmodules\n----------\n\nnbclient.client module\n----------------------\n\n.. automodule:: nbclient.client\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\nnbclient.exceptions module\n--------------------------\n\n.. automodule:: nbclient.exceptions\n   :members:\n   :undoc-members:\n   :show-inheritance:\n\n\nModule contents\n---------------\n\n.. automodule:: nbclient\n   :members:\n   :undoc-members:\n   :show-inheritance:\n"
  },
  {
    "path": "nbclient/__init__.py",
    "content": "from ._version import __version__, version_info\nfrom .client import NotebookClient, execute\n\n__all__ = [\"__version__\", \"version_info\", \"NotebookClient\", \"execute\"]\n"
  },
  {
    "path": "nbclient/_version.py",
    "content": "\"\"\"Version info.\"\"\"\nfrom __future__ import annotations\n\nimport re\n\n__version__ = \"0.10.4\"\n\n# Build up version_info tuple for backwards compatibility\npattern = r\"(?P<major>\\d+).(?P<minor>\\d+).(?P<patch>\\d+)(?P<rest>.*)\"\nmatch = re.match(pattern, __version__)\nif match:\n    parts: list[int | str] = [int(match[part]) for part in [\"major\", \"minor\", \"patch\"]]\n    if match[\"rest\"]:\n        parts.append(match[\"rest\"])\nelse:\n    parts = []\nversion_info = tuple(parts)\n"
  },
  {
    "path": "nbclient/cli.py",
    "content": "\"\"\"nbclient cli.\"\"\"\nfrom __future__ import annotations\n\nimport logging\nimport sys\nimport typing\nfrom pathlib import Path\nfrom textwrap import dedent\n\nimport nbformat\nfrom jupyter_core.application import JupyterApp\nfrom traitlets import Bool, Integer, List, Unicode, default\nfrom traitlets.config import catch_config_error\n\nfrom nbclient import __version__\n\nfrom .client import NotebookClient\n\n# mypy: disable-error-code=\"no-untyped-call\"\n\nnbclient_aliases: dict[str, str] = {\n    \"timeout\": \"NbClientApp.timeout\",\n    \"startup_timeout\": \"NbClientApp.startup_timeout\",\n    \"kernel_name\": \"NbClientApp.kernel_name\",\n    \"output\": \"NbClientApp.output_base\",\n}\n\nnbclient_flags: dict[str, typing.Any] = {\n    \"allow-errors\": (\n        {\n            \"NbClientApp\": {\n                \"allow_errors\": True,\n            },\n        },\n        \"Errors are ignored and execution is continued until the end of the notebook.\",\n    ),\n    \"inplace\": (\n        {\n            \"NbClientApp\": {\n                \"inplace\": True,\n            },\n        },\n        \"Overwrite input notebook with executed results.\",\n    ),\n}\n\n\nclass NbClientApp(JupyterApp):\n    \"\"\"\n    An application used to execute notebook files (``*.ipynb``)\n    \"\"\"\n\n    version = Unicode(__version__)\n    name = \"jupyter-execute\"\n    aliases = nbclient_aliases\n    flags = nbclient_flags\n\n    description = \"An application used to execute notebook files (*.ipynb)\"\n    notebooks = List(Unicode(), help=\"Path of notebooks to convert\").tag(config=True)\n    timeout = Integer(\n        None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            The time to wait (in seconds) for output from executions.\n            If a cell execution takes longer, a TimeoutError is raised.\n            ``-1`` will disable the timeout.\n            \"\"\"\n        ),\n    ).tag(config=True)\n    startup_timeout = Integer(\n        60,\n        help=dedent(\n            \"\"\"\n            The time to wait (in seconds) for the kernel to start.\n            If kernel startup takes longer, a RuntimeError is\n            raised.\n            \"\"\"\n        ),\n    ).tag(config=True)\n    allow_errors = Bool(\n        False,\n        help=dedent(\n            \"\"\"\n            When a cell raises an error the default behavior is that\n            execution is stopped and a :py:class:`nbclient.exceptions.CellExecutionError`\n            is raised.\n            If this flag is provided, errors are ignored and execution\n            is continued until the end of the notebook.\n            \"\"\"\n        ),\n    ).tag(config=True)\n    skip_cells_with_tag = Unicode(\n        \"skip-execution\",\n        help=dedent(\n            \"\"\"\n            Name of the cell tag to use to denote a cell that should be skipped.\n            \"\"\"\n        ),\n    ).tag(config=True)\n    kernel_name = Unicode(\n        \"\",\n        help=dedent(\n            \"\"\"\n            Name of kernel to use to execute the cells.\n            If not set, use the kernel_spec embedded in the notebook.\n            \"\"\"\n        ),\n    ).tag(config=True)\n    inplace = Bool(\n        False,\n        help=dedent(\n            \"\"\"\n            Default is execute notebook without writing the newly executed notebook.\n            If this flag is provided, the newly generated notebook will\n            overwrite the input notebook.\n            \"\"\"\n        ),\n    ).tag(config=True)\n    output_base = Unicode(\n        None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            Write executed notebook to this file base name.\n            Supports pattern replacements ``'{notebook_name}'``,\n            the name of the input notebook file without extension.\n            Note that output is always relative to the parent directory of the\n            input notebook.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    @default(\"log_level\")\n    def _log_level_default(self) -> int:\n        return logging.INFO\n\n    @catch_config_error\n    def initialize(self, argv: list[str] | None = None) -> None:\n        \"\"\"Initialize the app.\"\"\"\n        super().initialize(argv)\n\n        # Get notebooks to run\n        self.notebooks = self.get_notebooks()\n\n        # If there are none, throw an error\n        if not self.notebooks:\n            sys.exit(-1)\n\n        # If output, must have single notebook\n        if len(self.notebooks) > 1 and self.output_base is not None:\n            if \"{notebook_name}\" not in self.output_base:\n                msg = (\n                    \"If passing multiple notebooks with `--output=output` option, \"\n                    \"output string must contain {notebook_name}\"\n                )\n                raise ValueError(msg)\n\n        # Loop and run them one by one\n        for path in self.notebooks:\n            self.run_notebook(path)\n\n    def get_notebooks(self) -> list[str]:\n        \"\"\"Get the notebooks for the app.\"\"\"\n        # If notebooks were provided from the command line, use those\n        if self.extra_args:\n            notebooks = self.extra_args\n        # If not, look to the class attribute\n        else:\n            notebooks = self.notebooks\n\n        # Return what we got.\n        return notebooks\n\n    def run_notebook(self, notebook_path: str) -> None:\n        \"\"\"Run a notebook by path.\"\"\"\n        # Log it\n        self.log.info(f\"Executing {notebook_path}\")\n\n        input_path = Path(notebook_path).with_suffix(\".ipynb\")\n\n        # Get its parent directory so we can add it to the $PATH\n        path = input_path.parent.absolute()\n\n        # Optional output of executed notebook\n        if self.inplace:\n            output_path = input_path\n        elif self.output_base:\n            output_path = input_path.parent.joinpath(\n                self.output_base.format(notebook_name=input_path.with_suffix(\"\").name)\n            ).with_suffix(\".ipynb\")\n        else:\n            output_path = None\n\n        if output_path and not output_path.parent.is_dir():\n            msg = f\"Cannot write to directory={output_path.parent} that does not exist\"\n            raise ValueError(msg)\n\n        # Open up the notebook we're going to run\n        with input_path.open() as f:\n            nb = nbformat.read(f, as_version=4)\n\n        # Configure nbclient to run the notebook\n        client = NotebookClient(\n            nb,\n            timeout=self.timeout,\n            startup_timeout=self.startup_timeout,\n            skip_cells_with_tag=self.skip_cells_with_tag,\n            allow_errors=self.allow_errors,\n            kernel_name=self.kernel_name,\n            resources={\"metadata\": {\"path\": path}},\n        )\n\n        # Run it\n        client.execute()\n\n        # Save it\n        if output_path:\n            self.log.info(f\"Save executed results to {output_path}\")\n            nbformat.write(nb, output_path)\n\n\nmain = NbClientApp.launch_instance\n"
  },
  {
    "path": "nbclient/client.py",
    "content": "\"\"\"nbclient implementation.\"\"\"\nfrom __future__ import annotations\n\nimport asyncio\nimport atexit\nimport base64\nimport collections\nimport datetime\nimport re\nimport signal\nimport typing as t\nfrom contextlib import asynccontextmanager, contextmanager\nfrom queue import Empty\nfrom textwrap import dedent\nfrom time import monotonic\n\nfrom jupyter_client.client import KernelClient\nfrom jupyter_client.manager import KernelManager\nfrom nbformat import NotebookNode\nfrom nbformat.v4 import output_from_msg\nfrom traitlets import Any, Bool, Callable, Dict, Enum, Integer, List, Type, Unicode, default\nfrom traitlets.config.configurable import LoggingConfigurable\n\nfrom .exceptions import (\n    CellControlSignal,\n    CellExecutionComplete,\n    CellExecutionError,\n    CellTimeoutError,\n    DeadKernelError,\n)\nfrom .output_widget import OutputWidget\nfrom .util import ensure_async, run_hook, run_sync\n\n_RGX_CARRIAGERETURN = re.compile(r\".*\\r(?=[^\\n])\")\n_RGX_BACKSPACE = re.compile(r\"[^\\n]\\b\")\n\n# mypy: disable-error-code=\"no-untyped-call\"\n\n\ndef timestamp(msg: dict[str, t.Any] | None = None) -> str:\n    \"\"\"Get the timestamp for a message.\"\"\"\n    if msg and \"header\" in msg:  # The test mocks don't provide a header, so tolerate that\n        msg_header = msg[\"header\"]\n        if \"date\" in msg_header and isinstance(msg_header[\"date\"], datetime.datetime):\n            try:\n                # reformat datetime into expected format\n                formatted_time = datetime.datetime.strftime(\n                    msg_header[\"date\"], \"%Y-%m-%dT%H:%M:%S.%fZ\"\n                )\n                if (\n                    formatted_time\n                ):  # docs indicate strftime may return empty string, so let's catch that too\n                    return formatted_time\n            except Exception:  # noqa\n                pass  # fallback to a local time\n\n    return datetime.datetime.utcnow().isoformat() + \"Z\"\n\n\nclass NotebookClient(LoggingConfigurable):\n    \"\"\"\n    Encompasses a Client for executing cells in a notebook\n    \"\"\"\n\n    timeout = Integer(\n        None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            The time to wait (in seconds) for output from executions.\n            If a cell execution takes longer, a TimeoutError is raised.\n\n            ``None`` or ``-1`` will disable the timeout. If ``timeout_func`` is set,\n            it overrides ``timeout``.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    timeout_func: t.Callable[..., int | None] | None = Any(  # type:ignore[assignment]\n        default_value=None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            A callable which, when given the cell source as input,\n            returns the time to wait (in seconds) for output from cell\n            executions. If a cell execution takes longer, a TimeoutError\n            is raised.\n\n            Returning ``None`` or ``-1`` will disable the timeout for the cell.\n            Not setting ``timeout_func`` will cause the client to\n            default to using the ``timeout`` trait for all cells. The\n            ``timeout_func`` trait overrides ``timeout`` if it is not ``None``.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    interrupt_on_timeout = Bool(\n        False,\n        help=dedent(\n            \"\"\"\n            If execution of a cell times out, interrupt the kernel and\n            continue executing other cells rather than throwing an error and\n            stopping.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    error_on_timeout = Dict(\n        default_value=None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            If a cell execution was interrupted after a timeout, don't wait for\n            the execute_reply from the kernel (e.g. KeyboardInterrupt error).\n            Instead, return an execute_reply with the given error, which should\n            be of the following form::\n\n                {\n                    'ename': str,  # Exception name, as a string\n                    'evalue': str,  # Exception value, as a string\n                    'traceback': list(str),  # traceback frames, as strings\n                }\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    startup_timeout = Integer(\n        60,\n        help=dedent(\n            \"\"\"\n            The time to wait (in seconds) for the kernel to start.\n            If kernel startup takes longer, a RuntimeError is\n            raised.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    allow_errors = Bool(\n        False,\n        help=dedent(\n            \"\"\"\n            If ``False`` (default), when a cell raises an error the\n            execution is stopped and a ``CellExecutionError``\n            is raised, except if the error name is in\n            ``allow_error_names``.\n            If ``True``, execution errors are ignored and the execution\n            is continued until the end of the notebook. Output from\n            exceptions is included in the cell output in both cases.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    allow_error_names = List(\n        Unicode(),\n        help=dedent(\n            \"\"\"\n            List of error names which won't stop the execution. Use this if the\n            ``allow_errors`` option it too general and you want to allow only\n            specific kinds of errors.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    force_raise_errors = Bool(\n        False,\n        help=dedent(\n            \"\"\"\n            If False (default), errors from executing the notebook can be\n            allowed with a ``raises-exception`` tag on a single cell, or the\n            ``allow_errors`` or ``allow_error_names`` configurable options for\n            all cells. An allowed error will be recorded in notebook output, and\n            execution will continue. If an error occurs when it is not\n            explicitly allowed, a ``CellExecutionError`` will be raised.\n            If True, ``CellExecutionError`` will be raised for any error that occurs\n            while executing the notebook. This overrides the ``allow_errors``\n            and ``allow_error_names`` options and the ``raises-exception`` cell\n            tag.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    skip_cells_with_tag = Unicode(\n        \"skip-execution\",\n        help=dedent(\n            \"\"\"\n            Name of the cell tag to use to denote a cell that should be skipped.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    extra_arguments = List(Unicode()).tag(config=True)\n\n    kernel_name = Unicode(\n        \"\",\n        help=dedent(\n            \"\"\"\n            Name of kernel to use to execute the cells.\n            If not set, use the kernel_spec embedded in the notebook.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    raise_on_iopub_timeout = Bool(\n        False,\n        help=dedent(\n            \"\"\"\n            If ``False`` (default), then the kernel will continue waiting for\n            iopub messages until it receives a kernel idle message, or until a\n            timeout occurs, at which point the currently executing cell will be\n            skipped. If ``True``, then an error will be raised after the first\n            timeout. This option generally does not need to be used, but may be\n            useful in contexts where there is the possibility of executing\n            notebooks with memory-consuming infinite loops.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    store_widget_state = Bool(\n        True,\n        help=dedent(\n            \"\"\"\n            If ``True`` (default), then the state of the Jupyter widgets created\n            at the kernel will be stored in the metadata of the notebook.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    record_timing = Bool(\n        True,\n        help=dedent(\n            \"\"\"\n            If ``True`` (default), then the execution timings of each cell will\n            be stored in the metadata of the notebook.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    iopub_timeout = Integer(\n        4,\n        allow_none=False,\n        help=dedent(\n            \"\"\"\n            The time to wait (in seconds) for IOPub output. This generally\n            doesn't need to be set, but on some slow networks (such as CI\n            systems) the default timeout might not be long enough to get all\n            messages.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    shell_timeout_interval = Integer(\n        5,\n        allow_none=False,\n        help=dedent(\n            \"\"\"\n            The time to wait (in seconds) for Shell output before retrying.\n            This generally doesn't need to be set, but if one needs to check\n            for dead kernels at a faster rate this can help.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    shutdown_kernel = Enum(\n        [\"graceful\", \"immediate\"],\n        default_value=\"graceful\",\n        help=dedent(\n            \"\"\"\n            If ``graceful`` (default), then the kernel is given time to clean\n            up after executing all cells, e.g., to execute its ``atexit`` hooks.\n            If ``immediate``, then the kernel is signaled to immediately\n            terminate.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    ipython_hist_file = Unicode(\n        default_value=\":memory:\",\n        help=\"\"\"Path to file to use for SQLite history database for an IPython kernel.\n\n        The specific value ``:memory:`` (including the colon\n        at both end but not the back ticks), avoids creating a history file. Otherwise, IPython\n        will create a history file for each kernel.\n\n        When running kernels simultaneously (e.g. via multiprocessing) saving history a single\n        SQLite file can result in database errors, so using ``:memory:`` is recommended in\n        non-interactive contexts.\n        \"\"\",\n    ).tag(config=True)\n\n    kernel_manager_class = Type(\n        config=True, klass=KernelManager, help=\"The kernel manager class to use.\"\n    )\n\n    on_notebook_start = Callable(\n        default_value=None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            A callable which executes after the kernel manager and kernel client are setup, and\n            cells are about to execute.\n            Called with kwargs ``notebook``.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    on_notebook_complete = Callable(\n        default_value=None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            A callable which executes after the kernel is cleaned up.\n            Called with kwargs ``notebook``.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    on_notebook_error = Callable(\n        default_value=None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            A callable which executes when the notebook encounters an error.\n            Called with kwargs ``notebook``.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    on_cell_start = Callable(\n        default_value=None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            A callable which executes before a cell is executed and before non-executing cells\n            are skipped.\n            Called with kwargs ``cell`` and ``cell_index``.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    on_cell_execute = Callable(\n        default_value=None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            A callable which executes just before a code cell is executed.\n            Called with kwargs ``cell`` and ``cell_index``.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    on_cell_complete = Callable(\n        default_value=None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            A callable which executes after a cell execution is complete. It is\n            called even when a cell results in a failure.\n            Called with kwargs ``cell`` and ``cell_index``.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    on_cell_executed = Callable(\n        default_value=None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            A callable which executes just after a code cell is executed, whether\n            or not it results in an error.\n            Called with kwargs ``cell``, ``cell_index`` and ``execute_reply``.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    on_cell_error = Callable(\n        default_value=None,\n        allow_none=True,\n        help=dedent(\n            \"\"\"\n            A callable which executes when a cell execution results in an error.\n            This is executed even if errors are suppressed with ``cell_allows_errors``.\n            Called with kwargs ``cell`, ``cell_index`` and ``execute_reply``.\n            \"\"\"\n        ),\n    ).tag(config=True)\n\n    @default(\"kernel_manager_class\")\n    def _kernel_manager_class_default(self) -> type[KernelManager]:\n        \"\"\"Use a dynamic default to avoid importing jupyter_client at startup\"\"\"\n        from jupyter_client import AsyncKernelManager  # type:ignore[attr-defined]\n\n        return AsyncKernelManager\n\n    _display_id_map: dict[str, t.Any] = Dict(  # type:ignore[assignment]\n        help=dedent(\n            \"\"\"\n              mapping of locations of outputs with a given display_id\n              tracks cell index and output index within cell.outputs for\n              each appearance of the display_id\n              {\n                   'display_id': {\n                  cell_idx: [output_idx,]\n                   }\n              }\n              \"\"\"\n        )\n    )\n\n    display_data_priority = List(\n        [\n            \"text/html\",\n            \"application/pdf\",\n            \"text/latex\",\n            \"image/svg+xml\",\n            \"image/png\",\n            \"image/jpeg\",\n            \"text/markdown\",\n            \"text/plain\",\n        ],\n        help=\"\"\"\n            An ordered list of preferred output type, the first\n            encountered will usually be used when converting discarding\n            the others.\n            \"\"\",\n    ).tag(config=True)\n\n    resources: dict[str, t.Any] = Dict(  # type:ignore[assignment]\n        help=dedent(\n            \"\"\"\n            Additional resources used in the conversion process. For example,\n            passing ``{'metadata': {'path': run_path}}`` sets the\n            execution path to ``run_path``.\n            \"\"\"\n        )\n    )\n\n    coalesce_streams = Bool(\n        help=dedent(\n            \"\"\"\n            Merge all stream outputs with shared names into single streams.\n            \"\"\"\n        )\n    )\n\n    def __init__(self, nb: NotebookNode, km: KernelManager | None = None, **kw: t.Any) -> None:\n        \"\"\"Initializes the execution manager.\n\n        Parameters\n        ----------\n        nb : NotebookNode\n            Notebook being executed.\n        km : KernelManager (optional)\n            Optional kernel manager. If none is provided, a kernel manager will\n            be created.\n        \"\"\"\n        super().__init__(**kw)\n        self.nb: NotebookNode = nb\n        self.km: KernelManager | None = km\n        self.owns_km: bool = km is None  # whether the NotebookClient owns the kernel manager\n        self.kc: KernelClient | None = None\n        self.reset_execution_trackers()\n        self.widget_registry: dict[str, dict[str, t.Any]] = {\n            \"@jupyter-widgets/output\": {\"OutputModel\": OutputWidget}\n        }\n        # comm_open_handlers should return an object with a .handle_msg(msg) method or None\n        self.comm_open_handlers: dict[str, t.Any] = {\n            \"jupyter.widget\": self.on_comm_open_jupyter_widget\n        }\n\n    def reset_execution_trackers(self) -> None:\n        \"\"\"Resets any per-execution trackers.\"\"\"\n        self.task_poll_for_reply: asyncio.Future[t.Any] | None = None\n        self.code_cells_executed = 0\n        self._display_id_map = {}\n        self.widget_state: dict[str, dict[str, t.Any]] = {}\n        self.widget_buffers: dict[str, dict[tuple[str, ...], dict[str, str]]] = {}\n        # maps to list of hooks, where the last is used, this is used\n        # to support nested use of output widgets.\n        self.output_hook_stack: dict[str, list[OutputWidget]] = collections.defaultdict(list)\n        # our front-end mimicking Output widgets\n        self.comm_objects: dict[str, t.Any] = {}\n\n    def create_kernel_manager(self) -> KernelManager:\n        \"\"\"Creates a new kernel manager.\n\n        Returns\n        -------\n        km : KernelManager\n            Kernel manager whose client class is asynchronous.\n        \"\"\"\n        if not self.kernel_name:\n            kn = self.nb.metadata.get(\"kernelspec\", {}).get(\"name\")\n            if kn is not None:\n                self.kernel_name = kn\n\n        if not self.kernel_name:\n            self.km = self.kernel_manager_class(config=self.config)\n        else:\n            self.km = self.kernel_manager_class(kernel_name=self.kernel_name, config=self.config)\n        assert self.km is not None\n        return self.km\n\n    async def _async_cleanup_kernel(self) -> None:\n        assert self.km is not None\n        now = self.shutdown_kernel == \"immediate\"\n        try:\n            # Queue the manager to kill the process, and recover gracefully if it's already dead.\n            if await ensure_async(self.km.is_alive()):\n                await ensure_async(self.km.shutdown_kernel(now=now))\n        except RuntimeError as e:\n            # The error isn't specialized, so we have to check the message\n            if \"No kernel is running!\" not in str(e):\n                raise\n        finally:\n            # Remove any state left over even if we failed to stop the kernel\n            await ensure_async(self.km.cleanup_resources())\n            if getattr(self, \"kc\", None) and self.kc is not None:\n                await ensure_async(self.kc.stop_channels())  # type:ignore[func-returns-value]\n                self.kc = None\n                self.km = None\n\n    _cleanup_kernel = run_sync(_async_cleanup_kernel)\n\n    async def async_start_new_kernel(self, **kwargs: t.Any) -> None:\n        \"\"\"Creates a new kernel.\n\n        Parameters\n        ----------\n        kwargs :\n            Any options for ``self.kernel_manager_class.start_kernel()``. Because\n            that defaults to AsyncKernelManager, this will likely include options\n            accepted by ``AsyncKernelManager.start_kernel()``, which includes ``cwd``.\n        \"\"\"\n        assert self.km is not None\n        resource_path = self.resources.get(\"metadata\", {}).get(\"path\") or None\n        if resource_path and \"cwd\" not in kwargs:\n            kwargs[\"cwd\"] = resource_path\n\n        has_history_manager_arg = any(\n            arg.startswith(\"--HistoryManager.hist_file\") for arg in self.extra_arguments\n        )\n        if (\n            hasattr(self.km, \"ipykernel\")\n            and self.km.ipykernel\n            and self.ipython_hist_file\n            and not has_history_manager_arg\n        ):\n            self.extra_arguments += [f\"--HistoryManager.hist_file={self.ipython_hist_file}\"]\n\n        await ensure_async(self.km.start_kernel(extra_arguments=self.extra_arguments, **kwargs))\n\n    start_new_kernel = run_sync(async_start_new_kernel)\n\n    async def async_start_new_kernel_client(self) -> KernelClient:\n        \"\"\"Creates a new kernel client.\n\n        Returns\n        -------\n        kc : KernelClient\n            Kernel client as created by the kernel manager ``km``.\n        \"\"\"\n        assert self.km is not None\n        try:\n            self.kc = self.km.client()\n            await ensure_async(self.kc.start_channels())  # type:ignore[func-returns-value]\n            await ensure_async(self.kc.wait_for_ready(timeout=self.startup_timeout))\n        except Exception as e:\n            self.log.error(\n                \"Error occurred while starting new kernel client for kernel {}: {}\".format(\n                    getattr(self.km, \"kernel_id\", None), str(e)\n                )\n            )\n            await self._async_cleanup_kernel()\n            raise\n        self.kc.allow_stdin = False\n        await run_hook(self.on_notebook_start, notebook=self.nb)\n        return self.kc\n\n    start_new_kernel_client = run_sync(async_start_new_kernel_client)\n\n    @contextmanager\n    def setup_kernel(self, **kwargs: t.Any) -> t.Generator[None, None, None]:\n        \"\"\"\n        Context manager for setting up the kernel to execute a notebook.\n\n        The assigns the Kernel Manager (``self.km``) if missing and Kernel Client(``self.kc``).\n\n        When control returns from the yield it stops the client's zmq channels, and shuts\n        down the kernel.\n        \"\"\"\n        # by default, cleanup the kernel client if we own the kernel manager\n        # and keep it alive if we don't\n        cleanup_kc = kwargs.pop(\"cleanup_kc\", self.owns_km)\n\n        # Can't use run_until_complete on an asynccontextmanager function :(\n        if self.km is None:\n            self.km = self.create_kernel_manager()\n\n        if not self.km.has_kernel:\n            self.start_new_kernel(**kwargs)\n\n        if self.kc is None:\n            self.start_new_kernel_client()\n\n        try:\n            yield\n        finally:\n            if cleanup_kc:\n                self._cleanup_kernel()\n\n    @asynccontextmanager\n    async def async_setup_kernel(self, **kwargs: t.Any) -> t.AsyncGenerator[None, None]:\n        \"\"\"\n        Context manager for setting up the kernel to execute a notebook.\n\n        This assigns the Kernel Manager (``self.km``) if missing and Kernel Client(``self.kc``).\n\n        When control returns from the yield it stops the client's zmq channels, and shuts\n        down the kernel.\n\n        Handlers for SIGINT and SIGTERM are also added to cleanup in case of unexpected shutdown.\n        \"\"\"\n        # by default, cleanup the kernel client if we own the kernel manager\n        # and keep it alive if we don't\n        cleanup_kc = kwargs.pop(\"cleanup_kc\", self.owns_km)\n        if self.km is None:\n            self.km = self.create_kernel_manager()\n\n        # self._cleanup_kernel uses run_async, which ensures the ioloop is running again.\n        # This is necessary as the ioloop has stopped once atexit fires.\n        atexit.register(self._cleanup_kernel)\n\n        def on_signal() -> None:\n            \"\"\"Handle signals.\"\"\"\n            self._async_cleanup_kernel_future = asyncio.ensure_future(self._async_cleanup_kernel())\n            atexit.unregister(self._cleanup_kernel)\n\n        loop = asyncio.get_event_loop()\n        try:\n            loop.add_signal_handler(signal.SIGINT, on_signal)\n            loop.add_signal_handler(signal.SIGTERM, on_signal)\n        except RuntimeError:\n            # NotImplementedError: Windows does not support signals.\n            # RuntimeError: Raised when add_signal_handler is called outside the main thread\n            pass\n\n        if not self.km.has_kernel:\n            await self.async_start_new_kernel(**kwargs)\n\n        if self.kc is None:\n            await self.async_start_new_kernel_client()\n\n        try:\n            yield\n        except RuntimeError as e:\n            await run_hook(self.on_notebook_error, notebook=self.nb)\n            raise e\n        finally:\n            if cleanup_kc:\n                await self._async_cleanup_kernel()\n            await run_hook(self.on_notebook_complete, notebook=self.nb)\n            atexit.unregister(self._cleanup_kernel)\n            try:\n                loop.remove_signal_handler(signal.SIGINT)\n                loop.remove_signal_handler(signal.SIGTERM)\n            except RuntimeError:\n                pass\n\n    async def async_execute(self, reset_kc: bool = False, **kwargs: t.Any) -> NotebookNode:\n        \"\"\"\n        Executes each code cell.\n\n        Parameters\n        ----------\n        kwargs :\n            Any option for ``self.kernel_manager_class.start_kernel()``. Because\n            that defaults to AsyncKernelManager, this will likely include options\n            accepted by ``jupyter_client.AsyncKernelManager.start_kernel()``,\n            which includes ``cwd``.\n\n            ``reset_kc`` if True, the kernel client will be reset and a new one\n            will be created (default: False).\n\n        Returns\n        -------\n        nb : NotebookNode\n            The executed notebook.\n        \"\"\"\n        if reset_kc and self.owns_km:\n            await self._async_cleanup_kernel()\n        self.reset_execution_trackers()\n\n        async with self.async_setup_kernel(**kwargs):\n            assert self.kc is not None\n            self.log.info(\"Executing notebook with kernel: %s\" % self.kernel_name)\n            msg_id = await ensure_async(self.kc.kernel_info())\n            info_msg = await self.async_wait_for_reply(msg_id)\n            if info_msg is not None:\n                if \"language_info\" in info_msg[\"content\"]:\n                    self.nb.metadata[\"language_info\"] = info_msg[\"content\"][\"language_info\"]\n                else:\n                    raise RuntimeError(\n                        'Kernel info received message content has no \"language_info\" key. '\n                        \"Content is:\\n\" + str(info_msg[\"content\"])\n                    )\n            for index, cell in enumerate(self.nb.cells):\n                # Ignore `'execution_count' in content` as it's always 1\n                # when store_history is False\n                await self.async_execute_cell(\n                    cell, index, execution_count=self.code_cells_executed + 1\n                )\n            self.set_widgets_metadata()\n\n        return self.nb\n\n    execute = run_sync(async_execute)\n\n    def set_widgets_metadata(self) -> None:\n        \"\"\"Set with widget metadata.\"\"\"\n        if self.widget_state:\n            self.nb.metadata.widgets = {\n                \"application/vnd.jupyter.widget-state+json\": {\n                    \"state\": {\n                        model_id: self._serialize_widget_state(state)\n                        for model_id, state in self.widget_state.items()\n                        if \"_model_name\" in state\n                    },\n                    \"version_major\": 2,\n                    \"version_minor\": 0,\n                }\n            }\n            for key, widget in self.nb.metadata.widgets[\n                \"application/vnd.jupyter.widget-state+json\"\n            ][\"state\"].items():\n                buffers = self.widget_buffers.get(key)\n                if buffers:\n                    widget[\"buffers\"] = list(buffers.values())\n\n    def _update_display_id(self, display_id: str, msg: dict[str, t.Any]) -> None:\n        \"\"\"Update outputs with a given display_id\"\"\"\n        if display_id not in self._display_id_map:\n            self.log.debug(\"display id %r not in %s\", display_id, self._display_id_map)\n            return\n\n        if msg[\"header\"][\"msg_type\"] == \"update_display_data\":\n            msg[\"header\"][\"msg_type\"] = \"display_data\"\n\n        try:\n            out = output_from_msg(msg)\n        except ValueError:\n            self.log.error(f\"unhandled iopub msg: {msg['msg_type']}\")\n            return\n\n        for cell_idx, output_indices in self._display_id_map[display_id].items():\n            cell = self.nb[\"cells\"][cell_idx]\n            outputs = cell[\"outputs\"]\n            for output_idx in output_indices:\n                outputs[output_idx][\"data\"] = out[\"data\"]\n                outputs[output_idx][\"metadata\"] = out[\"metadata\"]\n\n    async def _async_poll_for_reply(\n        self,\n        msg_id: str,\n        cell: NotebookNode,\n        timeout: int | None,\n        task_poll_output_msg: asyncio.Future[t.Any],\n        task_poll_kernel_alive: asyncio.Future[t.Any],\n    ) -> dict[str, t.Any]:\n        msg: dict[str, t.Any]\n        assert self.kc is not None\n        new_timeout: float | None = None\n        if timeout is not None:\n            deadline = monotonic() + timeout\n            new_timeout = float(timeout)\n        error_on_timeout_execute_reply = None\n        while True:\n            try:\n                if error_on_timeout_execute_reply:\n                    msg = error_on_timeout_execute_reply\n                    msg[\"parent_header\"] = {\"msg_id\": msg_id}\n                else:\n                    msg = await ensure_async(self.kc.shell_channel.get_msg(timeout=new_timeout))\n                if msg[\"parent_header\"].get(\"msg_id\") == msg_id:\n                    if self.record_timing:\n                        cell[\"metadata\"][\"execution\"][\"shell.execute_reply\"] = timestamp(msg)\n                    try:\n                        await asyncio.wait_for(task_poll_output_msg, self.iopub_timeout)\n                    except (asyncio.TimeoutError, Empty):\n                        if self.raise_on_iopub_timeout:\n                            task_poll_kernel_alive.cancel()\n                            raise CellTimeoutError.error_from_timeout_and_cell(\n                                \"Timeout waiting for IOPub output\", self.iopub_timeout, cell\n                            ) from None\n                        else:\n                            self.log.warning(\"Timeout waiting for IOPub output\")\n                    task_poll_kernel_alive.cancel()\n                    return msg\n                else:\n                    if new_timeout is not None:\n                        new_timeout = max(0, deadline - monotonic())\n            except Empty:\n                # received no message, check if kernel is still alive\n                assert timeout is not None\n                task_poll_kernel_alive.cancel()\n                await self._async_check_alive()\n                error_on_timeout_execute_reply = await self._async_handle_timeout(timeout, cell)\n\n    async def _async_poll_output_msg(\n        self, parent_msg_id: str, cell: NotebookNode, cell_index: int\n    ) -> None:\n        assert self.kc is not None\n        while True:\n            msg = await ensure_async(self.kc.iopub_channel.get_msg(timeout=None))\n            if msg[\"parent_header\"].get(\"msg_id\") == parent_msg_id:\n                try:\n                    # Will raise CellExecutionComplete when completed\n                    self.process_message(msg, cell, cell_index)\n                except CellExecutionComplete:\n                    return\n\n    async def _async_poll_kernel_alive(self) -> None:\n        while True:\n            await asyncio.sleep(1)\n            try:\n                await self._async_check_alive()\n            except DeadKernelError:\n                assert self.task_poll_for_reply is not None\n                self.task_poll_for_reply.cancel()\n                return\n\n    def _get_timeout(self, cell: NotebookNode | None) -> int | None:\n        if self.timeout_func is not None and cell is not None:\n            timeout = self.timeout_func(cell)\n        else:\n            timeout = self.timeout\n\n        if not timeout or timeout < 0:\n            timeout = None\n\n        return timeout\n\n    async def _async_handle_timeout(\n        self, timeout: int, cell: NotebookNode | None = None\n    ) -> None | dict[str, t.Any]:\n        self.log.error(\"Timeout waiting for execute reply (%is).\" % timeout)\n        if self.interrupt_on_timeout:\n            self.log.error(\"Interrupting kernel\")\n            assert self.km is not None\n            await ensure_async(self.km.interrupt_kernel())\n            if self.error_on_timeout:\n                execute_reply = {\"content\": {**self.error_on_timeout, \"status\": \"error\"}}\n                return execute_reply\n            return None\n        else:\n            assert cell is not None\n            raise CellTimeoutError.error_from_timeout_and_cell(\n                \"Cell execution timed out\", timeout, cell\n            )\n\n    async def _async_check_alive(self) -> None:\n        assert self.kc is not None\n        if not await ensure_async(self.kc.is_alive()):  # type:ignore[attr-defined]\n            self.log.error(\"Kernel died while waiting for execute reply.\")\n            raise DeadKernelError(\"Kernel died\")\n\n    async def async_wait_for_reply(\n        self, msg_id: str, cell: NotebookNode | None = None\n    ) -> dict[str, t.Any] | None:\n        \"\"\"Wait for a message reply.\"\"\"\n        assert self.kc is not None\n        # wait for finish, with timeout\n        timeout = self._get_timeout(cell)\n        cummulative_time = 0\n        while True:\n            try:\n                msg: dict[str, t.Any] = await ensure_async(\n                    self.kc.shell_channel.get_msg(timeout=self.shell_timeout_interval)\n                )\n            except Empty:\n                await self._async_check_alive()\n                cummulative_time += self.shell_timeout_interval\n                if timeout and cummulative_time > timeout:\n                    await self._async_handle_timeout(timeout, cell)\n                    break\n            else:\n                if msg[\"parent_header\"].get(\"msg_id\") == msg_id:\n                    return msg\n        return None\n\n    wait_for_reply = run_sync(async_wait_for_reply)\n    # Backwards compatibility naming for papermill\n    _wait_for_reply = wait_for_reply\n\n    def _passed_deadline(self, deadline: int | None) -> bool:\n        if deadline is not None and deadline - monotonic() <= 0:\n            return True\n        return False\n\n    async def _check_raise_for_error(\n        self, cell: NotebookNode, cell_index: int, exec_reply: dict[str, t.Any] | None\n    ) -> None:\n        if exec_reply is None:\n            return None\n\n        exec_reply_content = exec_reply[\"content\"]\n        if exec_reply_content[\"status\"] != \"error\":\n            return None\n\n        cell_allows_errors = (not self.force_raise_errors) and (\n            self.allow_errors\n            or exec_reply_content.get(\"ename\") in self.allow_error_names\n            or \"raises-exception\" in cell.metadata.get(\"tags\", [])\n        )\n        await run_hook(\n            self.on_cell_error, cell=cell, cell_index=cell_index, execute_reply=exec_reply\n        )\n        if not cell_allows_errors:\n            raise CellExecutionError.from_cell_and_msg(cell, exec_reply_content)\n\n    async def async_execute_cell(\n        self,\n        cell: NotebookNode,\n        cell_index: int,\n        execution_count: int | None = None,\n        store_history: bool = True,\n    ) -> NotebookNode:\n        \"\"\"\n        Executes a single code cell.\n\n        To execute all cells see :meth:`execute`.\n\n        Parameters\n        ----------\n        cell : nbformat.NotebookNode\n            The cell which is currently being processed.\n        cell_index : int\n            The position of the cell within the notebook object.\n        execution_count : int\n            The execution count to be assigned to the cell (default: Use kernel response)\n        store_history : bool\n            Determines if history should be stored in the kernel (default: False).\n            Specific to ipython kernels, which can store command histories.\n\n        Returns\n        -------\n        output : dict\n            The execution output payload (or None for no output).\n\n        Raises\n        ------\n        CellExecutionError\n            If execution failed and should raise an exception, this will be raised\n            with defaults about the failure.\n\n        Returns\n        -------\n        cell : NotebookNode\n            The cell which was just processed.\n        \"\"\"\n        assert self.kc is not None\n\n        await run_hook(self.on_cell_start, cell=cell, cell_index=cell_index)\n\n        if cell.cell_type != \"code\" or not cell.source.strip():\n            self.log.debug(\"Skipping non-executing cell %s\", cell_index)\n            return cell\n\n        if self.skip_cells_with_tag in cell.metadata.get(\"tags\", []):\n            self.log.debug(\"Skipping tagged cell %s\", cell_index)\n            return cell\n\n        if self.record_timing:  # clear execution metadata prior to execution\n            cell[\"metadata\"][\"execution\"] = {}\n\n        self.log.debug(\"Executing cell:\\n%s\", cell.source)\n\n        cell_allows_errors = (not self.force_raise_errors) and (\n            self.allow_errors or \"raises-exception\" in cell.metadata.get(\"tags\", [])\n        )\n\n        await run_hook(self.on_cell_execute, cell=cell, cell_index=cell_index)\n        parent_msg_id = await ensure_async(\n            self.kc.execute(\n                cell.source, store_history=store_history, stop_on_error=not cell_allows_errors\n            )\n        )\n        await run_hook(self.on_cell_complete, cell=cell, cell_index=cell_index)\n        # We launched a code cell to execute\n        self.code_cells_executed += 1\n        exec_timeout = self._get_timeout(cell)\n\n        cell.outputs = []\n        self.clear_before_next_output = False\n\n        task_poll_kernel_alive = asyncio.ensure_future(self._async_poll_kernel_alive())\n        task_poll_output_msg = asyncio.ensure_future(\n            self._async_poll_output_msg(parent_msg_id, cell, cell_index)\n        )\n        self.task_poll_for_reply = asyncio.ensure_future(\n            self._async_poll_for_reply(\n                parent_msg_id, cell, exec_timeout, task_poll_output_msg, task_poll_kernel_alive\n            )\n        )\n        try:\n            exec_reply = await self.task_poll_for_reply\n        except asyncio.CancelledError:\n            # can only be cancelled by task_poll_kernel_alive when the kernel is dead\n            task_poll_output_msg.cancel()\n            raise DeadKernelError(\"Kernel died\") from None\n        except Exception as e:\n            # Best effort to cancel request if it hasn't been resolved\n            try:\n                # Check if the task_poll_output is doing the raising for us\n                if not isinstance(e, CellControlSignal):\n                    task_poll_output_msg.cancel()\n            finally:\n                raise\n\n        if execution_count:\n            cell[\"execution_count\"] = execution_count\n        await run_hook(\n            self.on_cell_executed, cell=cell, cell_index=cell_index, execute_reply=exec_reply\n        )\n\n        if self.coalesce_streams and cell.outputs:\n            new_outputs = []\n            streams: dict[str, NotebookNode] = {}\n            for output in cell.outputs:\n                if output[\"output_type\"] == \"stream\":\n                    if output[\"name\"] in streams:\n                        streams[output[\"name\"]][\"text\"] += output[\"text\"]\n                    else:\n                        new_outputs.append(output)\n                        streams[output[\"name\"]] = output\n                else:\n                    new_outputs.append(output)\n\n            # process \\r and \\b characters\n            for output in streams.values():\n                old = output[\"text\"]\n                while len(output[\"text\"]) < len(old):\n                    old = output[\"text\"]\n                    # Cancel out anything-but-newline followed by backspace\n                    output[\"text\"] = _RGX_BACKSPACE.sub(\"\", output[\"text\"])\n                # Replace all carriage returns not followed by newline\n                output[\"text\"] = _RGX_CARRIAGERETURN.sub(\"\", output[\"text\"])\n\n            # We also want to ensure stdout and stderr are always in the same consecutive order,\n            # because they are asynchronous, so order isn't guaranteed.\n            for i, output in enumerate(new_outputs):\n                if output[\"output_type\"] == \"stream\" and output[\"name\"] == \"stderr\":\n                    if (\n                        len(new_outputs) >= i + 2\n                        and new_outputs[i + 1][\"output_type\"] == \"stream\"\n                        and new_outputs[i + 1][\"name\"] == \"stdout\"\n                    ):\n                        stdout = new_outputs.pop(i + 1)\n                        new_outputs.insert(i, stdout)\n\n            cell.outputs = new_outputs\n\n        await self._check_raise_for_error(cell, cell_index, exec_reply)\n\n        self.nb[\"cells\"][cell_index] = cell\n        return cell\n\n    execute_cell = run_sync(async_execute_cell)\n\n    def process_message(\n        self, msg: dict[str, t.Any], cell: NotebookNode, cell_index: int\n    ) -> NotebookNode | None:\n        \"\"\"\n        Processes a kernel message, updates cell state, and returns the\n        resulting output object that was appended to cell.outputs.\n\n        The input argument *cell* is modified in-place.\n\n        Parameters\n        ----------\n        msg : dict\n            The kernel message being processed.\n        cell : nbformat.NotebookNode\n            The cell which is currently being processed.\n        cell_index : int\n            The position of the cell within the notebook object.\n\n        Returns\n        -------\n        output : NotebookNode\n            The execution output payload (or None for no output).\n\n        Raises\n        ------\n        CellExecutionComplete\n          Once a message arrives which indicates computation completeness.\n\n        \"\"\"\n        msg_type = msg[\"msg_type\"]\n        self.log.debug(\"msg_type: %s\", msg_type)\n        content = msg[\"content\"]\n        self.log.debug(\"content: %s\", content)\n\n        # while it's tempting to go for a more concise\n        # display_id = content.get(\"transient\", {}).get(\"display_id\", None)\n        # this breaks if transient is explicitly set to None\n        transient = content.get(\"transient\")\n        display_id = transient.get(\"display_id\") if transient else None\n\n        if display_id and msg_type in {\"execute_result\", \"display_data\", \"update_display_data\"}:\n            self._update_display_id(display_id, msg)\n\n        # set the prompt number for the input and the output\n        if \"execution_count\" in content:\n            cell[\"execution_count\"] = content[\"execution_count\"]\n\n        if self.record_timing:\n            if msg_type == \"status\":\n                if content[\"execution_state\"] == \"idle\":\n                    cell[\"metadata\"][\"execution\"][\"iopub.status.idle\"] = timestamp(msg)\n                elif content[\"execution_state\"] == \"busy\":\n                    cell[\"metadata\"][\"execution\"][\"iopub.status.busy\"] = timestamp(msg)\n            elif msg_type == \"execute_input\":\n                cell[\"metadata\"][\"execution\"][\"iopub.execute_input\"] = timestamp(msg)\n\n        if msg_type == \"status\":\n            if content[\"execution_state\"] == \"idle\":\n                raise CellExecutionComplete()\n        elif msg_type == \"clear_output\":\n            self.clear_output(cell.outputs, msg, cell_index)\n        elif msg_type.startswith(\"comm\"):\n            self.handle_comm_msg(cell.outputs, msg, cell_index)\n        # Check for remaining messages we don't process\n        elif msg_type not in [\"execute_input\", \"update_display_data\"]:\n            # Assign output as our processed \"result\"\n            return self.output(cell.outputs, msg, display_id, cell_index)\n        return None\n\n    def output(\n        self,\n        outs: list[NotebookNode],\n        msg: dict[str, t.Any],\n        display_id: str | None,\n        cell_index: int,\n    ) -> NotebookNode | None:\n        \"\"\"Handle output.\"\"\"\n\n        msg_type = msg[\"msg_type\"]\n        out: NotebookNode | None = None\n\n        parent_msg_id = msg[\"parent_header\"].get(\"msg_id\")\n        if self.output_hook_stack[parent_msg_id]:\n            # if we have a hook registered, it will override our\n            # default output behaviour (e.g. OutputWidget)\n            hook = self.output_hook_stack[parent_msg_id][-1]\n            hook.output(outs, msg, display_id, cell_index)\n            return None\n\n        try:\n            out = output_from_msg(msg)\n        except ValueError:\n            self.log.error(f\"unhandled iopub msg: {msg_type}\")\n            return None\n\n        if self.clear_before_next_output:\n            self.log.debug(\"Executing delayed clear_output\")\n            outs[:] = []\n            self.clear_display_id_mapping(cell_index)\n            self.clear_before_next_output = False\n\n        if display_id:\n            # record output index in:\n            #   _display_id_map[display_id][cell_idx]\n            cell_map = self._display_id_map.setdefault(display_id, {})\n            output_idx_list = cell_map.setdefault(cell_index, [])\n            output_idx_list.append(len(outs))\n\n        if out:\n            outs.append(out)\n\n        return out\n\n    def clear_output(\n        self, outs: list[NotebookNode], msg: dict[str, t.Any], cell_index: int\n    ) -> None:\n        \"\"\"Clear output.\"\"\"\n        content = msg[\"content\"]\n\n        parent_msg_id = msg[\"parent_header\"].get(\"msg_id\")\n        if self.output_hook_stack[parent_msg_id]:\n            # if we have a hook registered, it will override our\n            # default clear_output behaviour (e.g. OutputWidget)\n            hook = self.output_hook_stack[parent_msg_id][-1]\n            hook.clear_output(outs, msg, cell_index)\n            return\n\n        if content.get(\"wait\"):\n            self.log.debug(\"Wait to clear output\")\n            self.clear_before_next_output = True\n        else:\n            self.log.debug(\"Immediate clear output\")\n            outs[:] = []\n            self.clear_display_id_mapping(cell_index)\n\n    def clear_display_id_mapping(self, cell_index: int) -> None:\n        \"\"\"Clear a display id mapping for a cell.\"\"\"\n        for _, cell_map in self._display_id_map.items():\n            if cell_index in cell_map:\n                cell_map[cell_index] = []\n\n    def handle_comm_msg(\n        self, outs: list[NotebookNode], msg: dict[str, t.Any], cell_index: int\n    ) -> None:\n        \"\"\"Handle a comm message.\"\"\"\n        content = msg[\"content\"]\n        data = content[\"data\"]\n        if self.store_widget_state and \"state\" in data:  # ignore custom msg'es\n            self.widget_state.setdefault(content[\"comm_id\"], {}).update(data[\"state\"])\n            if data.get(\"buffer_paths\"):\n                comm_id = content[\"comm_id\"]\n                if comm_id not in self.widget_buffers:\n                    self.widget_buffers[comm_id] = {}\n                # for each comm, the path uniquely identifies a buffer\n                new_buffers: dict[tuple[str, ...], dict[str, str]] = {\n                    tuple(k[\"path\"]): k for k in self._get_buffer_data(msg)\n                }\n                self.widget_buffers[comm_id].update(new_buffers)\n        # There are cases where we need to mimic a frontend, to get similar behaviour as\n        # when using the Output widget from Jupyter lab/notebook\n        if msg[\"msg_type\"] == \"comm_open\":\n            target = msg[\"content\"].get(\"target_name\")\n            handler = self.comm_open_handlers.get(target)\n            if handler:\n                comm_id = msg[\"content\"][\"comm_id\"]\n                comm_object = handler(msg)\n                if comm_object:\n                    self.comm_objects[comm_id] = comm_object\n            else:\n                self.log.warning(f\"No handler found for comm target {target!r}\")\n        elif msg[\"msg_type\"] == \"comm_msg\":\n            content = msg[\"content\"]\n            comm_id = msg[\"content\"][\"comm_id\"]\n            if comm_id in self.comm_objects:\n                self.comm_objects[comm_id].handle_msg(msg)\n\n    def _serialize_widget_state(self, state: dict[str, t.Any]) -> dict[str, t.Any]:\n        \"\"\"Serialize a widget state, following format in @jupyter-widgets/schema.\"\"\"\n        return {\n            \"model_name\": state.get(\"_model_name\"),\n            \"model_module\": state.get(\"_model_module\"),\n            \"model_module_version\": state.get(\"_model_module_version\"),\n            \"state\": state,\n        }\n\n    def _get_buffer_data(self, msg: dict[str, t.Any]) -> list[dict[str, str]]:\n        encoded_buffers = []\n        paths = msg[\"content\"][\"data\"][\"buffer_paths\"]\n        buffers = msg[\"buffers\"]\n        for path, buffer in zip(paths, buffers, strict=False):\n            encoded_buffers.append(\n                {\n                    \"data\": base64.b64encode(buffer).decode(\"utf-8\"),\n                    \"encoding\": \"base64\",\n                    \"path\": path,\n                }\n            )\n        return encoded_buffers\n\n    def register_output_hook(self, msg_id: str, hook: OutputWidget) -> None:\n        \"\"\"Registers an override object that handles output/clear_output instead.\n\n        Multiple hooks can be registered, where the last one will be used (stack based)\n        \"\"\"\n        # mimics\n        # https://jupyterlab.github.io/jupyterlab/services/interfaces/kernel.ikernelconnection.html#registermessagehook\n        self.output_hook_stack[msg_id].append(hook)\n\n    def remove_output_hook(self, msg_id: str, hook: OutputWidget) -> None:\n        \"\"\"Unregisters an override object that handles output/clear_output instead\"\"\"\n        # mimics\n        # https://jupyterlab.github.io/jupyterlab/services/interfaces/kernel.ikernelconnection.html#removemessagehook\n        removed_hook = self.output_hook_stack[msg_id].pop()\n        assert removed_hook == hook\n\n    def on_comm_open_jupyter_widget(self, msg: dict[str, t.Any]) -> t.Any | None:\n        \"\"\"Handle a jupyter widget comm open.\"\"\"\n        content = msg[\"content\"]\n        data = content[\"data\"]\n        state = data[\"state\"]\n        comm_id = msg[\"content\"][\"comm_id\"]\n        module = self.widget_registry.get(state[\"_model_module\"])\n        if module:\n            widget_class = module.get(state[\"_model_name\"])\n            if widget_class:\n                return widget_class(comm_id, state, self.kc, self)\n        return None\n\n\ndef execute(\n    nb: NotebookNode,\n    cwd: str | None = None,\n    km: KernelManager | None = None,\n    **kwargs: t.Any,\n) -> NotebookNode:\n    \"\"\"Execute a notebook's code, updating outputs within the notebook object.\n\n    This is a convenient wrapper around NotebookClient. It returns the\n    modified notebook object.\n\n    Parameters\n    ----------\n    nb : NotebookNode\n      The notebook object to be executed\n    cwd : str, optional\n      If supplied, the kernel will run in this directory\n    km : AsyncKernelManager, optional\n      If supplied, the specified kernel manager will be used for code execution.\n    kwargs :\n      Any other options for NotebookClient, e.g. timeout, kernel_name\n    \"\"\"\n    resources = {}\n    if cwd is not None:\n        resources[\"metadata\"] = {\"path\": cwd}\n    return NotebookClient(nb=nb, resources=resources, km=km, **kwargs).execute()\n"
  },
  {
    "path": "nbclient/exceptions.py",
    "content": "\"\"\"Exceptions for nbclient.\"\"\"\nfrom __future__ import annotations\n\nfrom typing import Any\n\nfrom nbformat import NotebookNode\n\n\nclass CellControlSignal(Exception):  # noqa\n    \"\"\"\n    A custom exception used to indicate that the exception is used for cell\n    control actions (not the best model, but it's needed to cover existing\n    behavior without major refactors).\n    \"\"\"\n\n    pass\n\n\nclass CellTimeoutError(TimeoutError, CellControlSignal):\n    \"\"\"\n    A custom exception to capture when a cell has timed out during execution.\n    \"\"\"\n\n    @classmethod\n    def error_from_timeout_and_cell(\n        cls, msg: str, timeout: int, cell: NotebookNode\n    ) -> CellTimeoutError:\n        \"\"\"Create an error from a timeout on a cell.\"\"\"\n        if cell and cell.source:\n            src_by_lines = cell.source.strip().split(\"\\n\")\n            src = (\n                cell.source\n                if len(src_by_lines) < 11\n                else f\"{src_by_lines[:5]}\\n...\\n{src_by_lines[-5:]}\"\n            )\n        else:\n            src = \"Cell contents not found.\"\n        return cls(timeout_err_msg.format(timeout=timeout, msg=msg, cell_contents=src))\n\n\nclass DeadKernelError(RuntimeError):\n    \"\"\"A dead kernel error.\"\"\"\n\n    pass\n\n\nclass CellExecutionComplete(CellControlSignal):\n    \"\"\"\n    Used as a control signal for cell execution across execute_cell and\n    process_message function calls. Raised when all execution requests\n    are completed and no further messages are expected from the kernel\n    over zeromq channels.\n    \"\"\"\n\n    pass\n\n\nclass CellExecutionError(CellControlSignal):\n    \"\"\"\n    Custom exception to propagate exceptions that are raised during\n    notebook execution to the caller. This is mostly useful when\n    using nbconvert as a library, since it allows to deal with\n    failures gracefully.\n    \"\"\"\n\n    def __init__(self, traceback: str, ename: str, evalue: str) -> None:\n        \"\"\"Initialize the error.\"\"\"\n        super().__init__(traceback)\n        self.traceback = traceback\n        self.ename = ename\n        self.evalue = evalue\n\n    def __reduce__(self) -> tuple[Any]:\n        \"\"\"Reduce implementation.\"\"\"\n        return type(self), (self.traceback, self.ename, self.evalue)  # type:ignore[return-value]\n\n    def __str__(self) -> str:\n        \"\"\"Str repr.\"\"\"\n        if self.traceback:\n            return self.traceback\n        else:\n            return f\"{self.ename}: {self.evalue}\"\n\n    @classmethod\n    def from_cell_and_msg(cls, cell: NotebookNode, msg: dict[str, Any]) -> CellExecutionError:\n        \"\"\"Instantiate from a code cell object and a message contents\n        (message is either execute_reply or error)\n        \"\"\"\n\n        # collect stream outputs for our error message\n        stream_outputs: list[str] = []\n        for output in cell.outputs:\n            if output[\"output_type\"] == \"stream\":\n                stream_outputs.append(\n                    stream_output_msg.format(name=output[\"name\"], text=output[\"text\"].rstrip())\n                )\n        if stream_outputs:\n            # add blank line before, trailing separator\n            # if there is any stream output to display\n            stream_outputs.insert(0, \"\")\n            stream_outputs.append(\"------------------\")\n        stream_output: str = \"\\n\".join(stream_outputs)\n\n        tb = \"\\n\".join(msg.get(\"traceback\", []) or [])\n        return cls(\n            exec_err_msg.format(\n                cell=cell,\n                stream_output=stream_output,\n                traceback=tb,\n            ),\n            ename=msg.get(\"ename\", \"<Error>\"),\n            evalue=msg.get(\"evalue\", \"\"),\n        )\n\n\nstream_output_msg: str = \"\"\"\\\n----- {name} -----\n{text}\"\"\"\n\nexec_err_msg: str = \"\"\"\\\nAn error occurred while executing the following cell:\n------------------\n{cell.source}\n------------------\n{stream_output}\n\n{traceback}\n\"\"\"\n\n\ntimeout_err_msg: str = \"\"\"\\\nA cell timed out while it was being executed, after {timeout} seconds.\nThe message was: {msg}.\nHere is a preview of the cell contents:\n-------------------\n{cell_contents}\n-------------------\n\"\"\"\n"
  },
  {
    "path": "nbclient/jsonutil.py",
    "content": "\"\"\"Utilities to manipulate JSON objects.\"\"\"\n\n# NOTE: this is a copy of ipykernel/jsonutils.py (+blackified)\n\n# Copyright (c) IPython Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom __future__ import annotations\n\nimport math\nimport numbers\nimport re\nimport types\nfrom binascii import b2a_base64\nfrom datetime import datetime\nfrom typing import Any\n\n# -----------------------------------------------------------------------------\n# Globals and constants\n# -----------------------------------------------------------------------------\n\n# timestamp formats\nISO8601 = \"%Y-%m-%dT%H:%M:%S.%f\"\nISO8601_PAT = re.compile(\n    r\"^(\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2})(\\.\\d{1,6})?Z?([\\+\\-]\\d{2}:?\\d{2})?$\"\n)\n\n# holy crap, strptime is not threadsafe.\n# Calling it once at import seems to help.\ndatetime.strptime(\"2000-01-01\", \"%Y-%m-%d\")\n\n# -----------------------------------------------------------------------------\n# Classes and functions\n# -----------------------------------------------------------------------------\n\n\n# constants for identifying png/jpeg data\nPNG = b\"\\x89PNG\\r\\n\\x1a\\n\"\n# front of PNG base64-encoded\nPNG64 = b\"iVBORw0KG\"\nJPEG = b\"\\xff\\xd8\"\n# front of JPEG base64-encoded\nJPEG64 = b\"/9\"\n# constants for identifying gif data\nGIF_64 = b\"R0lGODdh\"\nGIF89_64 = b\"R0lGODlh\"\n# front of PDF base64-encoded\nPDF64 = b\"JVBER\"\n\n\ndef encode_images(format_dict: dict[str, str]) -> dict[str, str]:\n    \"\"\"b64-encodes images in a displaypub format dict\n\n    Perhaps this should be handled in json_clean itself?\n\n    Parameters\n    ----------\n\n    format_dict : dict\n        A dictionary of display data keyed by mime-type\n\n    Returns\n    -------\n\n    format_dict : dict\n        A copy of the same dictionary,\n        but binary image data ('image/png', 'image/jpeg' or 'application/pdf')\n        is base64-encoded.\n\n    \"\"\"\n    return format_dict\n\n\ndef json_clean(obj: Any) -> Any:\n    \"\"\"Clean an object to ensure it's safe to encode in JSON.\n\n    Atomic, immutable objects are returned unmodified.  Sets and tuples are\n    converted to lists, lists are copied and dicts are also copied.\n\n    Note: dicts whose keys could cause collisions upon encoding (such as a dict\n    with both the number 1 and the string '1' as keys) will cause a ValueError\n    to be raised.\n\n    Parameters\n    ----------\n    obj : any python object\n\n    Returns\n    -------\n    out : object\n\n      A version of the input which will not cause an encoding error when\n      encoded as JSON.  Note that this function does not *encode* its inputs,\n      it simply sanitizes it so that there will be no encoding errors later.\n\n    \"\"\"\n    # types that are 'atomic' and ok in json as-is.\n    atomic_ok = (str, type(None))\n\n    # containers that we need to convert into lists\n    container_to_list = (tuple, set, types.GeneratorType)\n\n    # Since bools are a subtype of Integrals, which are a subtype of Reals,\n    # we have to check them in that order.\n\n    if isinstance(obj, bool):\n        return obj\n\n    if isinstance(obj, numbers.Integral):\n        # cast int to int, in case subclasses override __str__ (e.g. boost enum, #4598)\n        return int(obj)\n\n    if isinstance(obj, numbers.Real):\n        # cast out-of-range floats to their reprs\n        if math.isnan(obj) or math.isinf(obj):\n            return repr(obj)\n        return float(obj)\n\n    if isinstance(obj, atomic_ok):\n        return obj\n\n    if isinstance(obj, bytes):\n        return b2a_base64(obj).decode(\"ascii\")\n\n    if isinstance(obj, container_to_list) or (\n        hasattr(obj, \"__iter__\") and hasattr(obj, \"__next__\")\n    ):\n        obj = list(obj)\n\n    if isinstance(obj, list):\n        return [json_clean(x) for x in obj]\n\n    if isinstance(obj, dict):\n        # First, validate that the dict won't lose data in conversion due to\n        # key collisions after stringification.  This can happen with keys like\n        # True and 'true' or 1 and '1', which collide in JSON.\n        nkeys = len(obj)\n        nkeys_collapsed = len(set(map(str, obj)))\n        if nkeys != nkeys_collapsed:\n            raise ValueError(\n                \"dict cannot be safely converted to JSON: \"\n                \"key collision would lead to dropped values\"\n            )\n        # If all OK, proceed by making the new dict that will be json-safe\n        out = {}\n        for k, v in iter(obj.items()):\n            out[str(k)] = json_clean(v)\n        return out\n    if isinstance(obj, datetime):\n        return obj.strftime(ISO8601)\n\n    # we don't understand it, it's probably an unserializable object\n    raise ValueError(\"Can't clean for JSON: %r\" % obj)\n"
  },
  {
    "path": "nbclient/output_widget.py",
    "content": "\"\"\"An output widget mimic.\"\"\"\nfrom __future__ import annotations\n\nfrom typing import Any\n\nfrom jupyter_client.client import KernelClient\nfrom nbformat import NotebookNode\nfrom nbformat.v4 import output_from_msg\n\nfrom .jsonutil import json_clean\n\n\nclass OutputWidget:\n    \"\"\"This class mimics a front end output widget\"\"\"\n\n    def __init__(\n        self, comm_id: str, state: dict[str, Any], kernel_client: KernelClient, executor: Any\n    ) -> None:\n        \"\"\"Initialize the widget.\"\"\"\n        self.comm_id: str = comm_id\n        self.state: dict[str, Any] = state\n        self.kernel_client: KernelClient = kernel_client\n        self.executor = executor\n        self.topic: bytes = (\"comm-%s\" % self.comm_id).encode(\"ascii\")\n        self.outputs: list[NotebookNode] = self.state[\"outputs\"]\n        self.clear_before_next_output: bool = False\n\n    def clear_output(self, outs: list[NotebookNode], msg: dict[str, Any], cell_index: int) -> None:\n        \"\"\"Clear output.\"\"\"\n        self.parent_header = msg[\"parent_header\"]\n        content = msg[\"content\"]\n        if content.get(\"wait\"):\n            self.clear_before_next_output = True\n        else:\n            self.outputs = []\n            # sync back the state to the kernel\n            self.sync_state()\n            if hasattr(self.executor, \"widget_state\"):\n                # sync the state to the nbconvert state as well, since that is used for testing\n                self.executor.widget_state[self.comm_id][\"outputs\"] = self.outputs\n\n    def sync_state(self) -> None:\n        \"\"\"Sync state.\"\"\"\n        state = {\"outputs\": self.outputs}\n        msg = {\"method\": \"update\", \"state\": state, \"buffer_paths\": []}\n        self.send(msg)\n\n    def _publish_msg(\n        self,\n        msg_type: str,\n        data: dict[str, Any] | None = None,\n        metadata: dict[str, Any] | None = None,\n        buffers: list[Any] | None = None,\n        **keys: Any,\n    ) -> None:\n        \"\"\"Helper for sending a comm message on IOPub\"\"\"\n        data = {} if data is None else data\n        metadata = {} if metadata is None else metadata\n        content = json_clean(dict(data=data, comm_id=self.comm_id, **keys))\n        msg = self.kernel_client.session.msg(\n            msg_type, content=content, parent=self.parent_header, metadata=metadata\n        )\n        self.kernel_client.shell_channel.send(msg)\n\n    def send(\n        self,\n        data: dict[str, Any] | None = None,\n        metadata: dict[str, Any] | None = None,\n        buffers: list[Any] | None = None,\n    ) -> None:\n        \"\"\"Send a comm message.\"\"\"\n        self._publish_msg(\"comm_msg\", data=data, metadata=metadata, buffers=buffers)\n\n    def output(\n        self, outs: list[NotebookNode], msg: dict[str, Any], display_id: str | None, cell_index: int\n    ) -> None:\n        \"\"\"Handle output.\"\"\"\n        if self.clear_before_next_output:\n            self.outputs = []\n            self.clear_before_next_output = False\n        self.parent_header = msg[\"parent_header\"]\n        output = output_from_msg(msg)  # type:ignore[no-untyped-call]\n\n        if self.outputs:\n            # try to coalesce/merge output text\n            last_output = self.outputs[-1]\n            if (\n                last_output[\"output_type\"] == \"stream\"\n                and output[\"output_type\"] == \"stream\"\n                and last_output[\"name\"] == output[\"name\"]\n            ):\n                last_output[\"text\"] += output[\"text\"]\n            else:\n                self.outputs.append(output)\n        else:\n            self.outputs.append(output)\n        self.sync_state()\n        if hasattr(self.executor, \"widget_state\"):\n            # sync the state to the nbconvert state as well, since that is used for testing\n            self.executor.widget_state[self.comm_id][\"outputs\"] = self.outputs\n\n    def set_state(self, state: dict[str, Any]) -> None:\n        \"\"\"Set the state.\"\"\"\n        if \"msg_id\" in state:\n            msg_id = state.get(\"msg_id\")\n            if msg_id:\n                self.executor.register_output_hook(msg_id, self)\n                self.msg_id = msg_id\n            else:\n                self.executor.remove_output_hook(self.msg_id, self)\n                self.msg_id = msg_id\n\n    def handle_msg(self, msg: dict[str, Any]) -> None:\n        \"\"\"Handle a message.\"\"\"\n        content = msg[\"content\"]\n        comm_id = content[\"comm_id\"]\n        if comm_id != self.comm_id:\n            raise AssertionError(\"Mismatched comm id\")\n        data = content[\"data\"]\n        if \"state\" in data:\n            self.set_state(data[\"state\"])\n"
  },
  {
    "path": "nbclient/py.typed",
    "content": ""
  },
  {
    "path": "nbclient/util.py",
    "content": "\"\"\"General utility methods\"\"\"\n\n# Copyright (c) Jupyter Development Team.\n# Distributed under the terms of the Modified BSD License.\nfrom __future__ import annotations\n\nimport inspect\nfrom collections.abc import Callable\nfrom typing import Any\n\nfrom jupyter_core.utils import ensure_async, run_sync\n\n__all__ = [\"ensure_async\", \"run_sync\", \"run_hook\"]\n\n\nasync def run_hook(hook: Callable[..., Any] | None, **kwargs: Any) -> None:\n    \"\"\"Run a hook callback.\"\"\"\n    if hook is None:\n        return\n    res = hook(**kwargs)\n    if inspect.isawaitable(res):\n        await res\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\n    \"hatchling>=1.10.0\",\n]\nbuild-backend = \"hatchling.build\"\n\n[project]\nname = \"nbclient\"\ndynamic = [\n    \"version\",\n]\ndescription = \"A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor.\"\nreadme = \"README.md\"\nlicense = { file = \"LICENSE\" }\nrequires-python = \">=3.10.0\"\nauthors = [\n    { name = \"Jupyter Development Team\", email = \"jupyter@googlegroups.com\" },\n]\nkeywords = [\n    \"executor\",\n    \"jupyter\",\n    \"notebook\",\n    \"pipeline\",\n]\nclassifiers = [\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: Science/Research\",\n    \"Intended Audience :: System Administrators\",\n    \"License :: OSI Approved :: BSD License\",\n    \"Programming Language :: Python\",\n    \"Programming Language :: Python :: 3\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Programming Language :: Python :: 3.14\",\n]\ndependencies = [\n    \"jupyter_client>=6.1.12\",\n    \"jupyter_core>=4.12,!=5.0.*\",\n    \"nbformat>=5.1.3\",\n    \"traitlets>=5.4\",\n]\n\n[project.optional-dependencies]\ntest = [\n    \"flaky\",\n    \"ipykernel>=6.19.3\",\n    \"ipython\",\n    \"ipywidgets\",\n    \"nbconvert>=7.1.0\",\n    \"pytest-asyncio >=1.3.0\",\n    \"pytest-cov>=4.0\",\n    \"pytest>=9.0.1,<10\",\n    \"testpath\",\n    \"xmltodict\",\n]\ndocs = [\n    \"autodoc-traits\",\n    \"mock\",\n    \"moto\",\n    \"myst-parser\",\n    \"sphinx-book-theme\",\n    \"sphinxcontrib_spelling\",\n    \"sphinx>=1.7\",\n    \"nbclient[test]\",\n]\ndev = [\n    \"pre-commit\",\n]\n\n[project.scripts]\njupyter-execute = \"nbclient.cli:main\"\n\n[project.urls]\nDocumentation = \"https://nbclient.readthedocs.io\"\nFunding = \"https://numfocus.org/\"\nHomepage = \"https://jupyter.org\"\nSource = \"https://github.com/jupyter/nbclient\"\nTracker = \"https://github.com/jupyter/nbclient/issues\"\n\n[tool.hatch.version]\npath = \"nbclient/_version.py\"\n\n[tool.hatch.build.targets.sdist]\ninclude = [\n    \"/nbclient\",\n    \"/tests\"\n]\n\n[tool.hatch.envs.docs]\nfeatures = [\"docs\"]\n[tool.hatch.envs.docs.scripts]\nbuild = \"make -C docs html SPHINXOPTS='-W'\"\n\n[tool.hatch.envs.test]\nfeatures = [\"test\"]\n[tool.hatch.envs.test.scripts]\ntest = \"python -m pytest -vv {args}\"\nnowarn = \"test -W default {args}\"\n\n[tool.hatch.envs.cov]\nfeatures = [\"test\"]\ndependencies = [\"coverage[toml]\", \"pytest-cov\"]\n[tool.hatch.envs.cov.scripts]\ntest = \"python -m pytest -vv --cov nbclient --cov-branch --cov-report term-missing:skip-covered {args}\"\nnowarn = \"test -W default {args}\"\n\n[tool.hatch.envs.lint]\ndetached = true\ndependencies = [\"pre-commit\"]\n[tool.hatch.envs.lint.scripts]\nbuild = [\n    \"pre-commit run --all-files ruff\",\n    \"pre-commit run --all-files ruff-format\"\n]\n\n[tool.hatch.envs.typing]\ndependencies = [ \"pre-commit\"]\ndetached = true\n[tool.hatch.envs.typing.scripts]\ntest = \"pre-commit run --all-files --hook-stage manual mypy\"\n\n[tool.pytest.ini_options]\nminversion = \"6.0\"\nxfail_strict = true\nlog_cli_level = \"info\"\naddopts = [\n  \"-ra\", \"--durations=10\", \"--color=yes\", \"--doctest-modules\",\n   \"--showlocals\", \"--strict-markers\", \"--strict-config\"\n]\ntestpaths = [\"tests\"]\nfilterwarnings= [\n  # Fail on warnings\n  \"error\",\n  \"module:Jupyter is migrating its paths:DeprecationWarning\",\n  \"module:unclosed <socket.socket:ResourceWarning\",\n  \"module:There is no current event loop:DeprecationWarning\",\n  \"module:unclosed event loop:ResourceWarning\",\n  \"module:Unclosed socket <zmq:ResourceWarning\",\n  \"module:zmq.eventloop.ioloop is deprecated:DeprecationWarning\",\n  \"module:subprocess .* is still running:ResourceWarning\",\n  \"module:Unclosed context <zmq:ResourceWarning\",\n  \"module:datetime.datetime.utc:DeprecationWarning\",\n  \"module:'asyncio.WindowsSelectorEventLoopPolicy' is deprecated and slated for removal in Python 3.16:DeprecationWarning\",\n  \"module:'asyncio.set_event_loop_policy' is deprecated and slated for removal in Python 3.16:DeprecationWarning\",\n]\n\n[tool.coverage.report]\nexclude_lines = [\n  \"pragma: no cover\",\n  \"def __repr__\",\n  \"if self.debug:\",\n  \"if settings.DEBUG\",\n  \"raise AssertionError\",\n  \"raise NotImplementedError\",\n  \"if 0:\",\n  \"if __name__ == .__main__.:\",\n  \"class .*\\bProtocol\\\\):\",\n\"@(abc\\\\.)?abstractmethod\",\n]\n\n[tool.coverage.run]\nrelative_files = true\nsource = [\"nbclient\"]\n\n[tool.mypy]\nfiles = \"nbclient\"\npython_version = \"3.10\"\nstrict = true\nenable_error_code = [\"ignore-without-code\", \"redundant-expr\", \"truthy-bool\"]\nwarn_unreachable = true\n\n[[tool.mypy.overrides]]\nmodule = [\n    \"async_generator.*\",\n    \"testpath\",\n    \"xmltodict\",\n]\nignore_missing_imports = true\n\n[tool.ruff]\nline-length = 100\n\n[tool.ruff.lint]\nselect = [\n  \"A\", \"B\", \"C\", \"E\", \"F\", \"FBT\", \"I\", \"N\", \"Q\", \"RUF\", \"S\", \"T\",\n  \"UP\", \"W\", \"YTT\",\n]\nignore = [\n# Q000 Single quotes found but double quotes preferred\n\"Q000\",\n# FBT001 Boolean positional arg in function definition\n\"FBT001\", \"FBT002\", \"FBT003\",\n# C901 `async_setup_kernel` is too complex (12)\n\"C901\",\n]\n\n[tool.ruff.lint.per-file-ignores]\n# S101 Use of `assert` detected\n\"tests/*\" = [\"S101\"]\n\"nbclient/client.py\" = [\"S101\"]\n\"*.ipynb\" = [\"B\", \"E402\", \"T201\", \"F821\", \"A001\", \"E722\", \"S110\", \"RUF001\"]\n\n[tool.interrogate]\nignore-init-module=true\nignore-private=true\nignore-semiprivate=true\nignore-property-decorators=true\nignore-nested-functions=true\nignore-nested-classes=true\nfail-under=100\nexclude = [\"tests\", \"docs\"]\n\n[tool.repo-review]\nignore = [\"PY005\", \"PY007\", \"GH102\"]\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/base.py",
    "content": "import unittest\n\nfrom nbformat import v4 as nbformat\n\n# mypy: disable-error-code=\"no-untyped-call,no-untyped-def\"\n\n\nclass NBClientTestsBase(unittest.TestCase):\n    def build_notebook(self, with_json_outputs=False):\n        \"\"\"Build a notebook in memory for use with NotebookClient tests\"\"\"\n\n        outputs = [\n            nbformat.new_output(\"stream\", name=\"stdout\", text=\"a\"),\n            nbformat.new_output(\"display_data\", data={\"text/plain\": \"b\"}),\n            nbformat.new_output(\"stream\", name=\"stdout\", text=\"c\"),\n            nbformat.new_output(\"stream\", name=\"stdout\", text=\"d\"),\n            nbformat.new_output(\"stream\", name=\"stderr\", text=\"e\"),\n            nbformat.new_output(\"stream\", name=\"stderr\", text=\"f\"),\n            nbformat.new_output(\"display_data\", data={\"image/png\": \"Zw==\"}),  # g\n            nbformat.new_output(\"display_data\", data={\"application/pdf\": \"aA==\"}),  # h\n        ]\n        if with_json_outputs:\n            outputs.extend(\n                [\n                    nbformat.new_output(\"display_data\", data={\"application/json\": [1, 2, 3]}),  # j\n                    nbformat.new_output(\n                        \"display_data\", data={\"application/json\": {\"a\": 1, \"c\": {\"b\": 2}}}\n                    ),  # k\n                    nbformat.new_output(\"display_data\", data={\"application/json\": \"abc\"}),  # l\n                    nbformat.new_output(\"display_data\", data={\"application/json\": 15.03}),  # m\n                ]\n            )\n\n        cells = [\n            nbformat.new_code_cell(source=\"$ e $\", execution_count=1, outputs=outputs),\n            nbformat.new_markdown_cell(source=\"$ e $\"),\n        ]\n\n        return nbformat.new_notebook(cells=cells)\n\n    def build_resources(self):\n        \"\"\"Build an empty resources dictionary.\"\"\"\n        return {\"metadata\": {}}\n\n    @classmethod\n    def merge_dicts(cls, *dict_args):\n        # Because this is annoying to do inline\n        outcome = {}\n        for d in dict_args:\n            outcome.update(d)\n        return outcome\n"
  },
  {
    "path": "tests/conftest.py",
    "content": "import asyncio\nimport os\n\n# This is important for ipykernel to show the same string\n# instead of randomly generated file names in outputs.\n# See: https://github.com/ipython/ipykernel/blob/360685c6/ipykernel/compiler.py#L50-L55\nos.environ[\"IPYKERNEL_CELL_NAME\"] = \"<IPY-INPUT>\"\n\nif os.name == \"nt\" and hasattr(asyncio, \"WindowsSelectorEventLoopPolicy\"):\n    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())\n"
  },
  {
    "path": "tests/fake_kernelmanager.py",
    "content": "from jupyter_client.manager import AsyncKernelManager\n\n# mypy: disable-error-code=\"no-untyped-call,no-untyped-def\"\n\n\nclass FakeCustomKernelManager(AsyncKernelManager):\n    expected_methods = {\"__init__\": 0, \"client\": 0, \"start_kernel\": 0}  # noqa\n\n    def __init__(self, *args, **kwargs):\n        self.log.info(\"FakeCustomKernelManager initialized\")\n        self.expected_methods[\"__init__\"] += 1\n        super().__init__(*args, **kwargs)\n\n    async def start_kernel(self, *args, **kwargs):\n        self.log.info(\"FakeCustomKernelManager started a kernel\")\n        self.expected_methods[\"start_kernel\"] += 1\n        return await super().start_kernel(*args, **kwargs)\n\n    def client(self, *args, **kwargs):\n        self.log.info(\"FakeCustomKernelManager created a client\")\n        self.expected_methods[\"client\"] += 1\n        return super().client(*args, **kwargs)\n"
  },
  {
    "path": "tests/files/Autokill.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import os\\n\",\n    \"import signal\\n\",\n    \"\\n\",\n    \"pid = os.getpid()\\n\",\n    \"os.kill(pid, signal.SIGTERM)\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.8.3\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 4\n}\n"
  },
  {
    "path": "tests/files/Check History in Memory.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from IPython import get_ipython\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"scrolled\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ip = get_ipython()\\n\",\n    \"assert ip.history_manager.hist_file == \\\":memory:\\\"\"\n   ]\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "tests/files/Clear Output.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"from IPython.display import clear_output\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"9\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"for i in range(10):\\n\",\n    \"    clear_output()\\n\",\n    \"    print(i)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(\\\"Hello world\\\")\\n\",\n    \"clear_output()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Hello world\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Hello world\\\", end=\\\"\\\")\\n\",\n    \"clear_output(wait=True)  # no output after this\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"world\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Hello\\\", end=\\\"\\\")\\n\",\n    \"clear_output(wait=True)  # here we have new output after wait=True\\n\",\n    \"print(\\\"world\\\", end=\\\"\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"'Hello world'\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"handle0 = display(\\\"Hello world\\\", display_id=\\\"id0\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"'world'\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"handle1 = display(\\\"Hello\\\", display_id=\\\"id1\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"handle1.update(\\\"world\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"handle2 = display(\\\"Hello world\\\", display_id=\\\"id2\\\")\\n\",\n    \"clear_output()  # clears all output, also with display_ids\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"'Hello world'\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"handle3 = display(\\\"Hello world\\\", display_id=\\\"id3\\\")\\n\",\n    \"clear_output(wait=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"world\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"handle4 = display(\\\"Hello\\\", display_id=\\\"id4\\\")\\n\",\n    \"clear_output(wait=True)\\n\",\n    \"print(\\\"world\\\", end=\\\"\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"handle4.update(\\\"Hello world\\\")  # it is cleared, so it should not show up in the above cell\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.6.7\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 1\n}\n"
  },
  {
    "path": "tests/files/Disable Stdin.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"try:\\n\",\n    \"    input = raw_input\\n\",\n    \"except:\\n\",\n    \"    pass\\n\",\n    \"\\n\",\n    \"name = input(\\\"name: \\\")\"\n   ]\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "tests/files/Empty Cell.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"Test that executing skips over an empty cell.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"'Code 1'\"\n      ]\n     },\n     \"execution_count\": 1,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"\\\"Code 1\\\"\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": []\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"'Code 2'\"\n      ]\n     },\n     \"execution_count\": 2,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"\\\"Code 2\\\"\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.5.2\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "tests/files/Error.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"id\": \"d200673b\",\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"ename\": \"ZeroDivisionError\",\n     \"evalue\": \"division by zero\",\n     \"output_type\": \"error\",\n     \"traceback\": [\n      \"\\u001b[0;31m---------------------------------------------------------------------------\\u001b[0m\",\n      \"\\u001b[0;31mZeroDivisionError\\u001b[0m                         Traceback (most recent call last)\",\n      \"\\u001b[0;32m/tmp/ipykernel_1277493/182040962.py\\u001b[0m in \\u001b[0;36m<module>\\u001b[0;34m\\u001b[0m\\n\\u001b[0;32m----> 1\\u001b[0;31m \\u001b[0;36m0\\u001b[0m\\u001b[0;34m/\\u001b[0m\\u001b[0;36m0\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0m\\n\\u001b[0m\",\n      \"\\u001b[0;31mZeroDivisionError\\u001b[0m: division by zero\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"0 / 0\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3 (ipykernel)\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.9.5\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 5\n}\n"
  },
  {
    "path": "tests/files/Factorials.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"i, j = 1, 1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"2\\n\",\n      \"3\\n\",\n      \"5\\n\",\n      \"8\\n\",\n      \"13\\n\",\n      \"21\\n\",\n      \"34\\n\",\n      \"55\\n\",\n      \"89\\n\",\n      \"144\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"for m in range(10):\\n\",\n    \"    i, j = j, i + j\\n\",\n    \"    print(j)\"\n   ]\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "tests/files/HelloWorld.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"Hello World\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"Hello World\\\")\"\n   ]\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "tests/files/Inline Image.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from IPython.display import Image\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/png\": [\n       \"iVBORw0KGgoAAAANSUhEUgAAAMgAAABQCAYAAABcbTqwAAAABHNCSVQICAgIfAhkiAAAABl0RVh0\\n\",\n       \"U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAACAASURBVHic7Z15eJNV2vDvJ0/2vUmapk3b\\n\",\n       \"dG9pKYWyFARkd6GojOI2vs6HfjqKI17i+LnNfM68LpfMq4KjM6/44jbgqKCfKLIoUFrWUigglO5N\\n\",\n       \"1yxttmZfnzzP90dImrZpmqY75nddVZJnO0nOfc597u0gBEFUQ4wYMUJCmuwGxIgxlYkJSIwYYYgJ\\n\",\n       \"SIwYYYgJSIwYYYgJSIxh8Xg8SHV1Na+srEw42W2ZaMiT3YAYU5vLly9zdu7cmeXxeEh5eXnGVatW\\n\",\n       \"6Se7TRPJlBGQbqONcr5Rzdaa7BSDxUExWl1ko81JNtmcFLPNRTbZnWTMi5PYdCrGYVAxLouK8ehU\\n\",\n       \"jMemYVwmDeOz6Vg8j+lZXJBqEXAY2GR/nhsFq9VK8Xg8v1pNY1IF5Gy9in2yVsG72NLD69QYGQAI\\n\",\n       \"ABAABMD1/wx4TYDR6qQYrQ4KaAGACDoHfK9JCEIUyESWFUXpvQ+sKNSjKImAGDGiZFIExGBxkl/b\\n\",\n       \"U5laWa+O6xMEJMwVkfdxnCCQmnYNt6ZNwz14vkn0xsaVbRlJQteoGnyDg+M4kEi/2kkiLBP+rdR1\\n\",\n       \"GRgPvnMov7JBHTfez5KrDKyN/7Uv/0h1M2+8nzXdwDAMOX/+PO/9999P37lzp2yy2zNVmXAB+eDH\\n\",\n       \"y1KjzUUZ+D4SbgIZBS7MS9r69ck0lc486Jm/ZiorK+N27tyZVVNTI3C5XOhkt2eqMqECclmuYV5q\\n\",\n       \"1fQbzedlJ8C7jy6DY2/eB6/cVwLpCWM/2NscHvKrn5el4fiY3zrGDc6ECkhVcw8n+DWXQYV3//dy\\n\",\n       \"WFKQDGw6Fe5amA2v/ceScXl2TVsPt6FTwxiXm8e4YZlQATFYHP3UnNIFGUCn9LcT5EgFUJgWPy7P\\n\",\n       \"r2nvZo7LjWPcsEyoFavX6pzUdUB9h5YFAJPq6HI6nSStVkvV6XQUp9OJZmZm2sVisTvS6+VyOUMm\\n\",\n       \"kznJZPKUMV/jOA4EQSAoio64TV6vF+ns7KSr1Wp6YmKiMzU11RnNfQB8hge1Wk1Tq9U0j8dD4vF4\\n\",\n       \"nszMTDuDwYhauZ5QAbG7sH4z1o9VcnjytllAp/Y1o1FhgJp27bg8X2uyT4qAWiwW9OTJk8LKykpR\\n\",\n       \"T0/PIDWPy+W6c3JyzBs3buyi0Wghf8y9e/cmXrp0SaDX6+nbtm37hcPheMM986233spWKpUsAIBN\\n\",\n       \"mza1FBQUWAEADh8+HH/w4EGp1+sNmEVqa2v5Tz/99Gz/661bt9aw2ewh749hGFJWViZsbGzkajQa\\n\",\n       \"ul6vpxEEARKJxDF//nzD7bffrhnObFxTU8P+/vvvpSqViolhff2CTCbjSUlJ9vXr1ysLCwutYW8C\\n\",\n       \"PgE7fvy4sLy8PEGn09EIguhn7mEymdiaNWvUt956q5ZCoYxY8CbVUWh1euCZ/zkO9y/Ng7mZYjhy\\n\",\n       \"uQP2nqqfzCaNOfv27ZP8/PPPiV6vN9AJ2Gy2RywWOx0OB6pWq5lms5laXV0t0mq19GeffbYlVOc8\\n\",\n       \"fvx4YnCnHg6Xy4X6rVPB1+E4jmAYRiKIvr7ify+C2yLHjx8XHj58ONFoNNIGHlQqlSylUsmqra3l\\n\",\n       \"bdmypWWoDrl///6EAwcOSP2dmU6nYxKJxKlUKpkej4fU2dnJ/uCDD3LWrl2rXL9+fU+oe+A4DqdO\\n\",\n       \"nRIcOnQoyWAw0FAUJSQSiUMsFjvtdjtZrVYzrFYrxW63k3/44YeUioqKhOeff75RIpFEPFsDTIFQ\\n\",\n       \"kyttWrjSph3sFZ/m4DgOO3bsSLt8+bIQAEAsFjvWrVunKiwstAQLgF6vp/z973/PUqvVzI6ODvbb\\n\",\n       \"b7+d8+qrrzZEq2YMR2lpqaa0tFRz6tQpwa5du9IBAAoLC3ufeeaZ1uGubWho4DU0NPAQBCHmzp2r\\n\",\n       \"z8/PN+Xl5dnsdjt69uxZQXl5uQQAoLm5mfvDDz9INmzYoB54j48//ji1qqoqHgBAIBC4Hnnkkbac\\n\",\n       \"nBwbiUQCj8eD1NTUcHbv3p1mtVopBw8eTO7u7mY8+eST7cH3aG5uZn7++edpGo2GQSaT8aVLl/bc\\n\",\n       \"dddd3TweLxBihGEYcuDAAfGhQ4ekBEEgJpOJumvXrtQXXnihZSTf16QLyI2Kx+Mh+YUDAOD111+v\\n\",\n       \"C6V2CIVCz3PPPdf82muv5VssFopKpWKePn06btmyZYYJbXCEpKenWx588MGu9PR0R/D7aWlpSgRB\\n\",\n       \"iOPHjycCAJSVlUnWrl2rYTKZgcGgvr6e5ReO+Ph4x4svvtgU3KkpFApRXFxs5vP5zdu3b891Op3o\\n\",\n       \"xYsXhTU1NfrCwkKL/7yGhga2RuOzSBYXF+t/97vfKQa2k0wmE+vXr+9xOByov00tLS1cl8tFGkqN\\n\",\n       \"DUUsvmCCCKeT8/l8bN68eQHjQXl5uXhCGjVCMjMzza+88krTQOHwc9NNNwWEGsMwUkdHB93/Gsdx\\n\",\n       \"2LNnT4r/9e23395vxA8mIyPDUVpaqvS/3rt3bzIepRPrjjvuCKhoBEEgKpVqkGoYjpiATBEWLlwY\\n\",\n       \"6FxKpZLV2Ng45UzSFAolbC+VyWRODofj8b9WqVQBAampqeH4jQZsNtuzaNGi3nD3WrJkiYFMJuMA\\n\",\n       \"AN3d3cyLFy9G5UFms9leNpsdaBOO4yOK2ZhQFeuvDy7ssLuwrtBHh1e5d5VdTThwrmVKjq6jJSMj\\n\",\n       \"w4GiKO5fzHd1dTFyc3Ptk92ukSKTyazXrl2LA/Cpmf73Ozs7A9a7pKQk+3Bmajab7U1LS7O2tLRw\\n\",\n       \"AXzfx/z5803RtIlGo3mtVmtUFswJFZB4HnNUeRpsBjWsaXO6w2QyMYvFQgUAMBgM1MluTzRQqdSQ\\n\",\n       \"s4xarQ4ICI/H84Q6ZyBcLjdwXnd3Nz3cuePFmAiIXG2inWpQczu1FnqnzkLXmx3UgBmRGJjXAX2v\\n\",\n       \"g/8/6Lygk68fs9idYyPQ5vNs6Ngm6/9sAgAQAGqCC5jZdmDNsIFgpRmQ8bEmhYLFYgUExGg0TksB\\n\",\n       \"GQqtVhvo4JEKSLC6ptFopp+AdGgt1A+P1CZV1CgFfQ6aAR18yI4fyXkhjo0FXjsJ3N30wQICAC4V\\n\",\n       \"HSyXfPpu91dWyHqzFWiJEf2goyXYb2CxWG4oCyOFQgnM/h6PJ6J1QLCKNtTMNN5EvUjfc0YufHD7\\n\",\n       \"sZnlNUohQYTNdpq+2BrYcG1jPlivTUiQo9VqDQhFsHpxIyASiQJJa2ZzZKkHwYOEUDg5SW9RCciR\\n\",\n       \"Kwreeweuyrw4cWMKRjBeCxla30gDInIvdrQELyRFItGIPL5TnYSEhEAHj1R9DP4+EhISnOPRruEY\\n\",\n       \"sYBUy7Ws17+5mIHfqLNGKJwdTFB8PK7WM41GQw1WKeLj44ccMV0u17Qzz6empgZ8J11dXSyHwxH2\\n\",\n       \"M3i9XiR43ZKSkhLS9zLejPiL/vDnOqnbi0+7H2jUdO9JBNw9boPCyZMnA153CoWCFxUVmYOP0+n0\\n\",\n       \"gAUw2GQaLURwMNYEUFhYaJFIJHYAnxOxurqaH+78y5cvc/0ziEQiccyZM8cc7vzxYkQdvUllol/r\\n\",\n       \"6uUMf+ZomYLxWLgdhd4T3KgvD+MJtlqt6Llz50T+1yUlJdqB0bopKSkBn8iJEyeGTZgZGNU6EIFA\\n\",\n       \"EFjjGI3GCYlyXrt2bSA269ChQ4lWq3XIVN+KiorA97Fu3TrVZBWVGNFT95yRj08m0wTBpJF9nc5r\\n\",\n       \"jy4HW3806kITr7zySkF5eblgYESu3W5Ht23blmUymagAvtmjtLR0UATr7NmzA57nuro6/q5du5JD\\n\",\n       \"qSk1NTXsN998M1elUoX1xMtksoDKolarmd3d3eNuVi4pKTGmpqZaAQB0Oh39ww8/TB84cOA4Dp9+\\n\",\n       \"+mlKY2MjHwAgIyPDUlJSYhzvtg1FxKZEHAcor1MJxrMxoyKCSSdbKvJ1CnuUYRymaj4QRFQVJvR6\\n\",\n       \"Pf3LL79M379/f7JUKrXHx8c7tVotXS6Xc/yh5kwmE9u0aVOLSCQaZMFatWqVXi6Xsy9cuCACADh1\\n\",\n       \"6lTCpUuXBOnp6VaBQOA2Go0UhULBMhgMg2KNEAQZ9O1wOBzvihUrusvLyyUYhpG2b9+eM2fOHENB\\n\",\n       \"QYG5oKDAOh4jNolEghdeeKF5x44dadeuXYtramrivfzyyzOzs7PN6enpNpVKxWhubuao1WomgC8Q\\n\",\n       \"8bHHHusY84aMgIgFpF1rptmc2LS2zRekiX1qir2JFdUNcDsKjlYaMDNHbHLcsmVL4+HDhxMaGhr4\\n\",\n       \"jY2NvMbGxn6xRUKh0Ll58+YWqVQ65L0fffTRToFA4C4rK5NgGEay2WwUf1iHH6lUaisuLu5tbGzk\\n\",\n       \"NDU18QAAhsqou//++1Verxc5c+ZMvMFgoJWVlSWWlZUlRpKQFS00Gg3fvHlz6zfffJNUUVGRYDAY\\n\",\n       \"aFVVVfH+KF8AACqV6l2zZo16qFyQiSTiDn+1XR9dp5oisBhkrChTYgOCAHC2Rx8IaL3GjEZA8vPz\\n\",\n       \"rfn5+VaFQkFvaGhgaTQautVqJctkMltBQYE1OTl5WDMmmUwmNmzYoF6xYoWutraW7Q+/4PP5nri4\\n\",\n       \"OI9MJnP403ffeeedwFoxOOQ8GBRFiYcfflixdu3anqamJlZPTw/N5XKhdDo9IFCFhYXmzZs3NwEA\\n\",\n       \"cDicYUOF1q1b17148WI9AEBSUlLIz0QikeD+++9XbdiwQd3W1sZobm5mdXd305OSkhw5OTk2mUzm\\n\",\n       \"CDeDLVy40OhXEYPXUkOxcePGdrfbTQrXpqGIWEDqlMYpF106Ev5w16IuNoOGg6GMB7gj+jpQtnoW\\n\",\n       \"wF1hI1HDkZyc7IxEGMIhFAo9N998c9g26PX6gKrFYrHCzgZCodCzaNGikHo+n8/H+Hy+JdSxUKSk\\n\",\n       \"pDhTUlIi+nwoihJZWVn2rKysEQVlxsfHu+Pj4yP2E+Xl5dlGcv9gIlY0WzWWaVsyZ26u1HjP0gID\\n\",\n       \"eIwoKD4YXRVBe/OUHyhwHIfe3t6AgITLL48RnohnkF7r4GqIE8IoLb4rZqfr//TbZb4Q+863UwEz\\n\",\n       \"je5zuDVTPohQp9NR/dYyiURiH6/03V8DEQuI1emZVgt0cRzLtemO+Yq1C3J8qoP6Xwlgqhy9FQ4z\\n\",\n       \"T/nvoaurK+CBzs/PjyqHIoaPUQjI2AxKCAAkCdjO7CS+LUPCc6AoQvTdeuAzCICQ9hhfBDAZJREZ\\n\",\n       \"SXHOWWliO5/D8KkVLjUFOramga0maidfP3AnCl4nCVD6lCxkajQayV9//XUqgM+8u2DBgqjXSzEi\\n\",\n       \"FBCvF0cw79gGJgo5DPczpbO7FhckWTgM2tjryI52GpgrudD9b+moFuWh8OjIgCZPqWBCj8eDlJWV\\n\",\n       \"iY4ePSoxm81UAIC77767KzMzc1JimG4UIhIQFCURdArqdXq8Y9LRVhel6l7aME8REAyXggb2eia4\\n\",\n       \"2hm+qNng5KoBs0hw7sagxCovAq4OBthbmYDbyb773Jjqt8vlIp0+fTquo6ODqVQqmWq1mukPdqTT\\n\",\n       \"6VhpaanqtttuG58KfL8iIlax2HTKqAUEAYA/318iXzc/w7cuMB6LA83uFPBaKP06/sCkqUGJVUP9\\n\",\n       \"23/tOAsFRTSsPwBFUUIoFDr1ev24ZMKRyWRi3759Kf7icAiCEGKx2JGXl2dev369erwcfb82RiIg\\n\",\n       \"mM7iHJUF586SjJ518zOMgJlR6P5nKliqBVMyMDEcJBoeyfqDTCYTW7durW1paWFWVo6BcWAAKIoS\\n\",\n       \"t9xyixpFUSI9Pd2WmZnpGEm9pxiREbGACDk0d7vWErUPQCpkOZ9bX6wCAADl2+ngqJ+euz6hI8v0\\n\",\n       \"i8YRFil33nnnpIdi3OhE7CjMSuSNarH3yr0L2ukUMg6GA6JpKxwAAIzQRdNiTF3MNgf69ZEq4b8O\\n\",\n       \"nhENf3Z/Ip5B8qT866PgyFUiLpPqmZeZYAOvBQXt18kjvsFUgjsn4rCLGFODl//5rez0L81xty6a\\n\",\n       \"qQMA3UiujXgGmZnCj1pNyE6K88XC2BuYQEzz/fC482MCMs3AvL5qimTSyLcEj1hAUkUcN4dOGb7w\\n\",\n       \"W4gmzEiO8wmXs2VaRwQDysaAPTOmYk0zsOthNyg6jgICALA4LyEqr2xO0vXZxzX6XOpJRbDCMG7b\\n\",\n       \"8cYYN7z+GSSKmLQRCci6ualRbV9GRq9ntBHTrxpHPxLuiTnepiF+FQsdTxULAGB+ltgm4TMmpT7R\\n\",\n       \"pMPMsQIr79f52ac5/hkERUc+Po/4is23zxy0WcmvguTfqya7CTGiw4v7VayRzyAjDt1ePSvZVHFN\\n\",\n       \"pTt6VTFim/K0hX+zHuKWjMp6pek1k89caeGotL3Ubr2JKhHy3HNnpFnn5MrsNEr4rQA0vWbyxfp2\\n\",\n       \"Vk2LkuXBMKQwK9k2Ny/NJhXHDXJa2hwu0uGzV/kpCUJXycyMkJl0ap2Rsq/ikuC3ty7U8TmD03Fr\\n\",\n       \"W5WMK01dzLuWzellMfp7501WB1p1Tc6ub1MzUhOFrtnZKfZ06dBF7vxgXhyplSsYV5q7mLWtKpbJ\\n\",\n       \"aifftqjQsH558aB1bV2bil51Tc7xYF6kIENqn52Tah/YjoGYrA70fG0rq7q+neP2YIhUHOdaUJBu\\n\",\n       \"nZWV4sD6ZpCJ2cTzpbvndCn0Vnq90siO5vppBTXBBWl/jHrWvNTQwfz4h5OSc9fkfP9UH2DfCUhL\\n\",\n       \"FNl3/nljsziOG9JCuOvgGdH7e46leLC+TUD3HrsACAKwcd0S5TMPrOkmBRkOHv7Lzly5QsNcNX+G\\n\",\n       \"PpSAlF2o4/7xva+zCQIgns/x3Lt6/qCt3vaf/EXw9ZEqybLiXHNwx3z3i58Sdx+uTCQIAiGREAK/\\n\",\n       \"Xnr2jqWzNa8+dqeCGkLQPZgX+eLwWdGXP51L0PRaAlmOCIIQW357qzL43Dallrbpb7uy1DoT3X8O\\n\",\n       \"QRCIkMd2/23zva3z89NDCvyFujbWs9u+zLLaXWQAgOC2yRKFDr3JV4AuGjNvVALCplPwT/6wvPHj\\n\",\n       \"soaEXRWNSRh2g1Za5C3shcy/dgCZG1Xg3+5DZ0XbvzqSCgBw28KZupuLc015skSHWMDFTv/SxHnr\\n\",\n       \"84OydrWO+ch/fpK7+7XfNwi4/XPHX/zgG9lPlTUiBo3i/cO9KztXzJ1hwrxe5Oj5Ov6n+08lffbj\\n\",\n       \"aWldm4q14+X/JfcLybwZaWa5QsOsqm3lYV4cGahWlJ2v4/tjOY9X1/NDCUjl1RZehjTeHjxD/eWj\\n\",\n       \"71O+P3FJnJUstr36+F0dBRlSZ61cwfjTh9+l/XjqF7HZ5iC///xDbcH3MVrs6Oa3v8i82qLg0Chk\\n\",\n       \"fMOqeT2Fmck2iYjndro8pFyZJLCmq2tT0Z/auivHZHOQn/jNcsUDt5boUBIJ/v1Tpeij7yqSn3jr\\n\",\n       \"X7m7/vp4/cxMaT8z+4lLjZz/8/c9WS4PRpqTm2p+fP0y9YKCDFtnt556/EI978ufz0n8gjNhMwgA\\n\",\n       \"AEoiwRNr8nvWFct6q+UaVpPayGzrNjMcLn992b4oWx6LPjaRpeQ4N5CFrpDh7kNG84aLEIaga/37\\n\",\n       \"g8S7gZFlB3a+HXglw+7THY4vDldKvF4ceeLu5YqnNqzsFzd1y8KZJomI1/y7v+zMV2h66V/+dE70\\n\",\n       \"9H2rAuccOnOV91NljYhCRvGdf3qksTArOdAxslISetKTRM6X/vFtZtW1Vv63ZRcE961eYAAAWL0g\\n\",\n       \"37jn6HmJ1e4iV15tYS+dk9NPNTx3rTUQ5nOxoZ3ncLlJDFrf1gKd3XpqR7ee8fDamwJrrjNXmtnf\\n\",\n       \"n7gkBgDY/tyDrakSoRsAoCgn1f7PF/6j5c4/vl948nJTXJtSqwpWt9776kji1RYFR8BluXf/5+ON\\n\",\n       \"yQmCIXNo3vrsQGqvxU5ZvSBf/9S9fd/VUxtW9sgVGsax83XCz348lfDusw+0+495MC/yxqc/ylwe\\n\",\n       \"jJSeJLL/44WH5ezrM15mstiVmSzWPFx6k7b02e0zdUYrNRoz76jTR6VCllsqTHcDwPhnrvFu1kPi\\n\",\n       \"49Nmsex3UNEo5JD686ysFMeMtCRrXZuKvf/k5fin7l3ZQ0IQcHkw5L2vjqQAAKycN8MQLBx+bltU\\n\",\n       \"aNpXccl0rkbO/+i7Culdy4p7aRQyMS8/3cZnMzxGq4NSdqGOFywgV5o6mXqTlbpiXp6+vLpB6HJj\\n\",\n       \"pIqLDZzbb5oVSMs9WlXLAwBYNX9G4L0vfz4nBgDIShHb/MLhR5YocicIuK4eg5m25+h54UsbSwO/\\n\",\n       \"j9vjK4gXx2Vh4YSjrk1Fv9qi4AAArF6QP6gfLSjIMB87XyesuNggsNidnRymL5r627JqgcZgpgEA\\n\",\n       \"PH3faiU7xDqFTqUQpOuF88bdUTjpYEYy2OqZfX91Q/zV9v1Zg/6cnSPa4XS0BBxU5KFHrvkF6WYA\\n\",\n       \"gB6DmdbYrqYDAFxt7mL2XP/h1y6eNeR20LctKjQAAOiMVmpDm4oBAEBCECgpzDQBAJy52tKvQHRZ\\n\",\n       \"tS9I9Pe/Wd6dKOI5AQCOX6jvd87pK808AZflmZMrswMAuD0YUnlVzgcAWDlvRshBMD8jyQoA0KUx\\n\",\n       \"9Mt9CYR4DNMxD56+IgAAoFHJ+Ip5MwYVqfavPTAvjnSo+8oZHTpzRQjgK3Q51PqkXzuiqBY55QsQ\\n\",\n       \"9MN0Ih6MFfGRJ0wN+De72AjZ78gnqrkB82KYxaGQywro+Wq9iTojPclZf72zAwAMHLGDSUmICxxr\\n\",\n       \"VeloRTmpdgCAlfNmGH+uvCbSGMy0a3Ilw6+3n7nSzEsQcF356UnOJUU5xm/KLviMBzjegZJIYHW4\\n\",\n       \"SDUtCs7qBfkBh3C7Wkfzf45DZ66KTlxqjAMAgoQggFxf92iNvm3j9Mb+G2X6rxvOQdfZ3Vcu9eFX\\n\",\n       \"/ycXQRACQRBAAALPCHxHOiPF/3kUGl9po1SJ0MFjM4ZU4wO/Q5iBaiiml4BMM7AIHFRsZt/6rFvv\\n\",\n       \"K0nUptIFRuL4OM6Q+Sc8Vt+mqEpNbyCZbVlxroVGIeMuD0Y6WlXLn5kpdXTrTZSWLg3r7hVzewAA\\n\",\n       \"Vi3IN31TdkFitjnJ52rk7MVF2daKiw1cD+YlLZ+bF1Cv2pS6QOdNlQgdDNrgraATRTwXAICQ17+t\\n\",\n       \"fhVzuBlEpfV1dC6LgaUkCEI6Y/3P8FvVbA4XyWC2UQEAZmYmh10r+o1I0XjSYwIyjvhVLEqYkcvt\\n\",\n       \"6TPfErjvtGChaVVqaUXZoTeP6bXYAr+fWNDXORk0Kl6cJzNX1sj5p39p4m357S3qY+fr/GsLIwDA\\n\",\n       \"goJ0K4/N8JisDsrRqlr+4qJs68lLjTwqhYwvK84NrFs8QdXon75vlbogQxpxsCYWYQyUf4SXJQod\\n\",\n       \"27Y82B7JvYPXE8N1fAyPTFBDMb3WIJGATJ26blgEKoa2t2+/Pr8FKFHEC6hOjR3qIQM8e/R912an\\n\",\n       \"9N+i7ObiXCMAQItCw1JpeymnfmnisZk0bGFhlu16m2DhzOtrlSvNfJwgoKq2lTcnJ9UcbNXKShYH\\n\",\n       \"7mu1jyyWzhuhgy5VInQCADicnojvT6dSCA7Tt6lQU2d32EzXSNsRihtHQOgZAKkvAczcB5D1DgC3\\n\",\n       \"ZLJbFBRFOvQPE6xOZaf6OnlwZ2/u7BlSQJoVvmM0ChnPSZX0E5BbSmaa/Nse7D/5S9zlhg7ugvwM\\n\",\n       \"U3BbVl6fTTS9Ftq/D1eKjBY7ZemcnH6F5jKk8S4SyXef2lbliFKuI12kpyfFOwEAunr0DJcHizhc\\n\",\n       \"2q92tSm1TGygEzYI/wxFGe9o3tEzjqHishcB4lYAkCgA7CKAtFcB0LGpFRcNHqxPNRlqcWi2OdAz\\n\",\n       \"V5vjAADm5qWZ4vm+6unz89Nt2SkJNgCAps6ekJ0SJwg4fLZGCABwz8p5PQNDMUR8NjYjLdEGALD7\\n\",\n       \"8NlElwfrt7YA6FurAADs+K5cCgCwekFBv3OoFDKRIY23AwB8V35xRBsoRTpyF+fJrAAAZpuT/H3F\\n\",\n       \"pYg3KVq7eJYeAMDlwUi1rcqQ1WO8OB6w00z9GYQyfMxOVLBmAdDT+r9HogII1/Z/jyqZsK2EgwXk\\n\",\n       \"5OUmntHSf1crL47DXz76PsXl9vkKnrxnuTr4+KN3LlUDAPzS1Mn95tiFQVVRdnxbnqAxmGlsJg17\\n\",\n       \"8p4VIYs3LJ3jK7tqtbvIZJRErAzybQD0rVX852SliG2JIt4go8BrT/ymg0JG8a4eA2Pr5weTQj3r\\n\",\n       \"aksX40pzV7/ZLtJF+rLiXMuakgIdAMCO/1curWtTDersJqsDPXjmCh8PcgLfu3qBnkmnegEA3vhk\\n\",\n       \"vyzU7FNe3RAYJf3+k5EwsYt0Rq4NjGVjf198iGzgge+zCqIugz9SggXk4Okr8ccv1AtuLs7pzUoW\\n\",\n       \"O5xujHTyUiO/uauHBQDwxN3LFQsK+sdNrV08y9TRrVd89F158t92HZI1dfYwlszONlvsTrTiYgPv\\n\",\n       \"aFWtSMRnu7c9+4B8KBPnLSUFxo++q0gGACjKTjWH6iDL5+YZK2t8fo7FRdkh6/gWZEgdT9+7SrH9\\n\",\n       \"qyOpXx2pSmzq7GHeVJRliudzsHM1ck5VbStPb7JS581IM33yfx9t8V/nvW50iMSD/dffr++6Jlew\\n\",\n       \"1ToT/bHXP8srXVKknZkptat0RurZK8282lYV24vjiDiO2+D3ebAZNPzdZx9o2bLtq6ymzh7WY69/\\n\",\n       \"mn3nzXN0yQkCd0O7inHkXK2grk3FnpGWaH3uoVsHfceRMMECUjCq0I0hcbQA2OoBWDP63vPaAQxH\\n\",\n       \"+5/HmTM+zw9BsIBsvn91Z0O7mnmsqk74c+W1wPsiPtv91IaVintWzgvpgNt0z4oemUTo/O9vj0u/\\n\",\n       \"KTsv2XvsvAQAgEGjektmZhhff/LujgRB6CBHAICslARXsjjOaXe60Q2r5oVM9lpTUmD6265DRLJY\\n\",\n       \"4Fy3pGjIaIiNdyzRshg0747vyqUXG9p5FxvaAyErSSK+87mHbu144JaSfgl1I0l1ZTNo+Cd/frTp\\n\",\n       \"7S8OS8urG4R7j52X7D3mO0Yho/jNxTmGJ36zvHtGev8NcG6alWV9//mHmv9r96GUGrmS7ffIC7gs\\n\",\n       \"T4Y03v7mprvl65bOjnqPQ4QgiOpoL46K7o+lYPxJAgBDV0mMprIiWQggvAMgbhWA9TKA9juf4Pin\\n\",\n       \"5LhVWkh7pXNcP1sQap2Jctsz784CAHj/+YealhXnWowWOypXaGiaXjOlIEPqCOcEHIjZ5kCvNncx\\n\",\n       \"4uO4WHZqgpMUYepvZ7eeKhXHudEw4OU8RgAAAbxJREFUXuR2lY6aliSKqC1eHAdFj4HaqtTSeGym\\n\",\n       \"NytF7OKyQs9gLg+G4DiOkFGUCGfqHojZ5kDlCg2t12InZyTFO1MkgrDt92O02NH6djUjK0Xs9K/n\\n\",\n       \"RsvECwjhRaDjT9ngbOGMqYCE86QzMm2Q849GIFEnrIxjV4+Bum7Le4UAAP/94sONi4uyJ2z2ijF2\\n\",\n       \"TLyZF0EJSH5ZDqy5Q8YYjSmsmWbIeEM+kcIB0F/FGsnoGWNqMTmedDLPCykvt4HpRC/0fJYKXvPY\\n\",\n       \"716FsjFIfLQLRHdMjCAOwB1kUYkJyPRlckNNeMuMwL3JBKbTPDAeE4G9jje6YtYIADPfDHGr9CBY\\n\",\n       \"bQTS5G1yEzyDRGN/jzE1mPxYLIRCAH+FEfgrjOAxkMF2lQ32OjY4mlng6mKGLRWEUHGgp9mAmWMD\\n\",\n       \"Zr4V2EU2oAjGZHE2WrCgGKZoPLgxpgaTLyDBUAQY8Jcbgb+8zyzntZHAo6cAZiAD7iYByvICyvEC\\n\",\n       \"mesFlIsBMjWjZThMulfEZ7t1Ris1pmJNXybeivUrAicIOHulmV2YlRI2XyHG1CUmIDFihGFq6icx\\n\",\n       \"YkwRYgISI0YYYgISI0YY/j+SFgT3yDrlYgAAAABJRU5ErkJggg==\\n\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.Image object>\"\n      ]\n     },\n     \"execution_count\": 2,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"Image(\\\"python.png\\\")\"\n   ]\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "tests/files/Interrupt.ipynb",
    "content": "{\n    \"cells\": [\n     {\n      \"cell_type\": \"code\",\n      \"execution_count\": 1,\n      \"metadata\": {\n       \"collapsed\": false\n      },\n      \"outputs\": [\n       {\n        \"ename\": \"KeyboardInterrupt\",\n        \"evalue\": \"\",\n        \"output_type\": \"error\",\n        \"traceback\": [\n         \"\\u001b[0;31m---------------------------------------------------------------------------\\u001b[0m\",\n         \"\\u001b[0;31mKeyboardInterrupt\\u001b[0m                         Traceback (most recent call last)\",\n         \"\\u001b[0;32m<ipython-input-1-31d18a52bf41>\\u001b[0m in \\u001b[0;36m<module>\\u001b[0;34m\\u001b[0m\\n\\u001b[0;32m----> 1\\u001b[0;31m \\u001b[0;32mwhile\\u001b[0m \\u001b[0;32mTrue\\u001b[0m\\u001b[0;34m:\\u001b[0m \\u001b[0;32mcontinue\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0m\\n\\u001b[0m\",\n         \"\\u001b[0;31mKeyboardInterrupt\\u001b[0m: \"\n        ]\n       }\n      ],\n      \"source\": [\n       \"while True: continue\"\n      ]\n     },\n     {\n      \"cell_type\": \"code\",\n      \"execution_count\": 2,\n      \"metadata\": {\n       \"collapsed\": false\n      },\n      \"outputs\": [\n       {\n        \"name\": \"stdout\",\n        \"output_type\": \"stream\",\n        \"text\": [\n         \"done\\n\"\n        ]\n       }\n      ],\n      \"source\": [\n       \"print(\\\"done\\\")\"\n      ]\n     }\n    ],\n    \"metadata\": {},\n    \"nbformat\": 4,\n    \"nbformat_minor\": 0\n   }\n"
  },
  {
    "path": "tests/files/JupyterWidgets.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"f46f26da84b54255bccc3a69d7eb08de\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Label(value='Hello World')\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import ipywidgets\\n\",\n    \"\\n\",\n    \"label = ipywidgets.Label(\\\"Hello World\\\")\\n\",\n    \"display(label)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# it should also handle custom msg'es\\n\",\n    \"label.send({\\\"msg\\\": \\\"Hello\\\"})\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.6.4\"\n  },\n  \"widgets\": {\n   \"application/vnd.jupyter.widget-state+json\": {\n    \"state\": {\n     \"8273e8fe9d9941a4a63c062158e0a630\": {\n      \"model_module\": \"@jupyter-widgets/controls\",\n      \"model_module_version\": \"1.4.0\",\n      \"model_name\": \"DescriptionStyleModel\",\n      \"state\": {\n       \"description_width\": \"\"\n      }\n     },\n     \"a72770a4f541425f8fe85833a3dc2a8e\": {\n      \"model_module\": \"@jupyter-widgets/controls\",\n      \"model_module_version\": \"1.4.0\",\n      \"model_name\": \"LabelModel\",\n      \"state\": {\n       \"context_menu\": null,\n       \"layout\": \"IPY_MODEL_dec20f599109458ca607b1df5959469b\",\n       \"style\": \"IPY_MODEL_8273e8fe9d9941a4a63c062158e0a630\",\n       \"value\": \"Hello World\"\n      }\n     },\n     \"dec20f599109458ca607b1df5959469b\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.1.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     }\n    },\n    \"version_major\": 2,\n    \"version_minor\": 0\n   }\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "tests/files/Other Comms.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"ExecuteTime\": {\n     \"end_time\": \"2020-05-29T11:16:26.365338Z\",\n     \"start_time\": \"2020-05-29T11:16:26.362047Z\"\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from comm import create_comm\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"ExecuteTime\": {\n     \"end_time\": \"2020-05-29T11:16:26.377700Z\",\n     \"start_time\": \"2020-05-29T11:16:26.371603Z\"\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"comm = create_comm(\\\"this-comm-tests-a-missing-handler\\\", data={\\\"id\\\": \\\"foo\\\"})\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {\n    \"ExecuteTime\": {\n     \"end_time\": \"2020-05-29T11:16:26.584520Z\",\n     \"start_time\": \"2020-05-29T11:16:26.581213Z\"\n    }\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"comm.send(data={\\\"id\\\": \\\"bar\\\"})\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.3\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "tests/files/Output.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"e152547dd69d46fcbcb602cf9f92e50b\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Output()\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import ipywidgets as widgets\\n\",\n    \"from IPython.display import clear_output\\n\",\n    \"\\n\",\n    \"output1 = widgets.Output()\\n\",\n    \"output1\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"hi\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"hi\\\")\\n\",\n    \"with output1:\\n\",\n    \"    print(\\\"in output\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"with output1:\\n\",\n    \"    raise ValueError(\\\"trigger msg_type=error\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"44dc393cd7c6461a8c4901f85becfc0e\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Output()\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import ipywidgets as widgets\\n\",\n    \"\\n\",\n    \"output2 = widgets.Output()\\n\",\n    \"output2\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"hi2\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"hi2\\\")\\n\",\n    \"with output2:\\n\",\n    \"    print(\\\"in output2\\\")\\n\",\n    \"    clear_output(wait=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"d6cd7a1de3494d2daff23c6d4ffe42ee\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Output()\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import ipywidgets as widgets\\n\",\n    \"\\n\",\n    \"output3 = widgets.Output()\\n\",\n    \"output3\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 7,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"hi3\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"hi3\\\")\\n\",\n    \"with output3:\\n\",\n    \"    print(\\\"hello\\\")\\n\",\n    \"    clear_output(wait=True)\\n\",\n    \"    print(\\\"world\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 8,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"10517a9d5b1d4ea386945642894dd898\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Output()\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import ipywidgets as widgets\\n\",\n    \"\\n\",\n    \"output4 = widgets.Output()\\n\",\n    \"output4\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 9,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"hi4\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"hi4\\\")\\n\",\n    \"with output4:\\n\",\n    \"    print(\\\"hello world\\\")\\n\",\n    \"    clear_output()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 10,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"37f7ba6a9ecc4c19b519e718cd12aafe\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Output()\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import ipywidgets as widgets\\n\",\n    \"\\n\",\n    \"output5 = widgets.Output()\\n\",\n    \"output5\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 11,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(\\\"hi5\\\")\\n\",\n    \"with output5:\\n\",\n    \"    display(\\\"hello world\\\")  # this is not a stream but plain text\\n\",\n    \"clear_output()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 12,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"4fb0ee7e557440109c08547514f03c7b\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Output()\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"import ipywidgets as widgets\\n\",\n    \"\\n\",\n    \"output_outer = widgets.Output()\\n\",\n    \"output_inner = widgets.Output()\\n\",\n    \"output_inner\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 13,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"application/vnd.jupyter.widget-view+json\": {\n       \"model_id\": \"01ea355e26484c13b1caaaf6d29ac0f2\",\n       \"version_major\": 2,\n       \"version_minor\": 0\n      },\n      \"text/plain\": [\n       \"Output()\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"output_outer\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 14,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"with output_inner:\\n\",\n    \"    print(\\\"in inner\\\")\\n\",\n    \"    with output_outer:\\n\",\n    \"        print(\\\"in outer\\\")\\n\",\n    \"    print(\\\"also in inner\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"language\": \"python\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.7.3\"\n  },\n  \"widgets\": {\n   \"application/vnd.jupyter.widget-state+json\": {\n    \"state\": {\n     \"01ea355e26484c13b1caaaf6d29ac0f2\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_7213e178683c4d0682b3c848a2452cf1\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in outer\\n\"\n        }\n       ]\n      }\n     },\n     \"025929abe8a143a08ad23de9e99c610f\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"03c04d8645a74c4dac2e08e2142122a6\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"091f6e59c48442b1bdb13320b4f6605d\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"10517a9d5b1d4ea386945642894dd898\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_2c67de94f62d4887866d22abca7f6f13\"\n      }\n     },\n     \"106de0ded502439c873de5449248b00c\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"1b9529b98aaf40ccbbf38e178796be88\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"22592f3cb7674cb79cc60def5e8bc060\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"2468aac6020349139ee6236b5dde0310\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_d5e88b6a26114d6da0b7af215aa2c3bb\"\n      }\n     },\n     \"2955dc9c531c4c6b80086da240d0df13\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_1b9529b98aaf40ccbbf38e178796be88\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"world\\n\"\n        }\n       ]\n      }\n     },\n     \"2c67de94f62d4887866d22abca7f6f13\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"37f7ba6a9ecc4c19b519e718cd12aafe\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_03c04d8645a74c4dac2e08e2142122a6\",\n       \"outputs\": [\n        {\n         \"data\": {\n          \"text/plain\": \"'hello world'\"\n         },\n         \"metadata\": {},\n         \"output_type\": \"display_data\"\n        }\n       ]\n      }\n     },\n     \"3945ce528fbf40dc830767281892ea56\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"3c6bb7a6fd4f4f8786d30ef7b2c7c050\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"3e0e8f5d18fe4992b11e1d5c13faecdf\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"44dc393cd7c6461a8c4901f85becfc0e\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_3c6bb7a6fd4f4f8786d30ef7b2c7c050\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in output2\\n\"\n        }\n       ]\n      }\n     },\n     \"45823daa739447a6ba5393e45204ec8e\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_3e0e8f5d18fe4992b11e1d5c13faecdf\",\n       \"outputs\": [\n        {\n         \"data\": {\n          \"text/plain\": \"'hello world'\"\n         },\n         \"metadata\": {},\n         \"output_type\": \"display_data\"\n        }\n       ]\n      }\n     },\n     \"4fa2d1a41bd64017a20e358526ad9cf3\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_6490daaa1d2e42a0aef909e7b8c8eff4\",\n       \"outputs\": [\n        {\n         \"data\": {\n          \"text/plain\": \"'hello world'\"\n         },\n         \"metadata\": {},\n         \"output_type\": \"display_data\"\n        }\n       ]\n      }\n     },\n     \"4fb0ee7e557440109c08547514f03c7b\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_dbf140d66ba247b7847c0f5642b7f607\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in inner\\nalso in inner\\n\"\n        }\n       ]\n      }\n     },\n     \"55aff5c4b53f440a868919f042cf9c14\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_a14653416772496aabed04b4719268ef\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in inner\\nalso in inner\\n\"\n        }\n       ]\n      }\n     },\n     \"5747ce87279c44519b9df62799e25e6f\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_6ef78dc31eec422ab2afce4be129836f\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in output2\\n\"\n        }\n       ]\n      }\n     },\n     \"6490daaa1d2e42a0aef909e7b8c8eff4\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"6ef78dc31eec422ab2afce4be129836f\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"7134e81fdb364a738c1e58b26ec0d008\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_025929abe8a143a08ad23de9e99c610f\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in inner\\nalso in inner\\n\"\n        }\n       ]\n      }\n     },\n     \"7213e178683c4d0682b3c848a2452cf1\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"804b6628ca0a48dfbad930615626b1fb\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"a14653416772496aabed04b4719268ef\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"a32671b19b814cf5bd964c36368f9f79\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_c843c22ff72e4983984ca4d62ce68e2b\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in outer\\n\"\n        }\n       ]\n      }\n     },\n     \"aaf673ac9c774aaba4f751db2f3dd6c5\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_106de0ded502439c873de5449248b00c\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in output2\\n\"\n        }\n       ]\n      }\n     },\n     \"bc3d9af2591e4a52af73921f46d79efa\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_22592f3cb7674cb79cc60def5e8bc060\"\n      }\n     },\n     \"c843c22ff72e4983984ca4d62ce68e2b\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"cc022dc8b5584570a04facf68f9bdf0b\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_3945ce528fbf40dc830767281892ea56\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in outer\\n\"\n        }\n       ]\n      }\n     },\n     \"d0cb56db68f2485480da1b2a43ad3c02\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_df4468e2240a430599a01e731472c319\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in output\\n\"\n        },\n        {\n         \"ename\": \"ValueError\",\n         \"evalue\": \"trigger msg_type=error\",\n         \"output_type\": \"error\",\n         \"traceback\": [\n          \"\\u001b[0;31m---------------------------------------------------------------------------\\u001b[0m\",\n          \"\\u001b[0;31mValueError\\u001b[0m                                Traceback (most recent call last)\",\n          \"\\u001b[0;32m<ipython-input-15-7f84f5558b68>\\u001b[0m in \\u001b[0;36m<module>\\u001b[0;34m\\u001b[0m\\n\\u001b[1;32m      1\\u001b[0m \\u001b[0;32mwith\\u001b[0m \\u001b[0moutput1\\u001b[0m\\u001b[0;34m:\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m----> 2\\u001b[0;31m     \\u001b[0;32mraise\\u001b[0m \\u001b[0mValueError\\u001b[0m\\u001b[0;34m(\\u001b[0m\\u001b[0;34m\\\"trigger msg_type=error\\\"\\u001b[0m\\u001b[0;34m)\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0m\\n\\u001b[0m\",\n          \"\\u001b[0;31mValueError\\u001b[0m: trigger msg_type=error\"\n         ]\n        }\n       ]\n      }\n     },\n     \"d314a6ef74d947f3a2149bdf9b8b57a3\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_804b6628ca0a48dfbad930615626b1fb\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in output\\n\"\n        }\n       ]\n      }\n     },\n     \"d5e88b6a26114d6da0b7af215aa2c3bb\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"d6cd7a1de3494d2daff23c6d4ffe42ee\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_091f6e59c48442b1bdb13320b4f6605d\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"world\\n\"\n        }\n       ]\n      }\n     },\n     \"dbf140d66ba247b7847c0f5642b7f607\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"de7ba4c0eed941a3b52fa940387d1415\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"df4468e2240a430599a01e731472c319\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"e152547dd69d46fcbcb602cf9f92e50b\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_de7ba4c0eed941a3b52fa940387d1415\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"in output\\n\"\n        },\n        {\n         \"ename\": \"ValueError\",\n         \"evalue\": \"trigger msg_type=error\",\n         \"output_type\": \"error\",\n         \"traceback\": [\n          \"\\u001b[0;31m---------------------------------------------------------------------------\\u001b[0m\",\n          \"\\u001b[0;31mValueError\\u001b[0m                                Traceback (most recent call last)\",\n          \"\\u001b[0;32m<ipython-input-3-7f84f5558b68>\\u001b[0m in \\u001b[0;36m<module>\\u001b[0;34m\\u001b[0m\\n\\u001b[1;32m      1\\u001b[0m \\u001b[0;32mwith\\u001b[0m \\u001b[0moutput1\\u001b[0m\\u001b[0;34m:\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0m\\n\\u001b[0;32m----> 2\\u001b[0;31m     \\u001b[0;32mraise\\u001b[0m \\u001b[0mValueError\\u001b[0m\\u001b[0;34m(\\u001b[0m\\u001b[0;34m\\\"trigger msg_type=error\\\"\\u001b[0m\\u001b[0;34m)\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0;34m\\u001b[0m\\u001b[0m\\n\\u001b[0m\",\n          \"\\u001b[0;31mValueError\\u001b[0m: trigger msg_type=error\"\n         ]\n        }\n       ]\n      }\n     },\n     \"e27795e5a4f14450b8c9590cac51cb6b\": {\n      \"model_module\": \"@jupyter-widgets/base\",\n      \"model_module_version\": \"1.2.0\",\n      \"model_name\": \"LayoutModel\",\n      \"state\": {}\n     },\n     \"e3e20af587534a9bb3fa413951ceb28d\": {\n      \"model_module\": \"@jupyter-widgets/output\",\n      \"model_module_version\": \"1.0.0\",\n      \"model_name\": \"OutputModel\",\n      \"state\": {\n       \"layout\": \"IPY_MODEL_e27795e5a4f14450b8c9590cac51cb6b\",\n       \"outputs\": [\n        {\n         \"name\": \"stdout\",\n         \"output_type\": \"stream\",\n         \"text\": \"world\\n\"\n        }\n       ]\n      }\n     }\n    },\n    \"version_major\": 2,\n    \"version_minor\": 0\n   }\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "tests/files/Parallel Execute A.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Ensure notebooks can execute in parallel\\n\",\n    \"\\n\",\n    \"This notebook uses a file system based \\\"lock\\\" to assert that two instances of the notebook kernel will run in parallel. Each instance writes to a file in a temporary directory, and then tries to read the other file from\\n\",\n    \"the temporary directory, so that running them in sequence will fail, but running them in parallel will succeed.\\n\",\n    \"\\n\",\n    \"Two notebooks are launched, each which sets the `this_notebook` variable. One notebook is set to `this_notebook = 'A'` and the other `this_notebook = 'B'`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import os\\n\",\n    \"import os.path\\n\",\n    \"import time\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# the variable this_notebook is injectected in a cell above by the test framework.\\n\",\n    \"this_notebook = \\\"A\\\"\\n\",\n    \"other_notebook = \\\"B\\\"\\n\",\n    \"directory = os.environ[\\\"NBEXECUTE_TEST_PARALLEL_TMPDIR\\\"]\\n\",\n    \"with open(os.path.join(directory, f\\\"test_file_{this_notebook}.txt\\\"), \\\"w\\\") as f:\\n\",\n    \"    f.write(f\\\"Hello from {this_notebook}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"start = time.time()\\n\",\n    \"timeout = 5\\n\",\n    \"end = start + timeout\\n\",\n    \"target_file = os.path.join(directory, f\\\"test_file_{other_notebook}.txt\\\")\\n\",\n    \"while time.time() < end:\\n\",\n    \"    time.sleep(0.1)\\n\",\n    \"    if os.path.exists(target_file):\\n\",\n    \"        with open(target_file) as f:\\n\",\n    \"            text = f.read()\\n\",\n    \"        if text == f\\\"Hello from {other_notebook}\\\":\\n\",\n    \"            break\\n\",\n    \"else:\\n\",\n    \"    assert False, f\\\"Timed out – didn't get a message from {other_notebook}\\\"\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.6.7\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "tests/files/Parallel Execute B.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"markdown\",\n   \"metadata\": {},\n   \"source\": [\n    \"# Ensure notebooks can execute in parallel\\n\",\n    \"\\n\",\n    \"This notebook uses a file system based \\\"lock\\\" to assert that two instances of the notebook kernel will run in parallel. Each instance writes to a file in a temporary directory, and then tries to read the other file from\\n\",\n    \"the temporary directory, so that running them in sequence will fail, but running them in parallel will succeed.\\n\",\n    \"\\n\",\n    \"Two notebooks are launched, each which sets the `this_notebook` variable. One notebook is set to `this_notebook = 'A'` and the other `this_notebook = 'B'`.\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import os\\n\",\n    \"import os.path\\n\",\n    \"import time\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"# the variable this_notebook is injectected in a cell above by the test framework.\\n\",\n    \"this_notebook = \\\"B\\\"\\n\",\n    \"other_notebook = \\\"A\\\"\\n\",\n    \"directory = os.environ[\\\"NBEXECUTE_TEST_PARALLEL_TMPDIR\\\"]\\n\",\n    \"with open(os.path.join(directory, f\\\"test_file_{this_notebook}.txt\\\"), \\\"w\\\") as f:\\n\",\n    \"    f.write(f\\\"Hello from {this_notebook}\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"start = time.time()\\n\",\n    \"timeout = 5\\n\",\n    \"end = start + timeout\\n\",\n    \"target_file = os.path.join(directory, f\\\"test_file_{other_notebook}.txt\\\")\\n\",\n    \"while time.time() < end:\\n\",\n    \"    time.sleep(0.1)\\n\",\n    \"    if os.path.exists(target_file):\\n\",\n    \"        with open(target_file) as f:\\n\",\n    \"            text = f.read()\\n\",\n    \"        if text == f\\\"Hello from {other_notebook}\\\":\\n\",\n    \"            break\\n\",\n    \"else:\\n\",\n    \"    assert False, f\\\"Timed out – didn't get a message from {other_notebook}\\\"\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.6.7\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "tests/files/SVG.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"from IPython.display import SVG\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"data\": {\n      \"image/svg+xml\": [\n       \"<svg height=\\\"100\\\" width=\\\"100\\\">\\n\",\n       \"    <circle cx=\\\"50\\\" cy=\\\"50\\\" fill=\\\"red\\\" r=\\\"40\\\" stroke=\\\"black\\\" stroke-width=\\\"2\\\"/>\\n\",\n       \"</svg>\"\n      ],\n      \"text/plain\": [\n       \"<IPython.core.display.SVG object>\"\n      ]\n     },\n     \"execution_count\": 2,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"SVG(\\n\",\n    \"    data=\\\"\\\"\\\"\\n\",\n    \"<svg height=\\\"100\\\" width=\\\"100\\\">\\n\",\n    \"    <circle cx=\\\"50\\\" cy=\\\"50\\\" r=\\\"40\\\" stroke=\\\"black\\\" stroke-width=\\\"2\\\" fill=\\\"red\\\" />\\n\",\n    \"</svg>\\\"\\\"\\\"\\n\",\n    \")\"\n   ]\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "tests/files/Skip Exceptions with Cell Tags.ipynb",
    "content": "{\n    \"cells\": [\n     {\n      \"cell_type\": \"code\",\n      \"execution_count\": 1,\n      \"metadata\": {\n       \"tags\": [\n        \"raises-exception\"\n       ]\n      },\n      \"outputs\": [\n       {\n        \"name\": \"stdout\",\n        \"output_type\": \"stream\",\n        \"text\": [\n         \"hello\\n\"\n        ]\n       },\n       {\n        \"name\": \"stderr\",\n        \"output_type\": \"stream\",\n        \"text\": [\n         \"errorred\\n\"\n        ]\n       },\n       {\n        \"ename\": \"Exception\",\n        \"evalue\": \"message\",\n        \"output_type\": \"error\",\n        \"traceback\": [\n         \"\\u001b[0;31m---------------------------------------------------------------------------\\u001b[0m\",\n         \"\\u001b[0;31mException\\u001b[0m                                 Traceback (most recent call last)\",\n         \"Cell \\u001b[0;32mIn[1], line 5\\u001b[0m\\n\\u001b[1;32m      3\\u001b[0m \\u001b[38;5;28mprint\\u001b[39m(\\u001b[38;5;124m\\\"\\u001b[39m\\u001b[38;5;124merrorred\\u001b[39m\\u001b[38;5;124m\\\"\\u001b[39m, file\\u001b[38;5;241m=\\u001b[39msys\\u001b[38;5;241m.\\u001b[39mstderr)\\n\\u001b[1;32m      4\\u001b[0m \\u001b[38;5;66;03m# üñîçø∂é\\u001b[39;00m\\n\\u001b[0;32m----> 5\\u001b[0m \\u001b[38;5;28;01mraise\\u001b[39;00m \\u001b[38;5;167;01mException\\u001b[39;00m(\\u001b[38;5;124m\\\"\\u001b[39m\\u001b[38;5;124mmessage\\u001b[39m\\u001b[38;5;124m\\\"\\u001b[39m)\\n\",\n         \"\\u001b[0;31mException\\u001b[0m: message\"\n        ]\n       }\n      ],\n      \"source\": [\n       \"import sys\\n\",\n       \"print(\\\"hello\\\")\\n\",\n       \"print(\\\"errorred\\\", file=sys.stderr)\\n\",\n       \"# üñîçø∂é\\n\",\n       \"raise Exception(\\\"message\\\")\"\n      ]\n     },\n     {\n      \"cell_type\": \"code\",\n      \"execution_count\": 2,\n      \"metadata\": {},\n      \"outputs\": [\n       {\n        \"name\": \"stdout\",\n        \"output_type\": \"stream\",\n        \"text\": [\n         \"ok\\n\"\n        ]\n       }\n      ],\n      \"source\": [\n       \"print('ok')\"\n      ]\n     }\n    ],\n    \"metadata\": {\n     \"kernelspec\": {\n      \"display_name\": \"Python 3 (ipykernel)\",\n      \"language\": \"python\",\n      \"name\": \"python3\"\n     },\n     \"language_info\": {\n      \"codemirror_mode\": {\n       \"name\": \"ipython\",\n       \"version\": 3\n      },\n      \"file_extension\": \".py\",\n      \"mimetype\": \"text/x-python\",\n      \"name\": \"python\",\n      \"nbconvert_exporter\": \"python\",\n      \"pygments_lexer\": \"ipython3\",\n      \"version\": \"3.10.9\"\n     },\n     \"widgets\": {\n      \"application/vnd.jupyter.widget-state+json\": {\n       \"state\": {},\n       \"version_major\": 2,\n       \"version_minor\": 0\n      }\n     }\n    },\n    \"nbformat\": 4,\n    \"nbformat_minor\": 4\n   }\n"
  },
  {
    "path": "tests/files/Skip Exceptions.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"ename\": \"Exception\",\n     \"evalue\": \"message\",\n     \"output_type\": \"error\",\n     \"traceback\": [\n      \"\\u001b[1;31m---------------------------------------------------------------------------\\u001b[0m\",\n      \"\\u001b[1;31mException\\u001b[0m                                 Traceback (most recent call last)\",\n      \"\\u001b[1;32m<ipython-input-1-9abc744909d9>\\u001b[0m in \\u001b[0;36m<module>\\u001b[1;34m\\u001b[0m\\n\\u001b[0;32m      1\\u001b[0m \\u001b[1;31m# üñîçø∂é\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[1;32m----> 2\\u001b[1;33m \\u001b[1;32mraise\\u001b[0m \\u001b[0mException\\u001b[0m\\u001b[1;33m(\\u001b[0m\\u001b[1;34m\\\"message\\\"\\u001b[0m\\u001b[1;33m)\\u001b[0m\\u001b[1;33m\\u001b[0m\\u001b[0m\\n\\u001b[0m\",\n      \"\\u001b[1;31mException\\u001b[0m: message\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"# üñîçø∂é\\n\",\n    \"raise Exception(\\\"message\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"ok\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"ok\\\")\"\n   ]\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "tests/files/Skip Execution with Cell Tag.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {\n    \"tags\": [\n     \"skip-execution\"\n    ]\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"print(\\\"a long running cell\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"ok\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"ok\\\")\"\n   ]\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 1\n}\n"
  },
  {
    "path": "tests/files/Sleep1s.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"import datetime\\n\",\n    \"import time\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"t0 = datetime.datetime.utcnow()\\n\",\n    \"time.sleep(1)\\n\",\n    \"t1 = datetime.datetime.utcnow()\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"time_format = \\\"%Y-%m-%dT%H:%M:%S.%fZ\\\"\\n\",\n    \"print(t0.strftime(time_format), end=\\\"\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": null,\n   \"metadata\": {},\n   \"outputs\": [],\n   \"source\": [\n    \"print(t1.strftime(time_format), end=\\\"\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  },\n  \"language_info\": {\n   \"codemirror_mode\": {\n    \"name\": \"ipython\",\n    \"version\": 3\n   },\n   \"file_extension\": \".py\",\n   \"mimetype\": \"text/x-python\",\n   \"name\": \"python\",\n   \"nbconvert_exporter\": \"python\",\n   \"pygments_lexer\": \"ipython3\",\n   \"version\": \"3.8.1\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "tests/files/Unicode.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"☃\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"☃\\\")\"\n   ]\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "tests/files/UnicodePy3.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": false\n   },\n   \"outputs\": [\n    {\n     \"name\": \"stdout\",\n     \"output_type\": \"stream\",\n     \"text\": [\n      \"☃\\n\"\n     ]\n    }\n   ],\n   \"source\": [\n    \"print(\\\"☃\\\")\"\n   ]\n  }\n ],\n \"metadata\": {\n  \"kernelspec\": {\n   \"display_name\": \"Python 3\",\n   \"language\": \"python\",\n   \"name\": \"python3\"\n  }\n },\n \"nbformat\": 4,\n \"nbformat_minor\": 0\n}\n"
  },
  {
    "path": "tests/files/update-display-id.ipynb",
    "content": "{\n \"cells\": [\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 1,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"ip = get_ipython()\\n\",\n    \"\\n\",\n    \"from IPython.display import display\\n\",\n    \"import IPython\\n\",\n    \"do_minus_one = IPython.version_info >= (9, 8)\\n\",\n    \"\\n\",\n    \"\\n\",\n    \"def display_with_id(obj, display_id, update=False, execute_result=False):\\n\",\n    \"    iopub = ip.kernel.iopub_socket\\n\",\n    \"    session = get_ipython().kernel.session\\n\",\n    \"    data, md = ip.display_formatter.format(obj)\\n\",\n    \"    transient = {\\\"display_id\\\": str(display_id)}\\n\",\n    \"    content = {\\\"data\\\": data, \\\"metadata\\\": md, \\\"transient\\\": transient}\\n\",\n    \"    if execute_result:\\n\",\n    \"        msg_type = \\\"execute_result\\\"\\n\",\n    \"        content[\\\"execution_count\\\"] = ip.execution_count - (1 if do_minus_one else 0)\\n\",\n    \"    else:\\n\",\n    \"        msg_type = \\\"update_display_data\\\" if update else \\\"display_data\\\"\\n\",\n    \"    session.send(iopub, msg_type, content, parent=ip.parent_header)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 2,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"'above'\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"8\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"'below'\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"display(\\\"above\\\")\\n\",\n    \"display_with_id(1, \\\"here\\\")\\n\",\n    \"display(\\\"below\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 3,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"8\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"6\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"8\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"display_with_id(2, \\\"here\\\")\\n\",\n    \"display_with_id(3, \\\"there\\\")\\n\",\n    \"display_with_id(4, \\\"here\\\")\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 4,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"6\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    }\n   ],\n   \"source\": [\n    \"display_with_id(5, \\\"there\\\")\\n\",\n    \"display_with_id(6, \\\"there\\\", update=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 5,\n   \"metadata\": {},\n   \"outputs\": [\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"8\"\n      ]\n     },\n     \"metadata\": {},\n     \"output_type\": \"display_data\"\n    },\n    {\n     \"data\": {\n      \"text/plain\": [\n       \"10\"\n      ]\n     },\n     \"execution_count\": 5,\n     \"metadata\": {},\n     \"output_type\": \"execute_result\"\n    }\n   ],\n   \"source\": [\n    \"display_with_id(7, \\\"here\\\")\\n\",\n    \"display_with_id(8, \\\"here\\\", update=True)\\n\",\n    \"display_with_id(9, \\\"result\\\", execute_result=True)\"\n   ]\n  },\n  {\n   \"cell_type\": \"code\",\n   \"execution_count\": 6,\n   \"metadata\": {\n    \"collapsed\": true\n   },\n   \"outputs\": [],\n   \"source\": [\n    \"display_with_id(10, \\\"result\\\", update=True)\"\n   ]\n  }\n ],\n \"metadata\": {},\n \"nbformat\": 4,\n \"nbformat_minor\": 2\n}\n"
  },
  {
    "path": "tests/test_cli.py",
    "content": "import sys\nfrom pathlib import Path\nfrom subprocess import CalledProcessError, check_output\nfrom unittest.mock import call, mock_open, patch\n\nimport pytest\n\nfrom nbclient.cli import NbClientApp\n\nif sys.version_info >= (3, 13):\n    PATH_OPEN_CALL_STEP = 4\nelse:\n    PATH_OPEN_CALL_STEP = 3\n\ncurrent_dir = Path(__file__).parent.absolute()\n\n\n@pytest.fixture()\ndef jupyterapp():\n    with patch(\"nbclient.cli.JupyterApp.initialize\") as mocked:\n        yield mocked\n\n\n@pytest.fixture()\ndef client():\n    with patch(\"nbclient.cli.NotebookClient\", autospec=True) as mocked:\n        yield mocked\n\n\n@pytest.fixture()\ndef writer():\n    with patch(\"nbformat.write\", autospec=True) as mocked:\n        yield mocked\n\n\n@pytest.fixture()\ndef reader():\n    with patch(\"nbformat.read\", autospec=True, return_value=\"nb\") as mocked:\n        yield mocked\n\n\n@pytest.fixture()\ndef path_open():\n    opener = mock_open()\n\n    def mocked_open(self, *args, **kwargs):\n        return opener(self, *args, **kwargs)\n\n    with patch(\"nbclient.cli.Path.open\", mocked_open):\n        yield opener\n\n\n@pytest.mark.parametrize(\n    \"input_names\", [(\"Other Comms\",), (\"Other Comms.ipynb\",), (\"Other Comms\", \"HelloWorld.ipynb\")]\n)\n@pytest.mark.parametrize(\"relative\", [False, True])\n@pytest.mark.parametrize(\"inplace\", [False, True])\ndef test_mult(input_names, relative, inplace, jupyterapp, client, reader, writer, path_open):\n    paths = [current_dir / \"files\" / name for name in input_names]\n    if relative:\n        paths = [p.relative_to(Path.cwd()) for p in paths]\n\n    c = NbClientApp(notebooks=[str(p) for p in paths], kernel_name=\"python3\", inplace=inplace)\n    c.initialize()\n\n    # add suffix if needed\n    paths = [p.with_suffix(\".ipynb\") for p in paths]\n\n    assert path_open.mock_calls[::PATH_OPEN_CALL_STEP] == [call(p) for p in paths]\n    assert reader.call_count == len(paths)\n    # assert reader.mock_calls == [call(p, as_version=4) for p in paths]\n\n    expected = []\n    for p in paths:\n        expected.extend(\n            [\n                call(\n                    \"nb\",\n                    timeout=c.timeout,\n                    startup_timeout=c.startup_timeout,\n                    skip_cells_with_tag=c.skip_cells_with_tag,\n                    allow_errors=c.allow_errors,\n                    kernel_name=c.kernel_name,\n                    resources={\"metadata\": {\"path\": p.parent.absolute()}},\n                ),\n                call().execute(),\n            ]\n        )\n\n    assert client.mock_calls == expected\n\n    if inplace:\n        assert writer.mock_calls == [call(\"nb\", p) for p in paths]\n    else:\n        writer.assert_not_called()\n\n\n@pytest.mark.parametrize(\n    \"input_names\", [(\"Other Comms\",), (\"Other Comms.ipynb\",), (\"Other Comms\", \"HelloWorld.ipynb\")]\n)\n@pytest.mark.parametrize(\"relative\", [False, True])\n@pytest.mark.parametrize(\"output_base\", [\"thing\", \"thing.ipynb\", \"{notebook_name}-new.ipynb\"])\ndef test_output(input_names, relative, output_base, jupyterapp, client, reader, writer, path_open):\n    paths = [current_dir / \"files\" / name for name in input_names]\n    if relative:\n        paths = [p.relative_to(Path.cwd()) for p in paths]\n\n    c = NbClientApp(\n        notebooks=[str(p) for p in paths], kernel_name=\"python3\", output_base=output_base\n    )\n\n    if len(paths) != 1 and \"{notebook_name}\" not in output_base:\n        with pytest.raises(ValueError) as e:\n            c.initialize()\n        assert \"If passing multiple\" in str(e.value)\n        return\n\n    c.initialize()\n\n    # add suffix if needed\n    paths = [p.with_suffix(\".ipynb\") for p in paths]\n\n    assert path_open.mock_calls[::PATH_OPEN_CALL_STEP] == [call(p) for p in paths]\n    assert reader.call_count == len(paths)\n\n    expected = []\n    for p in paths:\n        expected.extend(\n            [\n                call(\n                    \"nb\",\n                    timeout=c.timeout,\n                    startup_timeout=c.startup_timeout,\n                    skip_cells_with_tag=c.skip_cells_with_tag,\n                    allow_errors=c.allow_errors,\n                    kernel_name=c.kernel_name,\n                    resources={\"metadata\": {\"path\": p.parent.absolute()}},\n                ),\n                call().execute(),\n            ]\n        )\n\n    assert client.mock_calls == expected\n\n    assert writer.mock_calls == [\n        call(\n            \"nb\",\n            (p.parent / output_base.format(notebook_name=p.with_suffix(\"\").name)).with_suffix(\n                \".ipynb\"\n            ),\n        )\n        for p in paths\n    ]\n\n\ndef test_bad_output_dir(jupyterapp, client, reader, writer, path_open):\n    input_names = [\"Other Comms\"]\n    output_base = \"thing/thing\"\n\n    paths = [current_dir / \"files\" / name for name in input_names]\n\n    c = NbClientApp(\n        notebooks=[str(p) for p in paths], kernel_name=\"python3\", output_base=output_base\n    )\n\n    with pytest.raises(ValueError) as e:\n        c.initialize()\n\n    assert \"Cannot write to directory\" in str(e.value)\n\n\n# simple runner from command line\ndef test_cli_simple():\n    path = current_dir / \"files\" / \"Other Comms\"\n\n    with pytest.raises(CalledProcessError):\n        check_output([\"jupyter-execute\", \"--output\", \"thing/thing\", str(path)])  # noqa: S603, S607\n\n\ndef test_no_notebooks(jupyterapp):\n    c = NbClientApp(notebooks=[], kernel_name=\"python3\")\n\n    with pytest.raises(SystemExit):\n        c.initialize()\n"
  },
  {
    "path": "tests/test_client.py",
    "content": "from __future__ import annotations\n\nimport asyncio\nimport concurrent.futures\nimport copy\nimport datetime\nimport functools\nimport os\nimport re\nimport sys\nimport threading\nimport warnings\nfrom base64 import b64decode, b64encode\nfrom platform import python_implementation\nfrom queue import Empty\nfrom typing import Any\nfrom unittest.mock import MagicMock, Mock\n\nimport nbformat\nimport pytest\nimport xmltodict\nfrom flaky import flaky  # type:ignore[import-untyped]\nfrom jupyter_client._version import version_info\nfrom jupyter_client.client import KernelClient\nfrom jupyter_client.kernelspec import KernelSpecManager\nfrom jupyter_client.manager import KernelManager\nfrom nbconvert.filters import strip_ansi\nfrom nbformat import NotebookNode\nfrom testpath import modified_env\nfrom traitlets import TraitError\n\nfrom nbclient import NotebookClient, execute\nfrom nbclient.exceptions import CellExecutionError\n\nfrom .base import NBClientTestsBase\n\n# mypy: disable-error-code=\"no-untyped-call,no-untyped-def\"\n\naddr_pat = re.compile(r\"0x[0-9a-f]{7,9}\")\ncurrent_dir = os.path.dirname(__file__)\nipython_input_pat = re.compile(\n    r\"(<ipython-input-\\d+-[0-9a-f]+>|<IPY-INPUT>) in (<module>|<cell line: \\d>\\(\\))\"\n)\n# Tracebacks look different in IPython 8,\n# see: https://github.com/ipython/ipython/blob/master/docs/source/whatsnew/version8.rst#traceback-improvements  # noqa\nipython8_input_pat = re.compile(\n    r\"((Cell|Input) In\\s?\\[\\d+\\]|<IPY-INPUT>), (in )?(line \\d|<module>|<cell line: \\d>\\(\\))\"\n)\n\n\n# Avoid warnings from pydev.\nos.environ[\"PYDEVD_DISABLE_FILE_VALIDATION\"] = \"1\"\n\nhook_methods = [\n    \"on_cell_start\",\n    \"on_cell_execute\",\n    \"on_cell_complete\",\n    \"on_cell_executed\",\n    \"on_cell_error\",\n    \"on_notebook_start\",\n    \"on_notebook_complete\",\n    \"on_notebook_error\",\n]\n\n\ndef get_executor_with_hooks(nb=None, executor=None, async_hooks=False):\n    if async_hooks:\n        hooks = {key: AsyncMock() for key in hook_methods}\n    else:\n        hooks = {key: MagicMock() for key in hook_methods}\n    if nb is not None:\n        if executor is not None:\n            raise RuntimeError(\"Cannot pass nb and executor at the same time\")\n        executor = NotebookClient(nb)\n    for k, v in hooks.items():\n        setattr(executor, k, v)\n    return executor, hooks\n\n\nEXECUTE_REPLY_OK = {\n    \"parent_header\": {\"msg_id\": \"fake_id\"},\n    \"content\": {\"status\": \"ok\", \"execution_count\": 1},\n}\nEXECUTE_REPLY_ERROR = {\n    \"parent_header\": {\"msg_id\": \"fake_id\"},\n    \"content\": {\"status\": \"error\"},\n    \"msg_type\": \"execute_reply\",\n    \"header\": {\"msg_type\": \"execute_reply\"},\n}\n\n\nclass AsyncMock(Mock):\n    pass\n\n\ndef make_future(obj: Any) -> asyncio.Future[Any]:\n    try:\n        loop = asyncio.get_running_loop()\n    except RuntimeError:\n        loop = asyncio.new_event_loop()\n        asyncio.set_event_loop(loop)\n    future: asyncio.Future[Any] = asyncio.Future(loop=loop)\n    future.set_result(obj)\n    return future\n\n\ndef normalize_base64(b64_text):\n    # if it's base64, pass it through b64 decode/encode to avoid\n    # equivalent values from being considered unequal\n    try:\n        return b64encode(b64decode(b64_text.encode(\"ascii\"))).decode(\"ascii\")\n    except (ValueError, TypeError):\n        return b64_text\n\n\ndef run_notebook(filename, opts, resources=None):\n    \"\"\"Loads and runs a notebook, returning both the version prior to\n    running it and the version after running it.\n\n    \"\"\"\n    with open(filename) as f:\n        input_nb = nbformat.read(f, 4)\n\n    cleaned_input_nb = copy.deepcopy(input_nb)\n    for cell in cleaned_input_nb.cells:\n        if \"execution_count\" in cell:\n            del cell[\"execution_count\"]\n        cell[\"outputs\"] = []\n\n    if resources:\n        opts = {\"resources\": resources, **opts}\n    executor = NotebookClient(cleaned_input_nb, **opts)\n\n    with warnings.catch_warnings():\n        # suppress warning from jupyter_client's deprecated cleanup()\n        warnings.simplefilter(action=\"ignore\", category=FutureWarning)\n        # Override terminal size to standardise traceback format\n        with modified_env({\"COLUMNS\": \"80\", \"LINES\": \"24\"}):\n            output_nb = executor.execute()\n\n    return input_nb, output_nb\n\n\ndef run_notebook_wrapper(args):\n    # since concurrent.futures.ProcessPoolExecutor doesn't have starmap,\n    # we need to unpack the arguments\n    return run_notebook(*args)\n\n\nasync def async_run_notebook(filename, opts, resources=None):\n    \"\"\"Loads and runs a notebook, returning both the version prior to\n    running it and the version after running it.\n\n    \"\"\"\n    with open(filename) as f:\n        input_nb = nbformat.read(f, 4)\n\n    cleaned_input_nb = copy.deepcopy(input_nb)\n    for cell in cleaned_input_nb.cells:\n        if \"execution_count\" in cell:\n            del cell[\"execution_count\"]\n        cell[\"outputs\"] = []\n\n    if resources:\n        opts = {\"resources\": resources, **opts}\n    executor = NotebookClient(cleaned_input_nb, **opts)\n\n    # Override terminal size to standardise traceback format\n    with modified_env({\"COLUMNS\": \"80\", \"LINES\": \"24\"}):\n        output_nb = await executor.async_execute()\n\n    return input_nb, output_nb\n\n\ndef prepare_cell_mocks(*messages_input, reply_msg=None):\n    \"\"\"\n    This function prepares a executor object which has a fake kernel client\n    to mock the messages sent over zeromq. The mock kernel client will return\n    the messages passed into this wrapper back from ``preproc.kc.iopub_channel.get_msg``\n    callbacks. It also appends a kernel idle message to the end of messages.\n    \"\"\"\n    parent_id = \"fake_id\"\n    messages = list(messages_input)\n    # Always terminate messages with an idle to exit the loop\n    messages.append({\"msg_type\": \"status\", \"content\": {\"execution_state\": \"idle\"}})\n\n    def shell_channel_message_mock():\n        # Return the message generator for\n        # self.kc.shell_channel.get_msg => {'parent_header': {'msg_id': parent_id}}\n        return AsyncMock(\n            return_value=make_future(\n                NBClientTestsBase.merge_dicts(\n                    {\n                        \"parent_header\": {\"msg_id\": parent_id},\n                        \"content\": {\"status\": \"ok\", \"execution_count\": 1},\n                    },\n                    reply_msg or {},\n                )\n            )\n        )\n\n    def iopub_messages_mock():\n        # Return the message generator for\n        # self.kc.iopub_channel.get_msg => messages[i]\n        return AsyncMock(\n            side_effect=[\n                # Default the parent_header so mocks don't need to include this\n                make_future(\n                    NBClientTestsBase.merge_dicts({\"parent_header\": {\"msg_id\": parent_id}}, msg)\n                )\n                for msg in messages\n            ]\n        )\n\n    def prepared_wrapper(func):\n        @functools.wraps(func)\n        def test_mock_wrapper(self):\n            \"\"\"\n            This inner function wrapper populates the executor object with\n            the fake kernel client. This client has its iopub and shell\n            channels mocked so as to fake the setup handshake and return\n            the messages passed into prepare_cell_mocks as the execute_cell loop\n            processes them.\n            \"\"\"\n            cell_mock = NotebookNode(\n                source='\"foo\" = \"bar\"', metadata={}, cell_type=\"code\", outputs=[]\n            )\n\n            class NotebookClientWithParentID(NotebookClient):\n                parent_id: str\n\n            nb = nbformat.v4.new_notebook()\n            executor = NotebookClientWithParentID(nb)\n            executor.nb.cells = [cell_mock]\n\n            # self.kc.iopub_channel.get_msg => message_mock.side_effect[i]\n            message_mock = iopub_messages_mock()\n            executor.kc = MagicMock(\n                iopub_channel=MagicMock(get_msg=message_mock),\n                shell_channel=MagicMock(get_msg=shell_channel_message_mock()),\n                execute=MagicMock(return_value=parent_id),\n                is_alive=MagicMock(return_value=make_future(True)),\n            )\n            executor.parent_id = parent_id\n            return func(self, executor, cell_mock, message_mock)\n\n        return test_mock_wrapper\n\n    return prepared_wrapper\n\n\ndef normalize_output(output):\n    \"\"\"\n    Normalizes outputs for comparison.\n    \"\"\"\n    output = dict(output)\n    if \"metadata\" in output:\n        del output[\"metadata\"]\n    if \"text\" in output:\n        output[\"text\"] = re.sub(addr_pat, \"<HEXADDR>\", output[\"text\"])\n    if \"text/plain\" in output.get(\"data\", {}):\n        output[\"data\"][\"text/plain\"] = re.sub(addr_pat, \"<HEXADDR>\", output[\"data\"][\"text/plain\"])\n    if \"application/vnd.jupyter.widget-view+json\" in output.get(\"data\", {}):\n        output[\"data\"][\"application/vnd.jupyter.widget-view+json\"][\"model_id\"] = \"<MODEL_ID>\"\n    if \"image/svg+xml\" in output.get(\"data\", {}):\n        output[\"data\"][\"image/svg+xml\"] = xmltodict.parse(output[\"data\"][\"image/svg+xml\"])\n    for key, value in output.get(\"data\", {}).items():\n        if isinstance(value, str):\n            output[\"data\"][key] = normalize_base64(value)\n    if \"traceback\" in output:\n        tb = []\n        for line in output[\"traceback\"]:\n            line = re.sub(ipython_input_pat, \"<IPY-INPUT>\", strip_ansi(line))\n            line = re.sub(ipython8_input_pat, \"<IPY-INPUT>\", strip_ansi(line))\n            tb.append(line)\n        output[\"traceback\"] = tb\n\n    return output\n\n\ndef assert_notebooks_equal(expected, actual):\n    expected_cells = expected[\"cells\"]\n    actual_cells = actual[\"cells\"]\n    assert len(expected_cells) == len(actual_cells)\n\n    for expected_cell, actual_cell in zip(expected_cells, actual_cells, strict=False):\n        # Uncomment these to help debug test failures better\n        # from pprint import pprint\n        # pprint(expected_cell)\n        # pprint(actual_cell)\n        expected_outputs = expected_cell.get(\"outputs\", [])\n        actual_outputs = actual_cell.get(\"outputs\", [])\n        normalized_expected_outputs = list(map(normalize_output, expected_outputs))\n        normalized_actual_outputs = list(map(normalize_output, actual_outputs))\n        assert normalized_expected_outputs == normalized_actual_outputs\n\n        expected_execution_count = expected_cell.get(\"execution_count\", None)\n        actual_execution_count = actual_cell.get(\"execution_count\", None)\n        assert expected_execution_count == actual_execution_count\n\n\ndef notebook_resources():\n    \"\"\"\n    Prepare a notebook resources dictionary for executing test\n    notebooks in the ``files`` folder.\n    \"\"\"\n    return {\"metadata\": {\"path\": os.path.join(current_dir, \"files\")}}\n\n\ndef filter_messages_on_error_output(err_output):\n    allowed_lines = [\n        # ipykernel might be installed without debugpy extension\n        \"[IPKernelApp] WARNING | debugpy_stream undefined, debugging will not be enabled\",\n    ]\n    filtered_result = [line for line in err_output.splitlines() if line not in allowed_lines]\n\n    return os.linesep.join(filtered_result)\n\n\n@pytest.mark.parametrize(\n    [\"input_name\", \"opts\"],\n    [\n        (\"Other Comms.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"Clear Output.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"Empty Cell.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"Factorials.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"HelloWorld.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"Inline Image.ipynb\", {\"kernel_name\": \"python\"}),\n        pytest.param(\n            \"Interrupt.ipynb\",\n            {\n                \"kernel_name\": \"python\",\n                \"timeout\": 3,\n                \"interrupt_on_timeout\": True,\n                \"allow_errors\": True,\n            },\n            marks=pytest.mark.skipif(python_implementation() == \"PyPy\", reason=\"PyPy hangs\"),\n        ),\n        (\"JupyterWidgets.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"Skip Exceptions with Cell Tags.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"Skip Exceptions.ipynb\", {\"kernel_name\": \"python\", \"allow_errors\": True}),\n        (\"Skip Execution with Cell Tag.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"SVG.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"Unicode.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"UnicodePy3.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"update-display-id.ipynb\", {\"kernel_name\": \"python\"}),\n        (\"Check History in Memory.ipynb\", {\"kernel_name\": \"python\"}),\n    ],\n)\ndef test_run_all_notebooks(input_name, opts):\n    \"\"\"Runs a series of test notebooks and compares them to their actual output\"\"\"\n    input_file = os.path.join(current_dir, \"files\", input_name)\n    input_nb, output_nb = run_notebook(input_file, opts, notebook_resources())\n    assert_notebooks_equal(input_nb, output_nb)\n\n\n@flaky\ndef test_parallel_notebooks(capfd, tmpdir):\n    \"\"\"Two notebooks should be able to be run simultaneously without problems.\n\n    The two notebooks spawned here use the filesystem to check that the other notebook\n    wrote to the filesystem.\"\"\"\n\n    opts = {\"kernel_name\": \"python\"}\n    input_name = \"Parallel Execute {label}.ipynb\"\n    input_file = os.path.join(current_dir, \"files\", input_name)\n    res = notebook_resources()\n\n    with modified_env({\"NBEXECUTE_TEST_PARALLEL_TMPDIR\": str(tmpdir)}):\n        threads = [\n            threading.Thread(target=run_notebook, args=(input_file.format(label=label), opts, res))\n            for label in (\"A\", \"B\")\n        ]\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join(timeout=2)\n\n    captured = capfd.readouterr()\n    assert filter_messages_on_error_output(captured.err) == \"\"\n\n\n@flaky\n@pytest.mark.skipif(os.name == \"nt\", reason=\"warns about event loop on Windows\")\ndef test_many_parallel_notebooks(capfd):\n    \"\"\"Ensure that when many IPython kernels are run in parallel, nothing awful happens.\n\n    Specifically, many IPython kernels when run simultaneously would encounter errors\n    due to using the same SQLite history database.\n    \"\"\"\n    opts = {\"kernel_name\": \"python\", \"timeout\": 5}\n    input_name = \"HelloWorld.ipynb\"\n    input_file = os.path.join(current_dir, \"files\", input_name)\n    res = NBClientTestsBase().build_resources()\n    res[\"metadata\"][\"path\"] = os.path.join(current_dir, \"files\")\n\n    with warnings.catch_warnings():\n        # suppress warning from jupyter_client's deprecated cleanup()\n        warnings.simplefilter(action=\"ignore\", category=FutureWarning)\n\n        # run once, to trigger creating the original context\n        run_notebook(input_file, opts, res)\n\n        with concurrent.futures.ProcessPoolExecutor(max_workers=2) as executor:\n            executor.map(run_notebook_wrapper, [(input_file, opts, res) for i in range(8)])\n\n    captured = capfd.readouterr()\n    assert filter_messages_on_error_output(captured.err) == \"\"\n\n\n@flaky\ndef test_async_parallel_notebooks(capfd, tmpdir):\n    \"\"\"Two notebooks should be able to be run simultaneously without problems.\n\n    The two notebooks spawned here use the filesystem to check that the other notebook\n    wrote to the filesystem.\"\"\"\n\n    opts = {\"kernel_name\": \"python\"}\n    input_name = \"Parallel Execute {label}.ipynb\"\n    input_file = os.path.join(current_dir, \"files\", input_name)\n    res = notebook_resources()\n\n    with modified_env({\"NBEXECUTE_TEST_PARALLEL_TMPDIR\": str(tmpdir)}):\n\n        async def run_tasks():\n            tasks = [\n                async_run_notebook(input_file.format(label=label), opts, res)\n                for label in (\"A\", \"B\")\n            ]\n            await asyncio.gather(*tasks)\n\n        asyncio.run(run_tasks())\n\n    captured = capfd.readouterr()\n    assert filter_messages_on_error_output(captured.err) == \"\"\n\n\n@flaky\ndef test_many_async_parallel_notebooks(capfd):\n    \"\"\"Ensure that when many IPython kernels are run in parallel, nothing awful happens.\n\n    Specifically, many IPython kernels when run simultaneously would encounter errors\n    due to using the same SQLite history database.\n    \"\"\"\n    opts = {\"kernel_name\": \"python\", \"timeout\": 5}\n    input_name = \"HelloWorld.ipynb\"\n    input_file = os.path.join(current_dir, \"files\", input_name)\n    res = NBClientTestsBase().build_resources()\n    res[\"metadata\"][\"path\"] = os.path.join(current_dir, \"files\")\n\n    # run once, to trigger creating the original context\n    run_notebook(input_file, opts, res)\n\n    async def run_tasks():\n        tasks = [async_run_notebook(input_file, opts, res) for i in range(4)]\n        await asyncio.gather(*tasks)\n\n    asyncio.run(run_tasks())\n\n    captured = capfd.readouterr()\n    assert filter_messages_on_error_output(captured.err) == \"\"\n\n\ndef test_execution_timing():\n    \"\"\"Compare the execution timing information stored in the cell with the\n    actual time it took to run the cell. Also check for the cell timing string\n    format.\"\"\"\n    opts = {\"kernel_name\": \"python\"}\n    input_name = \"Sleep1s.ipynb\"\n    input_file = os.path.join(current_dir, \"files\", input_name)\n    res = notebook_resources()\n    input_nb, output_nb = run_notebook(input_file, opts, res)\n\n    def get_time_from_str(s):\n        time_format = \"%Y-%m-%dT%H:%M:%S.%fZ\"\n        return datetime.datetime.strptime(s, time_format)\n\n    execution_timing = output_nb[\"cells\"][1][\"metadata\"][\"execution\"]\n    status_busy = get_time_from_str(execution_timing[\"iopub.status.busy\"])\n    execute_input = get_time_from_str(execution_timing[\"iopub.execute_input\"])\n    execute_reply = get_time_from_str(execution_timing[\"shell.execute_reply\"])\n    status_idle = get_time_from_str(execution_timing[\"iopub.status.idle\"])\n\n    cell_start = get_time_from_str(output_nb[\"cells\"][2][\"outputs\"][0][\"text\"])\n    cell_end = get_time_from_str(output_nb[\"cells\"][3][\"outputs\"][0][\"text\"])\n\n    delta = datetime.timedelta(milliseconds=100)\n    assert status_busy - cell_start < delta\n    assert execute_input - cell_start < delta\n    assert execute_reply - cell_end < delta\n    assert status_idle - cell_end < delta\n\n\ndef test_synchronous_setup_kernel():\n    nb = nbformat.v4.new_notebook()\n    executor = NotebookClient(nb)\n    with executor.setup_kernel():\n        # Prove it initialized client\n        assert executor.kc is not None\n    # Prove it removed the client (and hopefully cleaned up)\n    assert executor.kc is None\n\n\ndef test_startnewkernel_with_kernelmanager():\n    nb = nbformat.v4.new_notebook()\n    km = KernelManager()\n    executor = NotebookClient(nb, km=km)\n    executor.start_new_kernel()\n    kc = executor.start_new_kernel_client()\n    # prove it initialized client\n    assert kc is not None\n    # since we are not using the setup_kernel context manager,\n    # cleanup has to be done manually\n    kc.shutdown()\n    km.cleanup_resources()\n    kc.stop_channels()\n\n\ndef test_start_new_kernel_history_file_setting():\n    nb = nbformat.v4.new_notebook()\n    km = KernelManager()\n    executor = NotebookClient(nb, km=km)\n    kc = km.client()\n\n    # Should start empty\n    assert executor.extra_arguments == []\n    # Should assign memory setting for ipykernel\n    executor.start_new_kernel()\n    assert executor.extra_arguments == [\"--HistoryManager.hist_file=:memory:\"]\n    # Should not add a second hist_file assignment\n    executor.start_new_kernel()\n    assert executor.extra_arguments == [\"--HistoryManager.hist_file=:memory:\"]\n\n    # since we are not using the setup_kernel context manager,\n    # cleanup has to be done manually\n    kc.shutdown()\n    km.cleanup_resources()\n    kc.stop_channels()\n\n\n@pytest.mark.skipif(int(version_info[0]) < 7, reason=\"requires client 7+\")\ndef test_start_new_kernel_client_cleans_up_kernel_on_failure():\n    class FakeClient(KernelClient):\n        def start_channels(\n            self,\n            shell: bool = True,\n            iopub: bool = True,\n            stdin: bool = True,\n            hb: bool = True,\n            control: bool = True,\n        ) -> None:\n            raise Exception(\"Any error\")\n\n        def stop_channels(self) -> None:\n            pass\n\n    nb = nbformat.v4.new_notebook()\n    km = KernelManager()\n    km.client_factory = FakeClient\n    executor = NotebookClient(nb, km=km)\n    executor.start_new_kernel()\n    assert km.has_kernel\n    assert executor.km is not None\n\n    with pytest.raises(Exception) as err:\n        executor.start_new_kernel_client()\n\n    assert str(err.value.args[0]) == \"Any error\"\n    assert executor.kc is None\n    assert executor.km is None\n    assert not km.has_kernel  # type:ignore[unreachable]\n\n\nclass TestExecute(NBClientTestsBase):\n    \"\"\"Contains test functions for execute.py\"\"\"\n\n    maxDiff = None\n\n    def test_constructor(self):\n        NotebookClient(nbformat.v4.new_notebook())\n\n    def test_populate_language_info(self):\n        nb = nbformat.v4.new_notebook()  # Certainly has no language_info.\n        executor = NotebookClient(nb, kernel_name=\"python\")\n        nb = executor.execute()\n        assert \"language_info\" in nb.metadata\n\n    def test_empty_path(self):\n        \"\"\"Can the kernel be started when the path is empty?\"\"\"\n        filename = os.path.join(current_dir, \"files\", \"HelloWorld.ipynb\")\n        res = self.build_resources()\n        res[\"metadata\"][\"path\"] = \"\"\n        input_nb, output_nb = run_notebook(filename, {}, res)\n        assert_notebooks_equal(input_nb, output_nb)\n\n    @pytest.mark.xfail(\n        \"python3\" not in KernelSpecManager().find_kernel_specs(),\n        reason=\"requires a python3 kernelspec\",\n    )\n    def test_empty_kernel_name(self):\n        \"\"\"Can kernel in nb metadata be found when an empty string is passed?\n\n        Note: this pattern should be discouraged in practice.\n        Passing in no kernel_name to NotebookClient is recommended instead.\n        \"\"\"\n        filename = os.path.join(current_dir, \"files\", \"UnicodePy3.ipynb\")\n        res = self.build_resources()\n        input_nb, output_nb = run_notebook(filename, {\"kernel_name\": \"\"}, res)\n        assert_notebooks_equal(input_nb, output_nb)\n        with pytest.raises(TraitError):\n            input_nb, output_nb = run_notebook(filename, {\"kernel_name\": None}, res)\n\n    def test_disable_stdin(self):\n        \"\"\"Test disabling standard input\"\"\"\n        filename = os.path.join(current_dir, \"files\", \"Disable Stdin.ipynb\")\n        res = self.build_resources()\n        res[\"metadata\"][\"path\"] = os.path.dirname(filename)\n        input_nb, output_nb = run_notebook(filename, {\"allow_errors\": True}, res)\n\n        # We need to special-case this particular notebook, because the\n        # traceback contains machine-specific stuff like where IPython\n        # is installed. It is sufficient here to just check that an error\n        # was thrown, and that it was a StdinNotImplementedError\n        self.assertEqual(len(output_nb[\"cells\"]), 1)\n        self.assertEqual(len(output_nb[\"cells\"][0][\"outputs\"]), 1)\n        output = output_nb[\"cells\"][0][\"outputs\"][0]\n        self.assertEqual(output[\"output_type\"], \"error\")\n        self.assertEqual(output[\"ename\"], \"StdinNotImplementedError\")\n        self.assertEqual(\n            output[\"evalue\"],\n            \"raw_input was called, but this frontend does not support input requests.\",\n        )\n\n    def test_timeout(self):\n        \"\"\"Check that an error is raised when a computation times out\"\"\"\n        filename = os.path.join(current_dir, \"files\", \"Interrupt.ipynb\")\n        res = self.build_resources()\n        res[\"metadata\"][\"path\"] = os.path.dirname(filename)\n\n        with pytest.raises(TimeoutError) as err:\n            run_notebook(filename, {\"timeout\": 1}, res)\n        self.assertEqual(\n            str(err.value.args[0]),\n            \"\"\"A cell timed out while it was being executed, after 1 seconds.\nThe message was: Cell execution timed out.\nHere is a preview of the cell contents:\n-------------------\nwhile True: continue\n-------------------\n\"\"\",\n        )\n\n    def test_timeout_func(self):\n        \"\"\"Check that an error is raised when a computation times out\"\"\"\n        filename = os.path.join(current_dir, \"files\", \"Interrupt.ipynb\")\n        res = self.build_resources()\n        res[\"metadata\"][\"path\"] = os.path.dirname(filename)\n\n        def timeout_func(source):\n            return 10\n\n        with pytest.raises(TimeoutError):\n            run_notebook(filename, {\"timeout_func\": timeout_func}, res)\n\n    def test_sync_kernel_manager(self):\n        nb = nbformat.v4.new_notebook()  # Certainly has no language_info.\n        executor = NotebookClient(nb, kernel_name=\"python\", kernel_manager_class=KernelManager)\n        nb = executor.execute()\n        assert \"language_info\" in nb.metadata\n        with executor.setup_kernel():\n            assert executor.kc is not None\n            info_msg = executor.wait_for_reply(executor.kc.kernel_info())\n            assert info_msg is not None\n            assert \"name\" in info_msg[\"content\"][\"language_info\"]\n\n    @flaky\n    def test_kernel_death_after_timeout(self):\n        \"\"\"Check that an error is raised when the kernel is_alive is false after a cell timed out\"\"\"\n        filename = os.path.join(current_dir, \"files\", \"Interrupt.ipynb\")\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n        res = self.build_resources()\n        res[\"metadata\"][\"path\"] = os.path.dirname(filename)\n\n        executor = NotebookClient(input_nb, timeout=1)\n\n        with pytest.raises(TimeoutError):\n            executor.execute()\n        km = executor.create_kernel_manager()\n\n        async def is_alive():\n            return False\n\n        km.is_alive = is_alive  # type:ignore[method-assign]\n        # Will be a RuntimeError, TimeoutError, or subclass DeadKernelError\n        # depending\n        # on if jupyter_client or nbconvert catches the dead client first\n        with pytest.raises((RuntimeError, TimeoutError)):\n            input_nb, output_nb = executor.execute()\n\n    def test_kernel_death_during_execution(self):\n        \"\"\"Check that an error is raised when the kernel is_alive is false during a cell\n        execution.\n        \"\"\"\n        filename = os.path.join(current_dir, \"files\", \"Autokill.ipynb\")\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n\n        executor = NotebookClient(input_nb)\n\n        with pytest.raises(RuntimeError):\n            executor.execute()\n\n    def test_allow_errors(self):\n        \"\"\"\n        Check that conversion halts if ``allow_errors`` is False.\n        \"\"\"\n        filename = os.path.join(current_dir, \"files\", \"Skip Exceptions.ipynb\")\n        res = self.build_resources()\n        res[\"metadata\"][\"path\"] = os.path.dirname(filename)\n        with pytest.raises(CellExecutionError) as exc:\n            run_notebook(filename, {\"allow_errors\": False}, res)\n\n        assert isinstance(str(exc.value), str)\n        exc_str = strip_ansi(str(exc.value))\n        # FIXME: we seem to have an encoding problem on Windows\n        # same check in force_raise_errors\n        if not sys.platform.startswith(\"win\"):\n            assert \"# üñîçø∂é\" in exc_str\n\n    def test_force_raise_errors(self):\n        \"\"\"\n        Check that conversion halts if the ``force_raise_errors`` traitlet on\n        NotebookClient is set to True.\n        \"\"\"\n        filename = os.path.join(current_dir, \"files\", \"Skip Exceptions with Cell Tags.ipynb\")\n        res = self.build_resources()\n        res[\"metadata\"][\"path\"] = os.path.dirname(filename)\n        with pytest.raises(CellExecutionError) as exc:\n            run_notebook(filename, {\"force_raise_errors\": True}, res)\n\n        # verify CellExecutionError contents\n        exc_str = strip_ansi(str(exc.value))\n        # print for better debugging with captured output\n        # print(exc_str)\n        assert \"Exception: message\" in exc_str\n        # FIXME: unicode handling seems to have a problem on Windows\n        # same check in allow_errors\n        if not sys.platform.startswith(\"win\"):\n            assert \"# üñîçø∂é\" in exc_str\n        assert \"stderr\" in exc_str\n        assert \"stdout\" in exc_str\n        assert \"hello\\n\" in exc_str\n        assert \"errorred\\n\" in exc_str\n        # stricter check for stream output format\n        assert \"\\n\".join([\"\", \"----- stdout -----\", \"hello\", \"---\"]) in exc_str\n        assert \"\\n\".join([\"\", \"----- stderr -----\", \"errorred\", \"---\"]) in exc_str\n\n    def test_reset_kernel_client(self):\n        filename = os.path.join(current_dir, \"files\", \"HelloWorld.ipynb\")\n\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n\n        executor = NotebookClient(\n            input_nb,\n            resources=self.build_resources(),\n        )\n\n        executor.execute(cleanup_kc=False)\n        # we didn't ask to reset the kernel client, a new one must have been created\n        kc = executor.kc\n        assert kc is not None\n\n        executor.execute(cleanup_kc=False)\n        # we didn't ask to reset the kernel client, the previously created one must have been reused\n        assert kc == executor.kc\n\n        executor.execute(reset_kc=True, cleanup_kc=False)\n        # we asked to reset the kernel client, the previous one must have been cleaned up,\n        # a new one must have been created\n        assert kc != executor.kc\n\n    def test_cleanup_kernel_client(self):\n        filename = os.path.join(current_dir, \"files\", \"HelloWorld.ipynb\")\n\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n\n        executor = NotebookClient(\n            input_nb,\n            resources=self.build_resources(),\n        )\n\n        executor.execute()\n        # we asked to cleanup the kernel client (default is True)\n        assert executor.kc is None\n\n        executor.execute(cleanup_kc=False)\n        # we didn't ask to reset the kernel client\n        # a new one must have been created and should still be available\n        assert executor.kc is not None\n\n    def test_custom_kernel_manager(self):\n        from .fake_kernelmanager import FakeCustomKernelManager\n\n        filename = os.path.join(current_dir, \"files\", \"HelloWorld.ipynb\")\n\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n\n        cleaned_input_nb = copy.deepcopy(input_nb)\n        for cell in cleaned_input_nb.cells:\n            if \"execution_count\" in cell:\n                del cell[\"execution_count\"]\n            cell[\"outputs\"] = []\n\n        executor = NotebookClient(\n            cleaned_input_nb,\n            resources=self.build_resources(),\n            kernel_manager_class=FakeCustomKernelManager,\n        )\n\n        # Override terminal size to standardise traceback format\n        with modified_env({\"COLUMNS\": \"80\", \"LINES\": \"24\"}):\n            executor.execute()\n\n        expected = FakeCustomKernelManager.expected_methods.items()\n\n        for method, call_count in expected:\n            self.assertNotEqual(call_count, 0, f\"{method} was called\")\n\n    def test_process_message_wrapper(self):\n        outputs: list[Any] = []\n\n        class WrappedPreProc(NotebookClient):\n            def process_message(self, msg, cell, cell_index):\n                result = super().process_message(msg, cell, cell_index)\n                if result:\n                    outputs.append(result)\n                return result\n\n        current_dir = os.path.dirname(__file__)\n        filename = os.path.join(current_dir, \"files\", \"HelloWorld.ipynb\")\n\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n\n        original = copy.deepcopy(input_nb)\n        wpp = WrappedPreProc(input_nb)\n        executed = wpp.execute()\n        assert outputs == [{\"name\": \"stdout\", \"output_type\": \"stream\", \"text\": \"Hello World\\n\"}]\n        assert_notebooks_equal(original, executed)\n\n    def test_execute_function(self):\n        # Test the execute() convenience API\n        filename = os.path.join(current_dir, \"files\", \"HelloWorld.ipynb\")\n\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n\n        original = copy.deepcopy(input_nb)\n        executed = execute(original, os.path.dirname(filename))\n        assert_notebooks_equal(original, executed)\n\n    def test_widgets(self):\n        \"\"\"Runs a test notebook with widgets and checks the widget state is saved.\"\"\"\n        input_file = os.path.join(current_dir, \"files\", \"JupyterWidgets.ipynb\")\n        opts = {\"kernel_name\": \"python\"}\n        res = self.build_resources()\n        res[\"metadata\"][\"path\"] = os.path.dirname(input_file)\n        input_nb, output_nb = run_notebook(input_file, opts, res)\n\n        output_data = [\n            output.get(\"data\", {}) for cell in output_nb[\"cells\"] for output in cell[\"outputs\"]\n        ]\n\n        model_ids = [\n            data[\"application/vnd.jupyter.widget-view+json\"][\"model_id\"]\n            for data in output_data\n            if \"application/vnd.jupyter.widget-view+json\" in data\n        ]\n\n        wdata = output_nb[\"metadata\"][\"widgets\"][\"application/vnd.jupyter.widget-state+json\"]\n        for k in model_ids:\n            d = wdata[\"state\"][k]\n            assert \"model_name\" in d\n            assert \"model_module\" in d\n            assert \"state\" in d\n        assert \"version_major\" in wdata\n        assert \"version_minor\" in wdata\n\n    def test_execution_hook(self):\n        filename = os.path.join(current_dir, \"files\", \"HelloWorld.ipynb\")\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n        executor, hooks = get_executor_with_hooks(nb=input_nb)\n        executor.execute()\n        hooks[\"on_cell_start\"].assert_called_once()\n        hooks[\"on_cell_execute\"].assert_called_once()\n        hooks[\"on_cell_complete\"].assert_called_once()\n        hooks[\"on_cell_executed\"].assert_called_once()\n        hooks[\"on_cell_error\"].assert_not_called()\n        hooks[\"on_notebook_start\"].assert_called_once()\n        hooks[\"on_notebook_complete\"].assert_called_once()\n        hooks[\"on_notebook_error\"].assert_not_called()\n\n    def test_error_execution_hook_error(self):\n        filename = os.path.join(current_dir, \"files\", \"Error.ipynb\")\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n        executor, hooks = get_executor_with_hooks(nb=input_nb)\n        with pytest.raises(CellExecutionError):\n            executor.execute()\n        hooks[\"on_cell_start\"].assert_called_once()\n        hooks[\"on_cell_execute\"].assert_called_once()\n        hooks[\"on_cell_complete\"].assert_called_once()\n        hooks[\"on_cell_executed\"].assert_called_once()\n        hooks[\"on_cell_error\"].assert_called_once()\n        hooks[\"on_notebook_start\"].assert_called_once()\n        hooks[\"on_notebook_complete\"].assert_called_once()\n        hooks[\"on_notebook_error\"].assert_not_called()\n\n    def test_error_notebook_hook(self):\n        filename = os.path.join(current_dir, \"files\", \"Autokill.ipynb\")\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n        executor, hooks = get_executor_with_hooks(nb=input_nb)\n        with pytest.raises(RuntimeError):\n            executor.execute()\n        hooks[\"on_cell_start\"].assert_called_once()\n        hooks[\"on_cell_execute\"].assert_called_once()\n        hooks[\"on_cell_complete\"].assert_called_once()\n        hooks[\"on_cell_executed\"].assert_not_called()\n        hooks[\"on_cell_error\"].assert_not_called()\n        hooks[\"on_notebook_start\"].assert_called_once()\n        hooks[\"on_notebook_complete\"].assert_called_once()\n        hooks[\"on_notebook_error\"].assert_called_once()\n\n    def test_async_execution_hook(self):\n        filename = os.path.join(current_dir, \"files\", \"HelloWorld.ipynb\")\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n        executor, hooks = get_executor_with_hooks(nb=input_nb)\n        executor.execute()\n        hooks[\"on_cell_start\"].assert_called_once()\n        hooks[\"on_cell_execute\"].assert_called_once()\n        hooks[\"on_cell_complete\"].assert_called_once()\n        hooks[\"on_cell_executed\"].assert_called_once()\n        hooks[\"on_cell_error\"].assert_not_called()\n        hooks[\"on_notebook_start\"].assert_called_once()\n        hooks[\"on_notebook_complete\"].assert_called_once()\n        hooks[\"on_notebook_error\"].assert_not_called()\n\n    def test_error_async_execution_hook(self):\n        filename = os.path.join(current_dir, \"files\", \"Error.ipynb\")\n        with open(filename) as f:\n            input_nb = nbformat.read(f, 4)\n        executor, hooks = get_executor_with_hooks(nb=input_nb)\n        with pytest.raises(CellExecutionError):\n            executor.execute()\n        hooks[\"on_cell_start\"].assert_called_once()\n        hooks[\"on_cell_execute\"].assert_called_once()\n        hooks[\"on_cell_complete\"].assert_called_once()\n        hooks[\"on_cell_executed\"].assert_called_once()\n        hooks[\"on_cell_error\"].assert_called_once()\n        hooks[\"on_notebook_start\"].assert_called_once()\n        hooks[\"on_notebook_complete\"].assert_called_once()\n        hooks[\"on_notebook_error\"].assert_not_called()\n\n\nclass TestRunCell(NBClientTestsBase):\n    \"\"\"Contains test functions for NotebookClient.execute_cell\"\"\"\n\n    @prepare_cell_mocks()\n    def test_idle_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # Just the exit message should be fetched\n        assert message_mock.call_count == 1\n        # Ensure no outputs were generated\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            \"parent_header\": {\"msg_id\": \"wrong_parent\"},\n            \"content\": {\"name\": \"stdout\", \"text\": \"foo\"},\n        }\n    )\n    def test_message_for_wrong_parent(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An ignored stream followed by an idle\n        assert message_mock.call_count == 2\n        # Ensure no output was written\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"status\",\n            \"header\": {\"msg_type\": \"status\"},\n            \"content\": {\"execution_state\": \"busy\"},\n        }\n    )\n    def test_busy_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # One busy message, followed by an idle\n        assert message_mock.call_count == 2\n        # Ensure no outputs were generated\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stdout\", \"text\": \"foo\"},\n        },\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stderr\", \"text\": \"bar\"},\n        },\n    )\n    def test_deadline_exec_reply(self, executor, cell_mock, message_mock):\n        # exec_reply is never received, so we expect to hit the timeout.\n        async def get_msg(timeout):\n            await asyncio.sleep(timeout)\n            raise Empty\n\n        executor.kc.shell_channel.get_msg = get_msg\n        executor.timeout = 1\n\n        with pytest.raises(TimeoutError):\n            executor.execute_cell(cell_mock, 0)\n\n        assert message_mock.call_count == 3\n        # Ensure the output was captured\n        self.assertListEqual(\n            cell_mock.outputs,\n            [\n                {\"output_type\": \"stream\", \"name\": \"stdout\", \"text\": \"foo\"},\n                {\"output_type\": \"stream\", \"name\": \"stderr\", \"text\": \"bar\"},\n            ],\n        )\n\n    @prepare_cell_mocks()\n    def test_deadline_iopub(self, executor, cell_mock, message_mock):\n        # The shell_channel will complete, so we expect only to hit the iopub timeout.\n        message_mock.side_effect = Empty()\n        executor.raise_on_iopub_timeout = True\n\n        with pytest.raises(TimeoutError):\n            executor.execute_cell(cell_mock, 0)\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stdout\", \"text\": \"foo\"},\n        },\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stderr\", \"text\": \"bar\"},\n        },\n    )\n    def test_eventual_deadline_iopub(self, executor, cell_mock, message_mock):\n        # Process a few messages before raising a timeout from iopub\n        def message_seq(messages):\n            yield from messages\n            while True:\n                yield Empty()\n\n        message_mock.side_effect = message_seq(list(message_mock.side_effect)[:-1])\n        executor.kc.shell_channel.get_msg = Mock(\n            return_value=make_future({\"parent_header\": {\"msg_id\": executor.parent_id}})\n        )\n        executor.raise_on_iopub_timeout = True\n\n        with pytest.raises(TimeoutError):\n            executor.execute_cell(cell_mock, 0)\n\n        assert message_mock.call_count >= 3\n        # Ensure the output was captured\n        self.assertListEqual(\n            cell_mock.outputs,\n            [\n                {\"output_type\": \"stream\", \"name\": \"stdout\", \"text\": \"foo\"},\n                {\"output_type\": \"stream\", \"name\": \"stderr\", \"text\": \"bar\"},\n            ],\n        )\n\n    @prepare_cell_mocks(\n        {\"msg_type\": \"execute_input\", \"header\": {\"msg_type\": \"execute_input\"}, \"content\": {}}\n    )\n    def test_execute_input_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # One ignored execute_input, followed by an idle\n        assert message_mock.call_count == 2\n        # Ensure no outputs were generated\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stdout\", \"text\": \"foo\"},\n        },\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stderr\", \"text\": \"bar\"},\n        },\n    )\n    def test_stream_messages(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An stdout then stderr stream followed by an idle\n        assert message_mock.call_count == 3\n        # Ensure the output was captured\n        self.assertListEqual(\n            cell_mock.outputs,\n            [\n                {\"output_type\": \"stream\", \"name\": \"stdout\", \"text\": \"foo\"},\n                {\"output_type\": \"stream\", \"name\": \"stderr\", \"text\": \"bar\"},\n            ],\n        )\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            \"content\": {\"name\": \"stdout\", \"text\": \"foo\"},\n        },\n        {\"msg_type\": \"clear_output\", \"header\": {\"msg_type\": \"clear_output\"}, \"content\": {}},\n    )\n    def test_clear_output_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # A stream, followed by a clear, and then an idle\n        assert message_mock.call_count == 3\n        # Ensure the output was cleared\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stdout\", \"text\": \"foo\"},\n        },\n        {\n            \"msg_type\": \"clear_output\",\n            \"header\": {\"msg_type\": \"clear_output\"},\n            \"content\": {\"wait\": True},\n        },\n    )\n    def test_clear_output_wait_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # A stream, followed by a clear, and then an idle\n        assert message_mock.call_count == 3\n        # Should be true without another message to trigger the clear\n        self.assertTrue(executor.clear_before_next_output)\n        # Ensure the output wasn't cleared yet\n        assert cell_mock.outputs == [{\"output_type\": \"stream\", \"name\": \"stdout\", \"text\": \"foo\"}]\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stdout\", \"text\": \"foo\"},\n        },\n        {\n            \"msg_type\": \"clear_output\",\n            \"header\": {\"msg_type\": \"clear_output\"},\n            \"content\": {\"wait\": True},\n        },\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stderr\", \"text\": \"bar\"},\n        },\n    )\n    def test_clear_output_wait_then_message_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An stdout stream, followed by a wait clear, an stderr stream, and then an idle\n        assert message_mock.call_count == 4\n        # Should be false after the stderr message\n        assert not executor.clear_before_next_output\n        # Ensure the output wasn't cleared yet\n        assert cell_mock.outputs == [{\"output_type\": \"stream\", \"name\": \"stderr\", \"text\": \"bar\"}]\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stdout\", \"text\": \"foo\"},\n        },\n        {\n            \"msg_type\": \"clear_output\",\n            \"header\": {\"msg_type\": \"clear_output\"},\n            \"content\": {\"wait\": True},\n        },\n        {\n            \"msg_type\": \"update_display_data\",\n            \"header\": {\"msg_type\": \"update_display_data\"},\n            \"content\": {\"metadata\": {\"metafoo\": \"metabar\"}, \"data\": {\"foo\": \"bar\"}},\n        },\n    )\n    def test_clear_output_wait_then_update_display_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An stdout stream, followed by a wait clear, an stderr stream, and then an idle\n        assert message_mock.call_count == 4\n        # Should be false after the stderr message\n        assert executor.clear_before_next_output\n        # Ensure the output wasn't cleared yet because update_display doesn't add outputs\n        assert cell_mock.outputs == [{\"output_type\": \"stream\", \"name\": \"stdout\", \"text\": \"foo\"}]\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            \"content\": {\"execution_count\": 42},\n        }\n    )\n    def test_execution_count_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An execution count followed by an idle\n        assert message_mock.call_count == 2\n        assert cell_mock.execution_count == 42\n        # Ensure no outputs were generated\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            \"content\": {\"execution_count\": 42},\n        }\n    )\n    def test_execution_count_message_ignored_on_override(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0, execution_count=21)\n        # An execution count followed by an idle\n        assert message_mock.call_count == 2\n        assert cell_mock.execution_count == 21\n        # Ensure no outputs were generated\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"execution_count\": 42, \"name\": \"stdout\", \"text\": \"foo\"},\n        }\n    )\n    def test_execution_count_with_stream_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An execution count followed by an idle\n        assert message_mock.call_count == 2\n        assert cell_mock.execution_count == 42\n        # Should also consume the message stream\n        assert cell_mock.outputs == [{\"output_type\": \"stream\", \"name\": \"stdout\", \"text\": \"foo\"}]\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"comm\",\n            \"header\": {\"msg_type\": \"comm\"},\n            \"content\": {\"comm_id\": \"foobar\", \"data\": {\"state\": {\"foo\": \"bar\"}}},\n        }\n    )\n    def test_widget_comm_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # A comm message without buffer info followed by an idle\n        assert message_mock.call_count == 2\n        self.assertEqual(executor.widget_state, {\"foobar\": {\"foo\": \"bar\"}})\n        # Buffers should still be empty\n        assert not executor.widget_buffers\n        # Ensure no outputs were generated\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"comm\",\n            \"header\": {\"msg_type\": \"comm\"},\n            \"buffers\": [b\"123\"],\n            \"content\": {\n                \"comm_id\": \"foobar\",\n                \"data\": {\"state\": {\"foo\": \"bar\"}, \"buffer_paths\": [[\"path\"]]},\n            },\n        }\n    )\n    def test_widget_comm_buffer_message_single(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # A comm message with buffer info followed by an idle\n        assert message_mock.call_count == 2\n        assert executor.widget_state == {\"foobar\": {\"foo\": \"bar\"}}\n        assert executor.widget_buffers == {\n            \"foobar\": {(\"path\",): {\"data\": \"MTIz\", \"encoding\": \"base64\", \"path\": [\"path\"]}}\n        }\n        # Ensure no outputs were generated\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"comm\",\n            \"header\": {\"msg_type\": \"comm\"},\n            \"buffers\": [b\"123\"],\n            \"content\": {\n                \"comm_id\": \"foobar\",\n                \"data\": {\"state\": {\"foo\": \"bar\"}, \"buffer_paths\": [[\"path\"]]},\n            },\n        },\n        {\n            \"msg_type\": \"comm\",\n            \"header\": {\"msg_type\": \"comm\"},\n            \"buffers\": [b\"123\"],\n            \"content\": {\n                \"comm_id\": \"foobar\",\n                \"data\": {\"state\": {\"foo2\": \"bar2\"}, \"buffer_paths\": [[\"path2\"]]},\n            },\n        },\n    )\n    def test_widget_comm_buffer_messages(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # A comm message with buffer info followed by an idle\n        assert message_mock.call_count == 3\n        assert executor.widget_state == {\"foobar\": {\"foo\": \"bar\", \"foo2\": \"bar2\"}}\n        assert executor.widget_buffers == {\n            \"foobar\": {\n                (\"path\",): {\"data\": \"MTIz\", \"encoding\": \"base64\", \"path\": [\"path\"]},\n                (\"path2\",): {\"data\": \"MTIz\", \"encoding\": \"base64\", \"path\": [\"path2\"]},\n            }\n        }\n        # Ensure no outputs were generated\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"comm\",\n            \"header\": {\"msg_type\": \"comm\"},\n            \"content\": {\n                \"comm_id\": \"foobar\",\n                # No 'state'\n                \"data\": {\"foo\": \"bar\"},\n            },\n        }\n    )\n    def test_unknown_comm_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An unknown comm message followed by an idle\n        assert message_mock.call_count == 2\n        # Widget states should be empty as the message has the wrong shape\n        assert not executor.widget_state\n        assert not executor.widget_buffers\n        # Ensure no outputs were generated\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"execute_result\",\n            \"header\": {\"msg_type\": \"execute_result\"},\n            \"content\": {\n                \"metadata\": {\"metafoo\": \"metabar\"},\n                \"data\": {\"foo\": \"bar\"},\n                \"execution_count\": 42,\n            },\n        }\n    )\n    def test_execute_result_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An execute followed by an idle\n        assert message_mock.call_count == 2\n        assert cell_mock.execution_count == 42\n        # Should generate an associated message\n        assert cell_mock.outputs == [\n            {\n                \"output_type\": \"execute_result\",\n                \"metadata\": {\"metafoo\": \"metabar\"},\n                \"data\": {\"foo\": \"bar\"},\n                \"execution_count\": 42,\n            }\n        ]\n        # No display id was provided\n        assert not executor._display_id_map\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"execute_result\",\n            \"header\": {\"msg_type\": \"execute_result\"},\n            \"content\": {\n                \"transient\": {\"display_id\": \"foobar\"},\n                \"metadata\": {\"metafoo\": \"metabar\"},\n                \"data\": {\"foo\": \"bar\"},\n                \"execution_count\": 42,\n            },\n        }\n    )\n    def test_execute_result_with_display_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An execute followed by an idle\n        assert message_mock.call_count == 2\n        assert cell_mock.execution_count == 42\n        # Should generate an associated message\n        assert cell_mock.outputs == [\n            {\n                \"output_type\": \"execute_result\",\n                \"metadata\": {\"metafoo\": \"metabar\"},\n                \"data\": {\"foo\": \"bar\"},\n                \"execution_count\": 42,\n            }\n        ]\n        assert \"foobar\" in executor._display_id_map\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"display_data\",\n            \"header\": {\"msg_type\": \"display_data\"},\n            \"content\": {\"metadata\": {\"metafoo\": \"metabar\"}, \"data\": {\"foo\": \"bar\"}},\n        }\n    )\n    def test_display_data_without_id_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # A display followed by an idle\n        assert message_mock.call_count == 2\n        # Should generate an associated message\n        assert cell_mock.outputs == [\n            {\n                \"output_type\": \"display_data\",\n                \"metadata\": {\"metafoo\": \"metabar\"},\n                \"data\": {\"foo\": \"bar\"},\n            }\n        ]\n        # No display id was provided\n        assert not executor._display_id_map\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"display_data\",\n            \"header\": {\"msg_type\": \"display_data\"},\n            \"content\": {\n                \"transient\": {\"display_id\": \"foobar\"},\n                \"metadata\": {\"metafoo\": \"metabar\"},\n                \"data\": {\"foo\": \"bar\"},\n            },\n        }\n    )\n    def test_display_data_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # A display followed by an idle\n        assert message_mock.call_count == 2\n        # Should generate an associated message\n        assert cell_mock.outputs == [\n            {\n                \"output_type\": \"display_data\",\n                \"metadata\": {\"metafoo\": \"metabar\"},\n                \"data\": {\"foo\": \"bar\"},\n            }\n        ]\n        assert \"foobar\" in executor._display_id_map\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"display_data\",\n            \"header\": {\"msg_type\": \"display_data\"},\n            \"content\": {\n                \"transient\": {\"display_id\": \"foobar\"},\n                \"metadata\": {\"metafoo\": \"metabar\"},\n                \"data\": {\"foo\": \"bar\"},\n            },\n        },\n        {\n            \"msg_type\": \"display_data\",\n            \"header\": {\"msg_type\": \"display_data\"},\n            \"content\": {\n                \"transient\": {\"display_id\": \"foobar_other\"},\n                \"metadata\": {\"metafoo_other\": \"metabar_other\"},\n                \"data\": {\"foo\": \"bar_other\"},\n            },\n        },\n        {\n            \"msg_type\": \"display_data\",\n            \"header\": {\"msg_type\": \"display_data\"},\n            \"content\": {\n                \"transient\": {\"display_id\": \"foobar\"},\n                \"metadata\": {\"metafoo2\": \"metabar2\"},\n                \"data\": {\"foo\": \"bar2\", \"baz\": \"foobarbaz\"},\n            },\n        },\n    )\n    def test_display_data_same_id_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # A display followed by an idle\n        assert message_mock.call_count == 4\n        # Original output should be manipulated and a copy of the second now\n        assert cell_mock.outputs == [\n            {\n                \"output_type\": \"display_data\",\n                \"metadata\": {\"metafoo2\": \"metabar2\"},\n                \"data\": {\"foo\": \"bar2\", \"baz\": \"foobarbaz\"},\n            },\n            {\n                \"output_type\": \"display_data\",\n                \"metadata\": {\"metafoo_other\": \"metabar_other\"},\n                \"data\": {\"foo\": \"bar_other\"},\n            },\n            {\n                \"output_type\": \"display_data\",\n                \"metadata\": {\"metafoo2\": \"metabar2\"},\n                \"data\": {\"foo\": \"bar2\", \"baz\": \"foobarbaz\"},\n            },\n        ]\n        assert \"foobar\" in executor._display_id_map\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"update_display_data\",\n            \"header\": {\"msg_type\": \"update_display_data\"},\n            \"content\": {\"metadata\": {\"metafoo\": \"metabar\"}, \"data\": {\"foo\": \"bar\"}},\n        }\n    )\n    def test_update_display_data_without_id_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An update followed by an idle\n        assert message_mock.call_count == 2\n        # Display updates don't create any outputs\n        assert cell_mock.outputs == []\n        # No display id was provided\n        assert not executor._display_id_map\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"display_data\",\n            \"header\": {\"msg_type\": \"display_data\"},\n            \"content\": {\n                \"transient\": {\"display_id\": \"foobar\"},\n                \"metadata\": {\"metafoo2\": \"metabar2\"},\n                \"data\": {\"foo\": \"bar2\", \"baz\": \"foobarbaz\"},\n            },\n        },\n        {\n            \"msg_type\": \"update_display_data\",\n            \"header\": {\"msg_type\": \"update_display_data\"},\n            \"content\": {\n                \"transient\": {\"display_id\": \"foobar2\"},\n                \"metadata\": {\"metafoo2\": \"metabar2\"},\n                \"data\": {\"foo\": \"bar2\", \"baz\": \"foobarbaz\"},\n            },\n        },\n    )\n    def test_update_display_data_mismatch_id_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An update followed by an idle\n        assert message_mock.call_count == 3\n        # Display updates don't create any outputs\n        assert cell_mock.outputs == [\n            {\n                \"output_type\": \"display_data\",\n                \"metadata\": {\"metafoo2\": \"metabar2\"},\n                \"data\": {\"foo\": \"bar2\", \"baz\": \"foobarbaz\"},\n            }\n        ]\n        assert \"foobar\" in executor._display_id_map\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"display_data\",\n            \"header\": {\"msg_type\": \"display_data\"},\n            \"content\": {\n                \"transient\": {\"display_id\": \"foobar\"},\n                \"metadata\": {\"metafoo\": \"metabar\"},\n                \"data\": {\"foo\": \"bar\"},\n            },\n        },\n        {\n            \"msg_type\": \"update_display_data\",\n            \"header\": {\"msg_type\": \"update_display_data\"},\n            \"content\": {\n                \"transient\": {\"display_id\": \"foobar\"},\n                \"metadata\": {\"metafoo2\": \"metabar2\"},\n                \"data\": {\"foo\": \"bar2\", \"baz\": \"foobarbaz\"},\n            },\n        },\n    )\n    def test_update_display_data_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # A display followed by an update then an idle\n        assert message_mock.call_count == 3\n        # Original output should be manipulated\n        assert cell_mock.outputs == [\n            {\n                \"output_type\": \"display_data\",\n                \"metadata\": {\"metafoo2\": \"metabar2\"},\n                \"data\": {\"foo\": \"bar2\", \"baz\": \"foobarbaz\"},\n            }\n        ]\n        assert \"foobar\" in executor._display_id_map\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"error\",\n            \"header\": {\"msg_type\": \"error\"},\n            \"content\": {\"ename\": \"foo\", \"evalue\": \"bar\", \"traceback\": [\"Boom\"]},\n        }\n    )\n    def test_error_message(self, executor, cell_mock, message_mock):\n        executor.execute_cell(cell_mock, 0)\n        # An error followed by an idle\n        assert message_mock.call_count == 2\n        # Should also consume the message stream\n        assert cell_mock.outputs == [\n            {\"output_type\": \"error\", \"ename\": \"foo\", \"evalue\": \"bar\", \"traceback\": [\"Boom\"]}\n        ]\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"error\",\n            \"header\": {\"msg_type\": \"error\"},\n            \"content\": {\"ename\": \"foo\", \"evalue\": \"bar\", \"traceback\": [\"Boom\"]},\n        },\n        reply_msg={\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            # ERROR\n            \"content\": {\"status\": \"error\"},\n        },\n    )\n    def test_error_and_error_status_messages(self, executor, cell_mock, message_mock):\n        with self.assertRaises(CellExecutionError):\n            executor.execute_cell(cell_mock, 0)\n\n        # An error followed by an idle\n        assert message_mock.call_count == 2\n        # Cell outputs should still be copied\n        assert cell_mock.outputs == [\n            {\"output_type\": \"error\", \"ename\": \"foo\", \"evalue\": \"bar\", \"traceback\": [\"Boom\"]}\n        ]\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"error\",\n            \"header\": {\"msg_type\": \"error\"},\n            \"content\": {\"ename\": \"foo\", \"evalue\": \"bar\", \"traceback\": [\"Boom\"]},\n        },\n        reply_msg={\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            # OK\n            \"content\": {\"status\": \"ok\"},\n        },\n    )\n    def test_error_message_only(self, executor, cell_mock, message_mock):\n        # Should NOT raise\n        executor.execute_cell(cell_mock, 0)\n\n        # An error followed by an idle\n        assert message_mock.call_count == 2\n        # Should also consume the message stream\n        assert cell_mock.outputs == [\n            {\"output_type\": \"error\", \"ename\": \"foo\", \"evalue\": \"bar\", \"traceback\": [\"Boom\"]}\n        ]\n\n    @prepare_cell_mocks(\n        reply_msg={\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            # ERROR\n            \"content\": {\"status\": \"error\"},\n        }\n    )\n    def test_allow_errors(self, executor, cell_mock, message_mock):\n        executor.allow_errors = True\n        # Should NOT raise\n        executor.execute_cell(cell_mock, 0)\n\n        # An error followed by an idle\n        assert message_mock.call_count == 1\n        # Should also consume the message stream\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        reply_msg={\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            # ERROR\n            \"content\": {\"status\": \"error\", \"ename\": \"NotImplementedError\"},\n        }\n    )\n    def test_allow_error_names(self, executor, cell_mock, message_mock):\n        executor.allow_error_names = [\"NotImplementedError\"]\n        # Should NOT raise\n        executor.execute_cell(cell_mock, 0)\n\n        # An error followed by an idle\n        assert message_mock.call_count == 1\n        # Should also consume the message stream\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        reply_msg={\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            # ERROR\n            \"content\": {\"status\": \"error\"},\n        }\n    )\n    def test_raises_exception_tag(self, executor, cell_mock, message_mock):\n        cell_mock.metadata[\"tags\"] = [\"raises-exception\"]\n        # Should NOT raise\n        executor.execute_cell(cell_mock, 0)\n\n        # An error followed by an idle\n        assert message_mock.call_count == 1\n        # Should also consume the message stream\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        reply_msg={\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            # ERROR\n            \"content\": {\"status\": \"error\"},\n        }\n    )\n    def test_non_code_cell(self, executor, cell_mock, message_mock):\n        cell_mock = NotebookNode(source='\"foo\" = \"bar\"', metadata={}, cell_type=\"raw\", outputs=[])\n        # Should NOT raise nor execute any code\n        executor.execute_cell(cell_mock, 0)\n\n        # An error followed by an idle\n        assert message_mock.call_count == 0\n        # Should also consume the message stream\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks(\n        reply_msg={\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            # ERROR\n            \"content\": {\"status\": \"error\"},\n        }\n    )\n    def test_no_source(self, executor, cell_mock, message_mock):\n        cell_mock = NotebookNode(\n            # Stripped source is empty\n            source=\"     \",\n            metadata={},\n            cell_type=\"code\",\n            outputs=[],\n        )\n        # Should NOT raise nor execute any code\n        executor.execute_cell(cell_mock, 0)\n\n        # An error followed by an idle\n        assert message_mock.call_count == 0\n        # Should also consume the message stream\n        assert cell_mock.outputs == []\n\n    @prepare_cell_mocks()\n    def test_cell_hooks(self, executor, cell_mock, message_mock):\n        executor, hooks = get_executor_with_hooks(executor=executor)\n        executor.execute_cell(cell_mock, 0)\n        hooks[\"on_cell_start\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_execute\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_complete\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_executed\"].assert_called_once_with(\n            cell=cell_mock, cell_index=0, execute_reply=EXECUTE_REPLY_OK\n        )\n        hooks[\"on_cell_error\"].assert_not_called()\n        hooks[\"on_notebook_start\"].assert_not_called()\n        hooks[\"on_notebook_complete\"].assert_not_called()\n        hooks[\"on_notebook_error\"].assert_not_called()\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"error\",\n            \"header\": {\"msg_type\": \"error\"},\n            \"content\": {\"ename\": \"foo\", \"evalue\": \"bar\", \"traceback\": [\"Boom\"]},\n        },\n        reply_msg={\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            # ERROR\n            \"content\": {\"status\": \"error\"},\n        },\n    )\n    def test_error_cell_hooks(self, executor, cell_mock, message_mock):\n        executor, hooks = get_executor_with_hooks(executor=executor)\n        with self.assertRaises(CellExecutionError):\n            executor.execute_cell(cell_mock, 0)\n        hooks[\"on_cell_start\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_execute\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_complete\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_executed\"].assert_called_once_with(\n            cell=cell_mock, cell_index=0, execute_reply=EXECUTE_REPLY_ERROR\n        )\n        hooks[\"on_cell_error\"].assert_called_once_with(\n            cell=cell_mock, cell_index=0, execute_reply=EXECUTE_REPLY_ERROR\n        )\n        hooks[\"on_notebook_start\"].assert_not_called()\n        hooks[\"on_notebook_complete\"].assert_not_called()\n        hooks[\"on_notebook_error\"].assert_not_called()\n\n    @prepare_cell_mocks(\n        reply_msg={\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            # ERROR\n            \"content\": {\"status\": \"error\"},\n        }\n    )\n    def test_non_code_cell_hooks(self, executor, cell_mock, message_mock):\n        cell_mock = NotebookNode(source='\"foo\" = \"bar\"', metadata={}, cell_type=\"raw\", outputs=[])\n        executor, hooks = get_executor_with_hooks(executor=executor)\n        executor.execute_cell(cell_mock, 0)\n        hooks[\"on_cell_start\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_execute\"].assert_not_called()\n        hooks[\"on_cell_complete\"].assert_not_called()\n        hooks[\"on_cell_executed\"].assert_not_called()\n        hooks[\"on_cell_error\"].assert_not_called()\n        hooks[\"on_notebook_start\"].assert_not_called()\n        hooks[\"on_notebook_complete\"].assert_not_called()\n        hooks[\"on_notebook_error\"].assert_not_called()\n\n    @prepare_cell_mocks()\n    def test_async_cell_hooks(self, executor, cell_mock, message_mock):\n        executor, hooks = get_executor_with_hooks(executor=executor, async_hooks=True)\n        executor.execute_cell(cell_mock, 0)\n        hooks[\"on_cell_start\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_execute\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_complete\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_executed\"].assert_called_once_with(\n            cell=cell_mock, cell_index=0, execute_reply=EXECUTE_REPLY_OK\n        )\n        hooks[\"on_cell_error\"].assert_not_called()\n        hooks[\"on_notebook_start\"].assert_not_called()\n        hooks[\"on_notebook_complete\"].assert_not_called()\n        hooks[\"on_notebook_error\"].assert_not_called()\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"error\",\n            \"header\": {\"msg_type\": \"error\"},\n            \"content\": {\"ename\": \"foo\", \"evalue\": \"bar\", \"traceback\": [\"Boom\"]},\n        },\n        reply_msg={\n            \"msg_type\": \"execute_reply\",\n            \"header\": {\"msg_type\": \"execute_reply\"},\n            # ERROR\n            \"content\": {\"status\": \"error\"},\n        },\n    )\n    def test_error_async_cell_hooks(self, executor, cell_mock, message_mock):\n        executor, hooks = get_executor_with_hooks(executor=executor, async_hooks=True)\n        with self.assertRaises(CellExecutionError):\n            executor.execute_cell(cell_mock, 0)\n        hooks[\"on_cell_start\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_execute\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_complete\"].assert_called_once_with(cell=cell_mock, cell_index=0)\n        hooks[\"on_cell_executed\"].assert_called_once_with(\n            cell=cell_mock, cell_index=0, execute_reply=EXECUTE_REPLY_ERROR\n        )\n        hooks[\"on_cell_error\"].assert_called_once_with(\n            cell=cell_mock, cell_index=0, execute_reply=EXECUTE_REPLY_ERROR\n        )\n        hooks[\"on_notebook_start\"].assert_not_called()\n        hooks[\"on_notebook_complete\"].assert_not_called()\n        hooks[\"on_notebook_error\"].assert_not_called()\n\n    @prepare_cell_mocks(\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stdout\", \"text\": \"foo1\"},\n        },\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stderr\", \"text\": \"bar1\"},\n        },\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stdout\", \"text\": \"foo2\"},\n        },\n        {\n            \"msg_type\": \"stream\",\n            \"header\": {\"msg_type\": \"stream\"},\n            \"content\": {\"name\": \"stderr\", \"text\": \"bar2\"},\n        },\n    )\n    def test_coalesce_streams(self, executor, cell_mock, message_mock):\n        executor.coalesce_streams = True\n        executor.execute_cell(cell_mock, 0)\n\n        assert cell_mock.outputs == [\n            {\"output_type\": \"stream\", \"name\": \"stdout\", \"text\": \"foo1foo2\"},\n            {\"output_type\": \"stream\", \"name\": \"stderr\", \"text\": \"bar1bar2\"},\n        ]\n"
  },
  {
    "path": "tests/test_util.py",
    "content": "import asyncio\nfrom unittest.mock import MagicMock\n\nimport pytest\nimport tornado\n\nfrom nbclient.util import run_hook, run_sync\n\n# mypy: disable-error-code=\"no-untyped-call,no-untyped-def\"\n\n\n@run_sync\nasync def some_async_function():\n    await asyncio.sleep(0.01)\n    return 42\n\n\ndef test_nested_asyncio_with_existing_ioloop():\n    async def _test():\n        assert some_async_function() == 42\n        return asyncio.get_running_loop()\n\n    loop = asyncio.new_event_loop()\n    asyncio.set_event_loop(loop)\n    event_loop = loop.run_until_complete(_test())\n    assert event_loop is loop\n\n\ndef test_nested_asyncio_with_no_ioloop():\n    asyncio.set_event_loop(None)\n    assert some_async_function() == 42\n\n\ndef test_nested_asyncio_with_tornado():\n    # This tests if tornado accepts the pure-Python Futures, see\n    # https://github.com/tornadoweb/tornado/issues/2753\n    asyncio.set_event_loop(asyncio.new_event_loop())\n    ioloop = tornado.ioloop.IOLoop.current()\n\n    async def some_async_function():\n        future: asyncio.Future[None] = asyncio.ensure_future(asyncio.sleep(0.1))\n        # the asyncio module, check if tornado likes it:\n        ioloop.add_future(future, lambda f: f.result())\n        await future\n        return 42\n\n    def some_sync_function():\n        return run_sync(some_async_function)()\n\n    async def run():\n        # calling some_async_function directly should work\n        assert await some_async_function() == 42\n        assert some_sync_function() == 42\n\n    ioloop.run_sync(run)\n\n\n@pytest.mark.asyncio\nasync def test_run_hook_sync():\n    some_sync_function = MagicMock()\n    await run_hook(some_sync_function)\n    assert some_sync_function.call_count == 1\n\n\n@pytest.mark.asyncio\nasync def test_run_hook_async():\n    hook = MagicMock(return_value=some_async_function())\n    await run_hook(hook)\n    assert hook.call_count == 1\n"
  }
]