├── .github └── workflows │ └── python-package.yml ├── .gitignore ├── .mailmap ├── .readthedocs.yaml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASING.md ├── docs ├── Makefile ├── autogen_config.py ├── conf.py ├── environment.yml ├── index.rst ├── make.bat ├── release.rst └── requirements.txt ├── jupyter_console ├── __init__.py ├── __main__.py ├── _version.py ├── app.py ├── completer.py ├── ptshell.py ├── tests │ ├── __init__.py │ ├── conftest.py │ ├── test_console.py │ ├── test_image_handler.py │ └── writetofile.py ├── utils.py └── zmqhistory.py ├── pyproject.toml └── scripts └── jupyter-console /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | defaults: 13 | run: 14 | shell: bash -eux {0} 15 | 16 | jobs: 17 | build: 18 | 19 | runs-on: ubuntu-latest 20 | strategy: 21 | matrix: 22 | python-version: ['3.7', '3.8', '3.9', '3.10', '3.11'] 23 | fail-fast: false 24 | 25 | steps: 26 | - uses: actions/checkout@v2 27 | - name: Set up Python ${{ matrix.python-version }} 28 | uses: actions/setup-python@v2 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | - name: Install dependencies 32 | run: | 33 | python -m pip install --upgrade pip check-manifest 34 | pip install pytest-cov 35 | pip install ".[test]" 36 | python -m ipykernel.kernelspec --user 37 | - name: Test with pytest 38 | run: | 39 | pytest --cov jupyter_console || pytest jupyter_console --lf 40 | 41 | lint: 42 | runs-on: ubuntu-latest 43 | steps: 44 | - uses: actions/checkout@v2 45 | - uses: actions/setup-python@v2 46 | - name: Linting 47 | run: | 48 | pip install Flake8-pyproject mypy 49 | mypy jupyter_console 50 | flake8 jupyter_console 51 | 52 | check_release: 53 | runs-on: ubuntu-latest 54 | steps: 55 | - uses: actions/checkout@v3 56 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 57 | - uses: jupyter-server/jupyter_releaser/.github/actions/check-release@v2 58 | with: 59 | token: ${{ secrets.GITHUB_TOKEN }} 60 | 61 | check_links: 62 | runs-on: ubuntu-latest 63 | steps: 64 | - uses: actions/checkout@v3 65 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 66 | - uses: jupyterlab/maintainer-tools/.github/actions/check-links@v1 67 | 68 | test_minimum_versions: 69 | name: Test Minimum Versions 70 | runs-on: ubuntu-latest 71 | timeout-minutes: 10 72 | steps: 73 | - uses: actions/checkout@v3 74 | - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 75 | with: 76 | dependency_type: minimum 77 | - name: Run the unit tests 78 | run: | 79 | pip install ".[test]" 80 | pytest || pytest --lf 81 | 82 | tests_check: # This job does nothing and is only used for the branch protection 83 | if: always() 84 | needs: 85 | - build 86 | - lint 87 | - check_release 88 | - check_links 89 | - test_minimum_versions 90 | runs-on: ubuntu-latest 91 | steps: 92 | - name: Decide whether the needed jobs succeeded or failed 93 | uses: re-actors/alls-green@release/v1 94 | with: 95 | jobs: ${{ toJSON(needs) }} 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | build 3 | dist 4 | _build 5 | docs/gh-pages 6 | docs/config_options.rst 7 | docs/changelog.md 8 | *.py[co] 9 | __pycache__ 10 | *.egg-info 11 | *~ 12 | *.bak 13 | .ipynb_checkpoints 14 | .tox 15 | .DS_Store 16 | \#*# 17 | .#* 18 | .coverage 19 | # PyCharm project cache 20 | .idea -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | A. J. Holyoake ajholyoake 2 | Aaron Culich Aaron Culich 3 | Aron Ahmadia ahmadia 4 | Benjamin Ragan-Kelley 5 | Benjamin Ragan-Kelley Min RK 6 | Benjamin Ragan-Kelley MinRK 7 | Barry Wark Barry Wark 8 | Ben Edwards Ben Edwards 9 | Bradley M. Froehle Bradley M. Froehle 10 | Bradley M. Froehle Bradley Froehle 11 | Brandon Parsons Brandon Parsons 12 | Brian E. Granger Brian Granger 13 | Brian E. Granger Brian Granger <> 14 | Brian E. Granger bgranger <> 15 | Brian E. Granger bgranger 16 | Christoph Gohlke cgohlke 17 | Cyrille Rossant rossant 18 | Damián Avila damianavila 19 | Damián Avila damianavila 20 | Damon Allen damontallen 21 | Darren Dale darren.dale <> 22 | Darren Dale Darren Dale <> 23 | Dav Clark Dav Clark <> 24 | Dav Clark Dav Clark 25 | David Hirschfeld dhirschfeld 26 | David P. Sanders David P. Sanders 27 | David Warde-Farley David Warde-Farley <> 28 | Doug Blank Doug Blank 29 | Eugene Van den Bulke Eugene Van den Bulke 30 | Evan Patterson 31 | Evan Patterson 32 | Evan Patterson 33 | Evan Patterson 34 | Evan Patterson epatters 35 | Evan Patterson epatters 36 | Ernie French Ernie French 37 | Ernie French ernie french 38 | Ernie French ernop 39 | Fernando Perez 40 | Fernando Perez Fernando Perez 41 | Fernando Perez fperez <> 42 | Fernando Perez fptest <> 43 | Fernando Perez fptest1 <> 44 | Fernando Perez Fernando Perez 45 | Fernando Perez Fernando Perez <> 46 | Fernando Perez Fernando Perez 47 | Frank Murphy Frank Murphy 48 | Gabriel Becker gmbecker 49 | Gael Varoquaux gael.varoquaux <> 50 | Gael Varoquaux gvaroquaux 51 | Gael Varoquaux Gael Varoquaux <> 52 | Ingolf Becker watercrossing 53 | Jake Vanderplas Jake Vanderplas 54 | Jakob Gager jakobgager 55 | Jakob Gager jakobgager 56 | Jakob Gager jakobgager 57 | Jason Grout 58 | Jason Grout 59 | Jason Gors jason gors 60 | Jason Gors jgors 61 | Jens Hedegaard Nielsen Jens Hedegaard Nielsen 62 | Jens Hedegaard Nielsen Jens H Nielsen 63 | Jens Hedegaard Nielsen Jens H. Nielsen 64 | Jez Ng Jez Ng 65 | Jonathan Frederic Jonathan Frederic 66 | Jonathan Frederic Jonathan Frederic 67 | Jonathan Frederic Jonathan Frederic 68 | Jonathan Frederic jon 69 | Jonathan Frederic U-Jon-PC\Jon 70 | Jonathan March Jonathan March 71 | Jonathan March jdmarch 72 | Jörgen Stenarson Jörgen Stenarson 73 | Jörgen Stenarson Jorgen Stenarson 74 | Jörgen Stenarson Jorgen Stenarson <> 75 | Jörgen Stenarson jstenar 76 | Jörgen Stenarson jstenar <> 77 | Jörgen Stenarson Jörgen Stenarson 78 | Juergen Hasch juhasch 79 | Juergen Hasch juhasch 80 | Julia Evans Julia Evans 81 | Kester Tong KesterTong 82 | Kyle Kelley Kyle Kelley 83 | Kyle Kelley rgbkrk 84 | Laurent Dufréchou 85 | Laurent Dufréchou 86 | Laurent Dufréchou laurent dufrechou <> 87 | Laurent Dufréchou laurent.dufrechou <> 88 | Laurent Dufréchou Laurent Dufrechou <> 89 | Laurent Dufréchou laurent.dufrechou@gmail.com <> 90 | Laurent Dufréchou ldufrechou 91 | Lorena Pantano Lorena 92 | Luis Pedro Coelho Luis Pedro Coelho 93 | Marc Molla marcmolla 94 | Martín Gaitán Martín Gaitán 95 | Matthias Bussonnier Matthias BUSSONNIER 96 | Matthias Bussonnier Bussonnier Matthias 97 | Matthias Bussonnier Matthias BUSSONNIER 98 | Matthias Bussonnier Matthias Bussonnier 99 | Michael Droettboom Michael Droettboom 100 | Nicholas Bollweg Nicholas Bollweg (Nick) 101 | Nicolas Rougier 102 | Nikolay Koldunov Nikolay Koldunov 103 | Omar Andrés Zapata Mesa Omar Andres Zapata Mesa 104 | Omar Andrés Zapata Mesa Omar Andres Zapata Mesa 105 | Pankaj Pandey Pankaj Pandey 106 | Pascal Schetelat pascal-schetelat 107 | Paul Ivanov Paul Ivanov 108 | Pauli Virtanen Pauli Virtanen <> 109 | Pauli Virtanen Pauli Virtanen 110 | Pierre Gerold Pierre Gerold 111 | Pietro Berkes Pietro Berkes 112 | Piti Ongmongkolkul piti118 113 | Prabhu Ramachandran Prabhu Ramachandran <> 114 | Puneeth Chaganti Puneeth Chaganti 115 | Robert Kern rkern <> 116 | Robert Kern Robert Kern 117 | Robert Kern Robert Kern 118 | Robert Kern Robert Kern <> 119 | Robert Marchman Robert Marchman 120 | Satrajit Ghosh Satrajit Ghosh 121 | Satrajit Ghosh Satrajit Ghosh 122 | Scott Sanderson Scott Sanderson 123 | smithj1 smithj1 124 | smithj1 smithj1 125 | Steven Johnson stevenJohnson 126 | Steven Silvester blink1073 127 | S. Weber s8weber 128 | Stefan van der Walt Stefan van der Walt 129 | Silvia Vinyes Silvia 130 | Silvia Vinyes silviav12 131 | Sylvain Corlay 132 | Sylvain Corlay sylvain.corlay 133 | Ted Drain TD22057 134 | Théophile Studer Théophile Studer 135 | Thomas Kluyver Thomas 136 | Thomas Spura Thomas Spura 137 | Timo Paulssen timo 138 | vds vds2212 139 | vds vds 140 | Ville M. Vainio 141 | Ville M. Vainio ville 142 | Ville M. Vainio ville 143 | Ville M. Vainio vivainio <> 144 | Ville M. Vainio Ville M. Vainio 145 | Ville M. Vainio Ville M. Vainio 146 | Walter Doerwald walter.doerwald <> 147 | Walter Doerwald Walter Doerwald <> 148 | W. Trevor King W. Trevor King 149 | Yoval P. y-p 150 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | conda: 2 | file: docs/environment.yml 3 | python: 4 | version: 3 5 | setup_py_install: true 6 | pip install: true 7 | formats: 8 | - epub 9 | - pdf 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changes in Jupyter Console {#changelog} 2 | 3 | 4 | 5 | ## 6.6.3 6 | 7 | ([Full Changelog](https://github.com/jupyter/jupyter_console/compare/v6.6.2...2c444cbd51c4a4ae8b2bd81b654687bb1fefa802)) 8 | 9 | ### Bugs fixed 10 | 11 | - Fix handle_external_iopub again [#286](https://github.com/jupyter/jupyter_console/pull/286) ([@blink1073](https://github.com/blink1073)) 12 | 13 | ### Contributors to this release 14 | 15 | ([GitHub contributors page for this release](https://github.com/jupyter/jupyter_console/graphs/contributors?from=2023-02-27&to=2023-03-06&type=c)) 16 | 17 | [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Ablink1073+updated%3A2023-02-27..2023-03-06&type=Issues) 18 | 19 | 20 | 21 | ## 6.6.2 22 | 23 | ([Full Changelog](https://github.com/jupyter/jupyter_console/compare/v6.6.1...27b0ffeb4e71f317bcd1ad2d3af41e0458bd05d4)) 24 | 25 | ### Bugs fixed 26 | 27 | - Fix handle_external_iopub [#285](https://github.com/jupyter/jupyter_console/pull/285) ([@blink1073](https://github.com/blink1073)) 28 | 29 | ### Contributors to this release 30 | 31 | ([GitHub contributors page for this release](https://github.com/jupyter/jupyter_console/graphs/contributors?from=2023-02-21&to=2023-02-27&type=c)) 32 | 33 | [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Ablink1073+updated%3A2023-02-21..2023-02-27&type=Issues) 34 | 35 | ## 6.6.1 36 | 37 | ([Full Changelog](https://github.com/jupyter/jupyter_console/compare/v6.6.0...0108eacfd6b7ff8bf6c6ef4d8d95a53df86d430b)) 38 | 39 | ### Maintenance and upkeep improvements 40 | 41 | - More build system cleanup [#282](https://github.com/jupyter/jupyter_console/pull/282) ([@blink1073](https://github.com/blink1073)) 42 | 43 | ### Contributors to this release 44 | 45 | ([GitHub contributors page for this release](https://github.com/jupyter/jupyter_console/graphs/contributors?from=2023-02-20&to=2023-02-21&type=c)) 46 | 47 | [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Ablink1073+updated%3A2023-02-20..2023-02-21&type=Issues) 48 | 49 | ## 6.6.0 50 | 51 | ([Full Changelog](https://github.com/jupyter/jupyter_console/compare/v6.5.1...7a6bbfecac9c34e9a6b26eae6c018cee7622b403)) 52 | 53 | ### Maintenance and upkeep improvements 54 | 55 | - Switch to hatch backend [#281](https://github.com/jupyter/jupyter_console/pull/281) ([@blink1073](https://github.com/blink1073)) 56 | - Add flaky [#280](https://github.com/jupyter/jupyter_console/pull/280) ([@blink1073](https://github.com/blink1073)) 57 | - Clean up license [#279](https://github.com/jupyter/jupyter_console/pull/279) ([@dcsaba89](https://github.com/dcsaba89)) 58 | 59 | ### Contributors to this release 60 | 61 | ([GitHub contributors page for this release](https://github.com/jupyter/jupyter_console/graphs/contributors?from=2023-02-13&to=2023-02-20&type=c)) 62 | 63 | [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Ablink1073+updated%3A2023-02-13..2023-02-20&type=Issues) | [@dcsaba89](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Adcsaba89+updated%3A2023-02-13..2023-02-20&type=Issues) 64 | 65 | ## 6.5.1 66 | 67 | ([Full Changelog](https://github.com/jupyter/jupyter_console/compare/v6.5.0...25fe1d530cefe22596fc2aa9694cdcded14c0af3)) 68 | 69 | ### Bugs fixed 70 | 71 | - Fix completion handling [#278](https://github.com/jupyter/jupyter_console/pull/278) ([@blink1073](https://github.com/blink1073)) 72 | 73 | ### Contributors to this release 74 | 75 | ([GitHub contributors page for this release](https://github.com/jupyter/jupyter_console/graphs/contributors?from=2023-02-09&to=2023-02-13&type=c)) 76 | 77 | [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Ablink1073+updated%3A2023-02-09..2023-02-13&type=Issues) 78 | 79 | ## 6.5.0 80 | 81 | ([Full Changelog](https://github.com/jupyter/jupyter_console/compare/v6.4.4...7bcb1c61c709d033d5b24ecaea3cc6161ff69f5a)) 82 | 83 | ### Bugs fixed 84 | 85 | - Fix client 7 and 8 compat [#276](https://github.com/jupyter/jupyter_console/pull/276) ([@blink1073](https://github.com/blink1073)) 86 | 87 | ### Contributors to this release 88 | 89 | ([GitHub contributors page for this release](https://github.com/jupyter/jupyter_console/graphs/contributors?from=2022-06-22&to=2023-02-09&type=c)) 90 | 91 | [@blink1073](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Ablink1073+updated%3A2022-06-22..2023-02-09&type=Issues) 92 | 93 | ## 6.4.4 94 | 95 | ([Full Changelog](https://github.com/jupyter/jupyter_console/compare/v6.4.3...18cb350dc05c903d541f30de18fcf53943ec0e3f)) 96 | 97 | ### Merged PRs 98 | 99 | - Use asyncio.create_task and asyncio.get_running_loop with interact(). Drop Python 3.6. [#270](https://github.com/jupyter/jupyter_console/pull/270) ([@encukou](https://github.com/encukou)) 100 | 101 | ### Contributors to this release 102 | 103 | ([GitHub contributors page for this release](https://github.com/jupyter/jupyter_console/graphs/contributors?from=2022-03-07&to=2022-06-22&type=c)) 104 | 105 | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Adavidbrochart+updated%3A2022-03-07..2022-06-22&type=Issues) | [@encukou](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Aencukou+updated%3A2022-03-07..2022-06-22&type=Issues) 106 | 107 | ## 6.4.3 108 | 109 | ([Full Changelog](https://github.com/jupyter/jupyter_console/compare/v6.4.2...6e8f29e0a90804badda75c60c5eb50046544eb49)) 110 | 111 | ### Merged PRs 112 | 113 | - Require jupyter_client>=7.0.0 [#266](https://github.com/jupyter/jupyter_console/pull/266) ([@davidbrochart](https://github.com/davidbrochart)) 114 | 115 | ### Contributors to this release 116 | 117 | ([GitHub contributors page for this release](https://github.com/jupyter/jupyter_console/graphs/contributors?from=2022-03-06&to=2022-03-07&type=c)) 118 | 119 | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Adavidbrochart+updated%3A2022-03-06..2022-03-07&type=Issues) 120 | 121 | ## 6.4.2 122 | 123 | ([Full Changelog](https://github.com/jupyter/jupyter_console/compare/v6.4.1...b3ff8fcd24fe22dfbd66518dc8e6a646f460a671)) 124 | 125 | ### Merged PRs 126 | 127 | - Don't pass loop to asyncio.wait() [#264](https://github.com/jupyter/jupyter_console/pull/264) ([@davidbrochart](https://github.com/davidbrochart)) 128 | 129 | ### Contributors to this release 130 | 131 | ([GitHub contributors page for this release](https://github.com/jupyter/jupyter_console/graphs/contributors?from=2022-03-06&to=2022-03-06&type=c)) 132 | 133 | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Adavidbrochart+updated%3A2022-03-06..2022-03-06&type=Issues) 134 | 135 | ## 6.4.1 136 | 137 | ([Full Changelog](https://github.com/jupyter/jupyter_console/compare/6.4.0...2d0b6aec59bde7499995d929ded4d23d7bb585f6)) 138 | 139 | ### Merged PRs 140 | 141 | - Prepare for use with Jupyter Releaser [#261](https://github.com/jupyter/jupyter_console/pull/261) ([@davidbrochart](https://github.com/davidbrochart)) 142 | 143 | ### Contributors to this release 144 | 145 | ([GitHub contributors page for this release](https://github.com/jupyter/jupyter_console/graphs/contributors?from=2021-03-24&to=2022-03-06&type=c)) 146 | 147 | [@davidbrochart](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Adavidbrochart+updated%3A2021-03-24..2022-03-06&type=Issues) | [@emuccino](https://github.com/search?q=repo%3Ajupyter%2Fjupyter_console+involves%3Aemuccino+updated%3A2021-03-24..2022-03-06&type=Issues) 148 | 149 | ## 6.4.0 150 | 151 | ## 5.3.0 152 | 153 | - Highlight matching parentheses [#147](https://github.com/jupyter/jupyter_console/pull/147) 154 | - The config option `JupyterConsoleApp.confirm_exit` replaces `ZMQTerminalInteractiveShell.confirm_exit`, to avoid redundancy [#141](https://github.com/jupyter/jupyter_console/pull/141) 155 | 156 | ## 5.2 157 | 158 | - When using a kernel that the console did not start, exiting with Ctrl-D now leaves it running [#127](https://github.com/jupyter/jupyter_console/pull/127) 159 | - Added Ctrl-\\ shortcut to quit the console [#130](https://github.com/jupyter/jupyter_console/pull/130) 160 | - Input prompt numbers are now updated when another frontend has executed code in the same kernel [#119](https://github.com/jupyter/jupyter_console/pull/119) 161 | - Fix setting next input with newer versions of prompt_toolkit [#123](https://github.com/jupyter/jupyter_console/pull/123) 162 | - Ensure history entries are unicode, not bytes, on Python 2 [#122](https://github.com/jupyter/jupyter_console/pull/122) 163 | 164 | ## 5.1 165 | 166 | - New `ZMQTerminalInteractiveShell.true_color` config option to use 24-bit colour 167 | - New `ZMQTerminalInteractiveShell.confirm_exit` config option to turn off asking 'are you sure' on exit 168 | - New `--simple-prompt` flag to explicitly use the fallback mode without prompt_toolkit 169 | - Fixed executing an empty input 170 | - Fixed formatting for code and outputs from other frontends executing code 171 | - Avoid using functions which will be removed in IPython 6 172 | 173 | ## 5.0 174 | 175 | ## 5.0.0 176 | 177 | ### Interactive Shell architecture 178 | 179 | - Disinherit shell class from IPython Interactive Shell. This separates jupyter_console's `ZMQTerminalInteractiveShell` from IPython's `TerminalInteractiveShell` and `InteractiveShell` classes [#68](https://github.com/jupyter/jupyter_console/pull/68) 180 | - Update SIGINT handler to not use the old interactive API shell [#80](https://github.com/jupyter/jupyter_console/pull/80) 181 | 182 | ### Image Handling improvement 183 | 184 | - use PIL as default image handler [#79](https://github.com/jupyter/jupyter_console/pull/79) 185 | - better indication of whether image data was handled [#77](https://github.com/jupyter/jupyter_console/pull/77) 186 | 187 | ### Prompts improvement 188 | 189 | - use prompt_toolkit 1.0 [#74](https://github.com/jupyter/jupyter_console/pull/74) 190 | - don't use prompt_manager [#75](https://github.com/jupyter/jupyter_console/pull/75) 191 | - remove `colors_force` flag that have no effects [#88](https://github.com/jupyter/jupyter_console/pull/88) 192 | 193 | ## 4.1 194 | 195 | ## 4.1.1 196 | 197 | - fix for readline history 198 | - don't confuse sys.path with virtualenvs 199 | 200 | ## 4.1.0 201 | 202 | - readline/completion fixes 203 | - use is_complete messages to determine if input is complete (important for non-Python kernels) 204 | - fix: 4.0 was looking for jupyter_console_config in IPython config directories, not Jupyter 205 | 206 | ## 4.0 207 | 208 | ## 4.0.3 209 | 210 | - fix `jupyter console --generate-config` 211 | 212 | ## 4.0.2 213 | 214 | - setuptools fixes for Windows 215 | 216 | ## 4.0.0 217 | 218 | - First release as a standalone package. 219 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We follow the [IPython Contributing Guide](https://github.com/ipython/ipython/blob/master/CONTRIBUTING.md). 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | - Copyright (c) 2001-2015, IPython Development Team 4 | - Copyright (c) 2015-, Jupyter Development Team 5 | 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | 2. Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | 3. Neither the name of the copyright holder nor the names of its 19 | contributors may be used to endorse or promote products derived from 20 | this software without specific prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 23 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 24 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 26 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 27 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 28 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 29 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 30 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jupyter Console 2 | [![Build Status](https://travis-ci.org/jupyter/jupyter_console.svg?branch=master)](https://travis-ci.org/jupyter/jupyter_console) 3 | [![Documentation Status](http://readthedocs.org/projects/jupyter-console/badge/?version=latest)](https://jupyter-console.readthedocs.io/en/latest/?badge=latest) 4 | 5 | A terminal-based console frontend for Jupyter kernels. 6 | This code is based on the single-process IPython terminal. 7 | 8 | Install with pip: 9 | 10 | pip install jupyter-console 11 | 12 | Install with conda: 13 | 14 | conda install -c conda-forge jupyter_console 15 | 16 | Start: 17 | 18 | jupyter console 19 | 20 | Help: 21 | 22 | jupyter console -h 23 | 24 | Jupyter Console allows for console-based interaction with non-python 25 | Jupyter kernels such as IJulia, IRKernel. 26 | 27 | To start the console with a particular kernel, ask for it by name:: 28 | 29 | jupyter console --kernel=julia-0.4 30 | 31 | A list of available kernels can be seen with:: 32 | 33 | jupyter kernelspec list 34 | 35 | 36 | ### Release build: 37 | 38 | ```bash 39 | $ pip install pep517 40 | $ python -m pep517.build . 41 | ``` 42 | 43 | 44 | ## Resources 45 | - [Project Jupyter website](https://jupyter.org) 46 | - [Documentation for Jupyter Console](https://jupyter-console.readthedocs.io/en/latest/) [[PDF](https://media.readthedocs.org/pdf/jupyter-console/latest/jupyter-console.pdf)] 47 | - [Documentation for Project Jupyter](https://jupyter.readthedocs.io/en/latest/index.html) [[PDF](https://media.readthedocs.org/pdf/jupyter/latest/jupyter.pdf)] 48 | - [Issues](https://github.com/jupyter/jupyter_console/issues) 49 | - [Technical support - Jupyter Google Group](https://groups.google.com/forum/#!forum/jupyter) 50 | 51 | ## About the Jupyter Development Team 52 | 53 | The Jupyter Development Team is the set of all contributors to the Jupyter project. 54 | This includes all of the Jupyter subprojects. 55 | 56 | The core team that coordinates development on GitHub can be found here: 57 | https://github.com/jupyter/. 58 | 59 | ## Our Copyright Policy 60 | 61 | Jupyter uses a shared copyright model. Each contributor maintains copyright 62 | over their contributions to Jupyter. But, it is important to note that these 63 | contributions are typically only changes to the repositories. Thus, the Jupyter 64 | source code, in its entirety is not the copyright of any single person or 65 | institution. Instead, it is the collective copyright of the entire Jupyter 66 | Development Team. If individual contributors want to maintain a record of what 67 | changes/contributions they have specific copyright on, they should indicate 68 | their copyright in the commit message of the change, when they commit the 69 | change to one of the Jupyter repositories. 70 | 71 | With this in mind, the following banner should be used in any source code file 72 | to indicate the copyright and license terms: 73 | 74 | ``` 75 | # Copyright (c) Jupyter Development Team. 76 | # Distributed under the terms of the Modified BSD License. 77 | ``` 78 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Releasing 2 | 3 | ## Using `jupyter_releaser` 4 | 5 | The recommended way to make a release is to use [`jupyter_releaser`](https://github.com/jupyter-server/jupyter_releaser#checklist-for-adoption). 6 | 7 | ## Manual Release 8 | 9 | ### Prerequisites 10 | 11 | - First check that the CHANGELOG.md is up to date for the next release version 12 | - Install packaging requirements: `pip install hatch twine` 13 | 14 | ### Bump version 15 | 16 | - `export version=` 17 | - `hatch version ${version}` 18 | 19 | ### Push to GitHub 20 | 21 | ```bash 22 | git push upstream && git push upstream --tags 23 | ``` 24 | 25 | ### Push to PyPI 26 | 27 | ```bash 28 | rm -rf dist/* 29 | rm -rf build/* 30 | hatch build . 31 | twine upload dist/* 32 | ``` 33 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: config_options.rst 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | config_options.rst: 60 | python3 autogen_config.py 61 | @echo "Created docs for config options" 62 | 63 | dirhtml: 64 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 65 | @echo 66 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 67 | 68 | singlehtml: 69 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 70 | @echo 71 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 72 | 73 | pickle: 74 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 75 | @echo 76 | @echo "Build finished; now you can process the pickle files." 77 | 78 | json: 79 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 80 | @echo 81 | @echo "Build finished; now you can process the JSON files." 82 | 83 | htmlhelp: 84 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 85 | @echo 86 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 87 | ".hhp project file in $(BUILDDIR)/htmlhelp." 88 | 89 | qthelp: 90 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 91 | @echo 92 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 93 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 94 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Jupyterconsole.qhcp" 95 | @echo "To view the help file:" 96 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Jupyterconsole.qhc" 97 | 98 | applehelp: 99 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 100 | @echo 101 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 102 | @echo "N.B. You won't be able to view it unless you put it in" \ 103 | "~/Library/Documentation/Help or install it in your application" \ 104 | "bundle." 105 | 106 | devhelp: 107 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 108 | @echo 109 | @echo "Build finished." 110 | @echo "To view the help file:" 111 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Jupyterconsole" 112 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Jupyterconsole" 113 | @echo "# devhelp" 114 | 115 | epub: 116 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 117 | @echo 118 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 119 | 120 | latex: 121 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 122 | @echo 123 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 124 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 125 | "(use \`make latexpdf' here to do that automatically)." 126 | 127 | latexpdf: 128 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 129 | @echo "Running LaTeX files through pdflatex..." 130 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 131 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 132 | 133 | latexpdfja: 134 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 135 | @echo "Running LaTeX files through platex and dvipdfmx..." 136 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 137 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 138 | 139 | text: 140 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 141 | @echo 142 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 143 | 144 | man: 145 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 146 | @echo 147 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 148 | 149 | texinfo: 150 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 151 | @echo 152 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 153 | @echo "Run \`make' in that directory to run these through makeinfo" \ 154 | "(use \`make info' here to do that automatically)." 155 | 156 | info: 157 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 158 | @echo "Running Texinfo files through makeinfo..." 159 | make -C $(BUILDDIR)/texinfo info 160 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 161 | 162 | gettext: 163 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 164 | @echo 165 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 166 | 167 | changes: 168 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 169 | @echo 170 | @echo "The overview file is in $(BUILDDIR)/changes." 171 | 172 | linkcheck: 173 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 174 | @echo 175 | @echo "Link check complete; look for any errors in the above output " \ 176 | "or in $(BUILDDIR)/linkcheck/output.txt." 177 | 178 | doctest: 179 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 180 | @echo "Testing of doctests in the sources finished, look at the " \ 181 | "results in $(BUILDDIR)/doctest/output.txt." 182 | 183 | coverage: 184 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 185 | @echo "Testing of coverage in the sources finished, look at the " \ 186 | "results in $(BUILDDIR)/coverage/python.txt." 187 | 188 | xml: 189 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 190 | @echo 191 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 192 | 193 | pseudoxml: 194 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 195 | @echo 196 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 197 | -------------------------------------------------------------------------------- /docs/autogen_config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os.path 4 | from jupyter_console.app import ZMQTerminalIPythonApp 5 | 6 | header = """\ 7 | Configuration options 8 | ===================== 9 | 10 | These options can be set in ``~/.jupyter/jupyter_console_config.py``, or 11 | at the command line when you start it. 12 | """ 13 | 14 | try: 15 | indir = os.path.dirname(__file__) 16 | except NameError: 17 | indir = os.getcwd() 18 | destination = os.path.join(indir, 'config_options.rst') 19 | 20 | with open(destination, 'w') as f: 21 | f.write(header) 22 | f.write(ZMQTerminalIPythonApp().document_config_options()) 23 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # Jupyter console documentation build configuration file, created by 5 | # sphinx-quickstart on Mon Jun 8 14:18:13 2015. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | import sys 17 | import os 18 | import shlex 19 | import shutil 20 | from typing import Dict 21 | 22 | # If extensions (or modules to document with autodoc) are in another directory, 23 | # add these directories to sys.path here. If the directory is relative to the 24 | # documentation root, use os.path.abspath to make it absolute, like shown here. 25 | 26 | # add repo root to sys.path 27 | # docs_dir = root/docs 28 | docsdir = os.path.abspath(os.path.dirname(__file__)) 29 | reporoot = os.path.dirname(os.path.dirname(docsdir)) 30 | sys.path.insert(0, reporoot) 31 | 32 | 33 | if os.environ.get('READTHEDOCS', ''): 34 | # RTD doesn't use the Makefile, so re-run autogen_config.py here. 35 | 36 | with open('autogen_config.py') as f: 37 | exec(compile(f.read(), 'autogen_config.py', 'exec'), {}) 38 | 39 | # -- General configuration ------------------------------------------------ 40 | 41 | # If your documentation needs a minimal Sphinx version, state it here. 42 | #needs_sphinx = '1.0' 43 | 44 | # Add any Sphinx extension module names here, as strings. They can be 45 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 46 | # ones. 47 | extensions = [ 48 | 'sphinx.ext.intersphinx', 49 | 'sphinxcontrib_github_alt', 50 | ] 51 | 52 | github_project_url = "https://github.com/jupyter/jupyter_console" 53 | 54 | # Add any paths that contain templates here, relative to this directory. 55 | templates_path = ['_templates'] 56 | 57 | # The suffix(es) of source filenames. 58 | # You can specify multiple suffix as a list of string: 59 | # source_suffix = ['.rst', '.md'] 60 | source_suffix = '.rst' 61 | 62 | # The encoding of source files. 63 | #source_encoding = 'utf-8-sig' 64 | 65 | # The master toctree document. 66 | master_doc = 'index' 67 | 68 | # General information about the project. 69 | project = 'Jupyter console' 70 | copyright = '2015, The Jupyter Development Team' 71 | author = 'The Jupyter Development Team' 72 | 73 | # The version info for the project you're documenting, acts as replacement for 74 | # |version| and |release|, also used in various other places throughout the 75 | # built documents. 76 | # 77 | 78 | version_ns: Dict = {} 79 | version_py = os.path.join('..', 'jupyter_console', '_version.py') 80 | with open(version_py) as f: 81 | exec(compile(f.read(), version_py, 'exec'), version_ns) 82 | 83 | # The short X.Y version. 84 | version = '%i.%i' % version_ns['version_info'][:2] 85 | # The full version, including alpha/beta/rc tags. 86 | release = version_ns['__version__'] 87 | 88 | # The language for content autogenerated by Sphinx. Refer to documentation 89 | # for a list of supported languages. 90 | # 91 | # This is also used if you do content translation via gettext catalogs. 92 | # Usually you set "language" from the command line for these cases. 93 | language = None 94 | 95 | # There are two options for replacing |today|: either, you set today to some 96 | # non-false value, then it is used: 97 | #today = '' 98 | # Else, today_fmt is used as the format for a strftime call. 99 | #today_fmt = '%B %d, %Y' 100 | 101 | # List of patterns, relative to source directory, that match files and 102 | # directories to ignore when looking for source files. 103 | exclude_patterns = ['_build'] 104 | 105 | # The reST default role (used for this markup: `text`) to use for all 106 | # documents. 107 | #default_role = None 108 | 109 | # If true, '()' will be appended to :func: etc. cross-reference text. 110 | #add_function_parentheses = True 111 | 112 | # If true, the current module name will be prepended to all description 113 | # unit titles (such as .. function::). 114 | #add_module_names = True 115 | 116 | # If true, sectionauthor and moduleauthor directives will be shown in the 117 | # output. They are ignored by default. 118 | #show_authors = False 119 | 120 | # The name of the Pygments (syntax highlighting) style to use. 121 | pygments_style = 'sphinx' 122 | 123 | # A list of ignored prefixes for module index sorting. 124 | #modindex_common_prefix = [] 125 | 126 | # If true, keep warnings as "system message" paragraphs in the built documents. 127 | #keep_warnings = False 128 | 129 | # If true, `todo` and `todoList` produce output, else they produce nothing. 130 | todo_include_todos = False 131 | 132 | 133 | # -- Options for HTML output ---------------------------------------------- 134 | 135 | # The theme to use for HTML and HTML Help pages. See the documentation for 136 | # a list of builtin themes. 137 | # html_theme = 'alabaster' 138 | 139 | # Theme options are theme-specific and customize the look and feel of a theme 140 | # further. For a list of options available for each theme, see the 141 | # documentation. 142 | #html_theme_options = {} 143 | 144 | # Add any paths that contain custom themes here, relative to this directory. 145 | #html_theme_path = [] 146 | 147 | # The name for this set of Sphinx documents. If None, it defaults to 148 | # " v documentation". 149 | #html_title = None 150 | 151 | # A shorter title for the navigation bar. Default is the same as html_title. 152 | #html_short_title = None 153 | 154 | # The name of an image file (relative to this directory) to place at the top 155 | # of the sidebar. 156 | #html_logo = None 157 | 158 | # The name of an image file (within the static path) to use as favicon of the 159 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 160 | # pixels large. 161 | #html_favicon = None 162 | 163 | # Add any paths that contain custom static files (such as style sheets) here, 164 | # relative to this directory. They are copied after the builtin static files, 165 | # so a file named "default.css" will overwrite the builtin "default.css". 166 | #html_static_path = ['_static'] 167 | 168 | # Add any extra paths that contain custom files (such as robots.txt or 169 | # .htaccess) here, relative to this directory. These files are copied 170 | # directly to the root of the documentation. 171 | #html_extra_path = [] 172 | 173 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 174 | # using the given strftime format. 175 | #html_last_updated_fmt = '%b %d, %Y' 176 | 177 | # If true, SmartyPants will be used to convert quotes and dashes to 178 | # typographically correct entities. 179 | #html_use_smartypants = True 180 | 181 | # Custom sidebar templates, maps document names to template names. 182 | #html_sidebars = {} 183 | 184 | # Additional templates that should be rendered to pages, maps page names to 185 | # template names. 186 | #html_additional_pages = {} 187 | 188 | # If false, no module index is generated. 189 | #html_domain_indices = True 190 | 191 | # If false, no index is generated. 192 | #html_use_index = True 193 | 194 | # If true, the index is split into individual pages for each letter. 195 | #html_split_index = False 196 | 197 | # If true, links to the reST sources are added to the pages. 198 | #html_show_sourcelink = True 199 | 200 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 201 | #html_show_sphinx = True 202 | 203 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 204 | #html_show_copyright = True 205 | 206 | # If true, an OpenSearch description file will be output, and all pages will 207 | # contain a tag referring to it. The value of this option must be the 208 | # base URL from which the finished HTML is served. 209 | #html_use_opensearch = '' 210 | 211 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 212 | #html_file_suffix = None 213 | 214 | # Language to be used for generating the HTML full-text search index. 215 | # Sphinx supports the following languages: 216 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja' 217 | # 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr' 218 | #html_search_language = 'en' 219 | 220 | # A dictionary with options for the search language support, empty by default. 221 | # Now only 'ja' uses this config value 222 | #html_search_options = {'type': 'default'} 223 | 224 | # The name of a javascript file (relative to the configuration directory) that 225 | # implements a search results scorer. If empty, the default will be used. 226 | #html_search_scorer = 'scorer.js' 227 | 228 | # Output file base name for HTML help builder. 229 | htmlhelp_basename = 'Jupyterconsoledoc' 230 | 231 | # -- Options for LaTeX output --------------------------------------------- 232 | 233 | #latex_elements = { 234 | # The paper size ('letterpaper' or 'a4paper'). 235 | #'papersize': 'letterpaper', 236 | 237 | # The font size ('10pt', '11pt' or '12pt'). 238 | #'pointsize': '10pt', 239 | 240 | # Additional stuff for the LaTeX preamble. 241 | #'preamble': '', 242 | 243 | # Latex figure (float) alignment 244 | #'figure_align': 'htbp', 245 | #} 246 | 247 | # Grouping the document tree into LaTeX files. List of tuples 248 | # (source start file, target name, title, 249 | # author, documentclass [howto, manual, or own class]). 250 | latex_documents = [ 251 | (master_doc, 'Jupyterconsole.tex', 'Jupyter console Documentation', 252 | 'The Jupyter Development Team', 'manual'), 253 | ] 254 | 255 | # The name of an image file (relative to this directory) to place at the top of 256 | # the title page. 257 | #latex_logo = None 258 | 259 | # For "manual" documents, if this is true, then toplevel headings are parts, 260 | # not chapters. 261 | #latex_use_parts = False 262 | 263 | # If true, show page references after internal links. 264 | #latex_show_pagerefs = False 265 | 266 | # If true, show URL addresses after external links. 267 | #latex_show_urls = False 268 | 269 | # Documents to append as an appendix to all manuals. 270 | #latex_appendices = [] 271 | 272 | # If false, no module index is generated. 273 | #latex_domain_indices = True 274 | 275 | 276 | # -- Options for manual page output --------------------------------------- 277 | 278 | # One entry per manual page. List of tuples 279 | # (source start file, name, description, authors, manual section). 280 | man_pages = [ 281 | (master_doc, 'jupyterconsole', 'Jupyter console Documentation', 282 | [author], 1) 283 | ] 284 | 285 | # If true, show URL addresses after external links. 286 | #man_show_urls = False 287 | 288 | 289 | # -- Options for Texinfo output ------------------------------------------- 290 | 291 | # Grouping the document tree into Texinfo files. List of tuples 292 | # (source start file, target name, title, author, 293 | # dir menu entry, description, category) 294 | texinfo_documents = [ 295 | (master_doc, 'Jupyterconsole', 'Jupyter console Documentation', 296 | author, 'Jupyterconsole', 'One line description of project.', 297 | 'Miscellaneous'), 298 | ] 299 | 300 | # Documents to append as an appendix to all manuals. 301 | #texinfo_appendices = [] 302 | 303 | # If false, no module index is generated. 304 | #texinfo_domain_indices = True 305 | 306 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 307 | #texinfo_show_urls = 'footnote' 308 | 309 | # If true, do not generate a @detailmenu in the "Top" node's menu. 310 | #texinfo_no_detailmenu = False 311 | 312 | 313 | # Example configuration for intersphinx: refer to the Python standard library. 314 | intersphinx_mapping = {'https://docs.python.org/3': None,} 315 | 316 | # on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org 317 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 318 | 319 | if not on_rtd: # only import and set the theme if we're building docs locally 320 | import sphinx_rtd_theme 321 | html_theme = 'sphinx_rtd_theme' 322 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] 323 | 324 | # otherwise, readthedocs.org uses their theme by default, so no need to specify it 325 | 326 | 327 | def setup(app): 328 | HERE = os.path.abspath(os.path.dirname(__file__)) 329 | dest = os.path.join(HERE, 'changelog.md') 330 | shutil.copy(os.path.join(HERE, '..', 'CHANGELOG.md'), dest) 331 | -------------------------------------------------------------------------------- /docs/environment.yml: -------------------------------------------------------------------------------- 1 | name: jupyterconsole 2 | channels: 3 | - conda-forge 4 | - conda 5 | dependencies: 6 | - python=3 7 | - ipython 8 | - jupyter_core 9 | - jupyter_client 10 | - sphinx 11 | - sphinx_rtd_theme 12 | - pip: 13 | - sphinxcontrib_github_alt 14 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Jupyter console |version| 2 | ========================= 3 | 4 | The Jupyter console is a terminal frontend for kernels using the Jupyter protocol. 5 | The console can be installed with:: 6 | 7 | pip install jupyter_console 8 | 9 | If you want to use conda instead to perform your installation:: 10 | 11 | conda install -c conda-forge jupyter_console 12 | 13 | And started with:: 14 | 15 | jupyter console 16 | 17 | To see configuration options:: 18 | 19 | jupyter console -h 20 | 21 | To start the console with a particular kernel, ask for it by name:: 22 | 23 | jupyter console --kernel=julia-1.4 24 | 25 | A list of available kernels can be seen with:: 26 | 27 | jupyter kernelspec list 28 | 29 | You can connect to a live kernel (e.g. one running in a notebook) with its ID:: 30 | 31 | jupyter console --existing KERNEL_ID 32 | 33 | or even connect to the most recently started kernel by default:: 34 | 35 | jupyter console --existing 36 | 37 | 38 | Contents: 39 | 40 | .. toctree:: 41 | :maxdepth: 2 42 | 43 | config_options 44 | release 45 | changelog 46 | 47 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | echo. coverage to run coverage check of the documentation if enabled 41 | goto end 42 | ) 43 | 44 | if "%1" == "clean" ( 45 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 46 | del /q /s %BUILDDIR%\* 47 | goto end 48 | ) 49 | 50 | 51 | REM Check if sphinx-build is available and fallback to Python version if any 52 | %SPHINXBUILD% 2> nul 53 | if errorlevel 9009 goto sphinx_python 54 | goto sphinx_ok 55 | 56 | :sphinx_python 57 | 58 | set SPHINXBUILD=python -m sphinx.__init__ 59 | %SPHINXBUILD% 2> nul 60 | if errorlevel 9009 ( 61 | echo. 62 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 63 | echo.installed, then set the SPHINXBUILD environment variable to point 64 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 65 | echo.may add the Sphinx directory to PATH. 66 | echo. 67 | echo.If you don't have Sphinx installed, grab it from 68 | echo.http://sphinx-doc.org/ 69 | exit /b 1 70 | ) 71 | 72 | :sphinx_ok 73 | 74 | 75 | if "%1" == "html" ( 76 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 77 | if errorlevel 1 exit /b 1 78 | echo. 79 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 80 | goto end 81 | ) 82 | 83 | if "%1" == "dirhtml" ( 84 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 85 | if errorlevel 1 exit /b 1 86 | echo. 87 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 88 | goto end 89 | ) 90 | 91 | if "%1" == "singlehtml" ( 92 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 93 | if errorlevel 1 exit /b 1 94 | echo. 95 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 96 | goto end 97 | ) 98 | 99 | if "%1" == "pickle" ( 100 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 101 | if errorlevel 1 exit /b 1 102 | echo. 103 | echo.Build finished; now you can process the pickle files. 104 | goto end 105 | ) 106 | 107 | if "%1" == "json" ( 108 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 109 | if errorlevel 1 exit /b 1 110 | echo. 111 | echo.Build finished; now you can process the JSON files. 112 | goto end 113 | ) 114 | 115 | if "%1" == "htmlhelp" ( 116 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 117 | if errorlevel 1 exit /b 1 118 | echo. 119 | echo.Build finished; now you can run HTML Help Workshop with the ^ 120 | .hhp project file in %BUILDDIR%/htmlhelp. 121 | goto end 122 | ) 123 | 124 | if "%1" == "qthelp" ( 125 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 129 | .qhcp project file in %BUILDDIR%/qthelp, like this: 130 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Jupyterconsole.qhcp 131 | echo.To view the help file: 132 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Jupyterconsole.ghc 133 | goto end 134 | ) 135 | 136 | if "%1" == "devhelp" ( 137 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 138 | if errorlevel 1 exit /b 1 139 | echo. 140 | echo.Build finished. 141 | goto end 142 | ) 143 | 144 | if "%1" == "epub" ( 145 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 146 | if errorlevel 1 exit /b 1 147 | echo. 148 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 149 | goto end 150 | ) 151 | 152 | if "%1" == "latex" ( 153 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 154 | if errorlevel 1 exit /b 1 155 | echo. 156 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 157 | goto end 158 | ) 159 | 160 | if "%1" == "latexpdf" ( 161 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 162 | cd %BUILDDIR%/latex 163 | make all-pdf 164 | cd %~dp0 165 | echo. 166 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 167 | goto end 168 | ) 169 | 170 | if "%1" == "latexpdfja" ( 171 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 172 | cd %BUILDDIR%/latex 173 | make all-pdf-ja 174 | cd %~dp0 175 | echo. 176 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 177 | goto end 178 | ) 179 | 180 | if "%1" == "text" ( 181 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 182 | if errorlevel 1 exit /b 1 183 | echo. 184 | echo.Build finished. The text files are in %BUILDDIR%/text. 185 | goto end 186 | ) 187 | 188 | if "%1" == "man" ( 189 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 190 | if errorlevel 1 exit /b 1 191 | echo. 192 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 193 | goto end 194 | ) 195 | 196 | if "%1" == "texinfo" ( 197 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 198 | if errorlevel 1 exit /b 1 199 | echo. 200 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 201 | goto end 202 | ) 203 | 204 | if "%1" == "gettext" ( 205 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 206 | if errorlevel 1 exit /b 1 207 | echo. 208 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 209 | goto end 210 | ) 211 | 212 | if "%1" == "changes" ( 213 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 214 | if errorlevel 1 exit /b 1 215 | echo. 216 | echo.The overview file is in %BUILDDIR%/changes. 217 | goto end 218 | ) 219 | 220 | if "%1" == "linkcheck" ( 221 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 222 | if errorlevel 1 exit /b 1 223 | echo. 224 | echo.Link check complete; look for any errors in the above output ^ 225 | or in %BUILDDIR%/linkcheck/output.txt. 226 | goto end 227 | ) 228 | 229 | if "%1" == "doctest" ( 230 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 231 | if errorlevel 1 exit /b 1 232 | echo. 233 | echo.Testing of doctests in the sources finished, look at the ^ 234 | results in %BUILDDIR%/doctest/output.txt. 235 | goto end 236 | ) 237 | 238 | if "%1" == "coverage" ( 239 | %SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage 240 | if errorlevel 1 exit /b 1 241 | echo. 242 | echo.Testing of coverage in the sources finished, look at the ^ 243 | results in %BUILDDIR%/coverage/python.txt. 244 | goto end 245 | ) 246 | 247 | if "%1" == "xml" ( 248 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 249 | if errorlevel 1 exit /b 1 250 | echo. 251 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 252 | goto end 253 | ) 254 | 255 | if "%1" == "pseudoxml" ( 256 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 257 | if errorlevel 1 exit /b 1 258 | echo. 259 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 260 | goto end 261 | ) 262 | 263 | :end 264 | -------------------------------------------------------------------------------- /docs/release.rst: -------------------------------------------------------------------------------- 1 | .. _jupyter_console_release: 2 | 3 | Making a release as a maintainer 4 | ================================ 5 | 6 | This document guides a maintainer through creating a release of the Jupyter 7 | console. 8 | 9 | Clean the repository 10 | -------------------- 11 | 12 | Remove all non-tracked files with: 13 | 14 | .. code:: bash 15 | 16 | git clean -xfdi 17 | 18 | This will ask you for confirmation before removing all untracked files. Make 19 | sure the ``dist/`` folder is clean and does not contain any stale builds from 20 | previous attempts. 21 | 22 | Create the release 23 | ------------------ 24 | 25 | #. Set Environment variables 26 | 27 | Set environment variables to document current release version, and git tag: 28 | 29 | .. code:: bash 30 | 31 | VERSION=4.1.0 32 | 33 | #. Update version number in ``jupyter_console/_version.py``. Make sure that 34 | a valid `PEP 440 `_ version is 35 | being used. 36 | 37 | #. Commit and tag the release with the current version number: 38 | 39 | .. code:: bash 40 | 41 | git commit -am "release $VERSION" 42 | git tag $VERSION 43 | 44 | #. You are now ready to build the ``sdist`` and ``wheel``: 45 | 46 | .. code:: bash 47 | 48 | python setup.py sdist --formats=gztar 49 | python setup.py bdist_wheel 50 | 51 | #. You can now test the ``wheel`` and the ``sdist`` locally before uploading 52 | to PyPI. Make sure to use `twine `_ to 53 | upload the archives over SSL. 54 | 55 | .. code:: bash 56 | 57 | twine upload dist/* 58 | 59 | #. If all went well, change the ``jupyter_console/_version.py`` to the next 60 | release. 61 | 62 | #. Push directly on master, not forgetting to push ``--tags`` too. 63 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | ipython 2 | -e ../. 3 | jupyter_core 4 | jupyter_client 5 | sphinx 6 | sphinxcontrib_github_alt 7 | -------------------------------------------------------------------------------- /jupyter_console/__init__.py: -------------------------------------------------------------------------------- 1 | """Jupyter terminal console""" 2 | 3 | from ._version import version_info, __version__ # noqa 4 | -------------------------------------------------------------------------------- /jupyter_console/__main__.py: -------------------------------------------------------------------------------- 1 | from jupyter_console import app 2 | 3 | if __name__ == '__main__': 4 | app.launch_new_instance() 5 | -------------------------------------------------------------------------------- /jupyter_console/_version.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import List, Union 3 | 4 | __version__ = "6.6.3" 5 | 6 | # Build up version_info tuple for backwards compatibility 7 | pattern = r'(?P\d+).(?P\d+).(?P\d+)(?P.*)' 8 | match = re.match(pattern, __version__) 9 | if match: 10 | parts: List[Union[int, str]] = [int(match[part]) for part in ['major', 'minor', 'patch']] 11 | if match['rest']: 12 | parts.append(match['rest']) 13 | else: 14 | parts = [] 15 | version_info = tuple(parts) 16 | -------------------------------------------------------------------------------- /jupyter_console/app.py: -------------------------------------------------------------------------------- 1 | """ A minimal application using the ZMQ-based terminal IPython frontend. 2 | 3 | This is not a complete console app, as subprocess will not be able to receive 4 | input, there is no real readline support, among other limitations. 5 | """ 6 | 7 | # Copyright (c) IPython Development Team. 8 | # Distributed under the terms of the Modified BSD License. 9 | 10 | from __future__ import print_function 11 | 12 | import signal 13 | import sys 14 | 15 | from traitlets import ( 16 | Dict, Any 17 | ) 18 | from traitlets.config import catch_config_error, boolean_flag 19 | 20 | from jupyter_core.application import JupyterApp, base_aliases, base_flags 21 | from jupyter_client.consoleapp import ( 22 | JupyterConsoleApp, app_aliases, app_flags, 23 | ) 24 | 25 | from jupyter_console.ptshell import ZMQTerminalInteractiveShell 26 | from jupyter_console import __version__ 27 | 28 | #----------------------------------------------------------------------------- 29 | # Globals 30 | #----------------------------------------------------------------------------- 31 | 32 | _examples = """ 33 | jupyter console # start the ZMQ-based console 34 | jupyter console --existing # connect to an existing ipython session 35 | """ 36 | 37 | #----------------------------------------------------------------------------- 38 | # Flags and Aliases 39 | #----------------------------------------------------------------------------- 40 | 41 | # copy flags from mixin: 42 | flags = dict(base_flags) 43 | # start with mixin frontend flags: 44 | # update full dict with frontend flags: 45 | flags.update(app_flags) 46 | flags.update(boolean_flag( 47 | 'simple-prompt', 'ZMQTerminalInteractiveShell.simple_prompt', 48 | "Force simple minimal prompt using `raw_input`", 49 | "Use a rich interactive prompt with prompt_toolkit" 50 | )) 51 | 52 | # copy flags from mixin 53 | aliases = dict(base_aliases) 54 | 55 | aliases.update(app_aliases) 56 | 57 | frontend_aliases = set(app_aliases.keys()) 58 | frontend_flags = set(app_flags.keys()) 59 | 60 | 61 | #----------------------------------------------------------------------------- 62 | # Classes 63 | #----------------------------------------------------------------------------- 64 | 65 | 66 | class ZMQTerminalIPythonApp(JupyterApp, JupyterConsoleApp): # type:ignore[misc] 67 | name = "jupyter-console" 68 | version = __version__ 69 | """Start a terminal frontend to the IPython zmq kernel.""" 70 | 71 | description = """ 72 | The Jupyter terminal-based Console. 73 | 74 | This launches a Console application inside a terminal. 75 | 76 | The Console supports various extra features beyond the traditional 77 | single-process Terminal IPython shell, such as connecting to an 78 | existing ipython session, via: 79 | 80 | jupyter console --existing 81 | 82 | where the previous session could have been created by another ipython 83 | console, an ipython qtconsole, or by opening an ipython notebook. 84 | 85 | """ 86 | examples = _examples 87 | 88 | classes = [ZMQTerminalInteractiveShell] + JupyterConsoleApp.classes # type:ignore[operator] 89 | flags = Dict(flags) # type:ignore[assignment] 90 | aliases = Dict(aliases) # type:ignore[assignment] 91 | frontend_aliases = Any(frontend_aliases) 92 | frontend_flags = Any(frontend_flags) 93 | 94 | subcommands = Dict() 95 | 96 | force_interact = True 97 | 98 | def parse_command_line(self, argv=None): 99 | super(ZMQTerminalIPythonApp, self).parse_command_line(argv) 100 | self.build_kernel_argv(self.extra_args) 101 | 102 | def init_shell(self): 103 | JupyterConsoleApp.initialize(self) 104 | # relay sigint to kernel 105 | signal.signal(signal.SIGINT, self.handle_sigint) 106 | self.shell = ZMQTerminalInteractiveShell.instance(parent=self, 107 | manager=self.kernel_manager, 108 | client=self.kernel_client, 109 | confirm_exit=self.confirm_exit, 110 | ) 111 | self.shell.own_kernel = not self.existing 112 | 113 | def init_gui_pylab(self): 114 | # no-op, because we don't want to import matplotlib in the frontend. 115 | pass 116 | 117 | def handle_sigint(self, *args): 118 | if self.shell._executing: 119 | if self.kernel_manager: 120 | self.kernel_manager.interrupt_kernel() 121 | else: 122 | print("ERROR: Cannot interrupt kernels we didn't start.", 123 | file = sys.stderr) 124 | else: 125 | # raise the KeyboardInterrupt if we aren't waiting for execution, 126 | # so that the interact loop advances, and prompt is redrawn, etc. 127 | raise KeyboardInterrupt 128 | 129 | @catch_config_error 130 | def initialize(self, argv=None): 131 | """Do actions after construct, but before starting the app.""" 132 | super(ZMQTerminalIPythonApp, self).initialize(argv) 133 | if self._dispatching: 134 | return 135 | # create the shell 136 | self.init_shell() 137 | # and draw the banner 138 | self.init_banner() 139 | 140 | def init_banner(self): 141 | """optionally display the banner""" 142 | self.shell.show_banner() 143 | 144 | def start(self): 145 | # JupyterApp.start dispatches on NoStart 146 | super(ZMQTerminalIPythonApp, self).start() 147 | self.log.debug("Starting the jupyter console mainloop...") 148 | self.shell.mainloop() 149 | 150 | 151 | main = launch_new_instance = ZMQTerminalIPythonApp.launch_instance 152 | 153 | 154 | if __name__ == '__main__': 155 | main() 156 | 157 | -------------------------------------------------------------------------------- /jupyter_console/completer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """Adapt readline completer interface to make ZMQ request.""" 3 | 4 | # Copyright (c) IPython Development Team. 5 | # Distributed under the terms of the Modified BSD License. 6 | 7 | from traitlets.config import Configurable 8 | from traitlets import Float 9 | 10 | from jupyter_console.utils import run_sync 11 | 12 | 13 | class ZMQCompleter(Configurable): 14 | """Client-side completion machinery. 15 | 16 | How it works: self.complete will be called multiple times, with 17 | state=0,1,2,... When state=0 it should compute ALL the completion matches, 18 | and then return them for each value of state.""" 19 | 20 | timeout = Float(5.0, config=True, help='timeout before completion abort') 21 | 22 | def __init__(self, shell, client, config=None): 23 | super(ZMQCompleter,self).__init__(config=config) 24 | 25 | self.shell = shell 26 | self.client = client 27 | self.matches = [] 28 | 29 | def complete_request(self, code, cursor_pos): 30 | # send completion request to kernel 31 | # Give the kernel up to 5s to respond 32 | msg_id = self.client.complete( 33 | code=code, 34 | cursor_pos=cursor_pos, 35 | ) 36 | 37 | msg = run_sync(self.client.shell_channel.get_msg)(timeout=self.timeout) 38 | if msg['parent_header']['msg_id'] == msg_id: 39 | return msg['content'] 40 | 41 | return {'matches': [], 'cursor_start': 0, 'cursor_end': 0, 42 | 'metadata': {}, 'status': 'ok'} 43 | 44 | -------------------------------------------------------------------------------- /jupyter_console/ptshell.py: -------------------------------------------------------------------------------- 1 | """IPython terminal interface using prompt_toolkit in place of readline""" 2 | from __future__ import print_function 3 | 4 | import asyncio 5 | import base64 6 | import errno 7 | from getpass import getpass 8 | from io import BytesIO 9 | import os 10 | from queue import Empty 11 | import signal 12 | import subprocess 13 | import sys 14 | from tempfile import TemporaryDirectory 15 | import time 16 | from warnings import warn 17 | 18 | from typing import Dict as DictType, Any as AnyType 19 | 20 | from zmq import ZMQError 21 | from IPython.core import page 22 | from traitlets import ( 23 | Bool, 24 | Integer, 25 | Float, 26 | Unicode, 27 | List, 28 | Dict, 29 | Enum, 30 | Instance, 31 | Any, 32 | ) 33 | from traitlets.config import SingletonConfigurable 34 | 35 | from .completer import ZMQCompleter 36 | from .zmqhistory import ZMQHistoryManager 37 | from . import __version__ 38 | 39 | # Discriminate version3 for asyncio 40 | from prompt_toolkit import __version__ as ptk_version 41 | PTK3 = ptk_version.startswith('3.') 42 | 43 | if not PTK3: 44 | # use_ayncio_event_loop obsolete in PKT3 45 | from prompt_toolkit.eventloop.defaults import use_asyncio_event_loop 46 | 47 | from prompt_toolkit.completion import Completer, Completion 48 | from prompt_toolkit.document import Document 49 | from prompt_toolkit.enums import DEFAULT_BUFFER, EditingMode 50 | from prompt_toolkit.filters import ( 51 | Condition, 52 | has_focus, 53 | has_selection, 54 | vi_insert_mode, 55 | emacs_insert_mode, 56 | is_done, 57 | ) 58 | from prompt_toolkit.history import InMemoryHistory 59 | from prompt_toolkit.shortcuts.prompt import PromptSession 60 | from prompt_toolkit.shortcuts import print_formatted_text, CompleteStyle 61 | from prompt_toolkit.key_binding import KeyBindings 62 | from prompt_toolkit.lexers import PygmentsLexer 63 | from prompt_toolkit.layout.processors import ( 64 | ConditionalProcessor, 65 | HighlightMatchingBracketProcessor, 66 | ) 67 | from prompt_toolkit.styles import merge_styles 68 | from prompt_toolkit.styles.pygments import (style_from_pygments_cls, 69 | style_from_pygments_dict) 70 | from prompt_toolkit.formatted_text import PygmentsTokens 71 | from prompt_toolkit.output import ColorDepth 72 | from prompt_toolkit.utils import suspend_to_background_supported 73 | 74 | from pygments.styles import get_style_by_name 75 | from pygments.lexers import get_lexer_by_name 76 | from pygments.util import ClassNotFound 77 | from pygments.token import Token 78 | 79 | from jupyter_console.utils import run_sync, ensure_async 80 | 81 | 82 | def ask_yes_no(prompt, default=None, interrupt=None): 83 | """Asks a question and returns a boolean (y/n) answer. 84 | 85 | If default is given (one of 'y','n'), it is used if the user input is 86 | empty. If interrupt is given (one of 'y','n'), it is used if the user 87 | presses Ctrl-C. Otherwise the question is repeated until an answer is 88 | given. 89 | 90 | An EOF is treated as the default answer. If there is no default, an 91 | exception is raised to prevent infinite loops. 92 | 93 | Valid answers are: y/yes/n/no (match is not case sensitive).""" 94 | 95 | answers = {'y': True, 'n': False, 'yes': True, 'no': False} 96 | ans = None 97 | while ans not in answers.keys(): 98 | try: 99 | ans = input(prompt + ' ').lower() 100 | if not ans: # response was an empty string 101 | ans = default 102 | except KeyboardInterrupt: 103 | if interrupt: 104 | ans = interrupt 105 | except EOFError: 106 | if default in answers.keys(): 107 | ans = default 108 | print() 109 | else: 110 | raise 111 | 112 | return answers[ans] 113 | 114 | 115 | async def async_input(prompt, loop=None): 116 | """Simple async version of input using a the default executor""" 117 | if loop is None: 118 | loop = asyncio.get_event_loop() 119 | 120 | raw = await loop.run_in_executor(None, input, prompt) 121 | return raw 122 | 123 | 124 | def get_pygments_lexer(name): 125 | name = name.lower() 126 | if name == 'ipython2': 127 | from IPython.lib.lexers import IPythonLexer 128 | return IPythonLexer 129 | elif name == 'ipython3': 130 | from IPython.lib.lexers import IPython3Lexer 131 | return IPython3Lexer 132 | else: 133 | try: 134 | return get_lexer_by_name(name).__class__ 135 | except ClassNotFound: 136 | warn("No lexer found for language %r. Treating as plain text." % name) 137 | from pygments.lexers.special import TextLexer 138 | return TextLexer 139 | 140 | 141 | class JupyterPTCompleter(Completer): 142 | """Adaptor to provide kernel completions to prompt_toolkit""" 143 | def __init__(self, jup_completer): 144 | self.jup_completer = jup_completer 145 | 146 | def get_completions(self, document, complete_event): 147 | if not document.current_line.strip(): 148 | return 149 | 150 | content = self.jup_completer.complete_request( 151 | code=document.text, 152 | cursor_pos=document.cursor_position 153 | ) 154 | meta = content["metadata"] 155 | 156 | if "_jupyter_types_experimental" in meta: 157 | try: 158 | new_meta = {} 159 | for c, m in zip( 160 | content["matches"], meta["_jupyter_types_experimental"] 161 | ): 162 | new_meta[c] = m["type"] 163 | meta = new_meta 164 | except Exception: 165 | pass 166 | 167 | start_pos = content["cursor_start"] - document.cursor_position 168 | for m in content["matches"]: 169 | yield Completion( 170 | m, 171 | start_position=start_pos, 172 | display_meta=meta.get(m, "?"), 173 | ) 174 | 175 | 176 | class ZMQTerminalInteractiveShell(SingletonConfigurable): 177 | readline_use = False 178 | 179 | pt_cli = None 180 | 181 | _executing = False 182 | _execution_state = Unicode('') 183 | _pending_clearoutput = False 184 | _eventloop = None 185 | own_kernel = False # Changed by ZMQTerminalIPythonApp 186 | 187 | editing_mode = Unicode('emacs', config=True, 188 | help="Shortcut style to use at the prompt. 'vi' or 'emacs'.", 189 | ) 190 | 191 | highlighting_style = Unicode('', config=True, 192 | help="The name of a Pygments style to use for syntax highlighting" 193 | ) 194 | 195 | highlighting_style_overrides = Dict(config=True, 196 | help="Override highlighting format for specific tokens" 197 | ) 198 | 199 | true_color = Bool(False, config=True, 200 | help=("Use 24bit colors instead of 256 colors in prompt highlighting. " 201 | "If your terminal supports true color, the following command " 202 | "should print 'TRUECOLOR' in orange: " 203 | "printf \"\\x1b[38;2;255;100;0mTRUECOLOR\\x1b[0m\\n\"") 204 | ) 205 | 206 | history_load_length = Integer(1000, config=True, 207 | help="How many history items to load into memory" 208 | ) 209 | 210 | banner = Unicode('Jupyter console {version}\n\n{kernel_banner}', config=True, 211 | help=("Text to display before the first prompt. Will be formatted with " 212 | "variables {version} and {kernel_banner}.") 213 | ) 214 | 215 | kernel_timeout = Float(60, config=True, 216 | help="""Timeout for giving up on a kernel (in seconds). 217 | 218 | On first connect and restart, the console tests whether the 219 | kernel is running and responsive by sending kernel_info_requests. 220 | This sets the timeout in seconds for how long the kernel can take 221 | before being presumed dead. 222 | """ 223 | ) 224 | 225 | image_handler = Enum(('PIL', 'stream', 'tempfile', 'callable'), 226 | 'PIL', config=True, allow_none=True, help= 227 | """ 228 | Handler for image type output. This is useful, for example, 229 | when connecting to the kernel in which pylab inline backend is 230 | activated. There are four handlers defined. 'PIL': Use 231 | Python Imaging Library to popup image; 'stream': Use an 232 | external program to show the image. Image will be fed into 233 | the STDIN of the program. You will need to configure 234 | `stream_image_handler`; 'tempfile': Use an external program to 235 | show the image. Image will be saved in a temporally file and 236 | the program is called with the temporally file. You will need 237 | to configure `tempfile_image_handler`; 'callable': You can set 238 | any Python callable which is called with the image data. You 239 | will need to configure `callable_image_handler`. 240 | """ 241 | ) 242 | 243 | stream_image_handler = List(config=True, help= 244 | """ 245 | Command to invoke an image viewer program when you are using 246 | 'stream' image handler. This option is a list of string where 247 | the first element is the command itself and reminders are the 248 | options for the command. Raw image data is given as STDIN to 249 | the program. 250 | """ 251 | ) 252 | 253 | tempfile_image_handler = List(config=True, help= 254 | """ 255 | Command to invoke an image viewer program when you are using 256 | 'tempfile' image handler. This option is a list of string 257 | where the first element is the command itself and reminders 258 | are the options for the command. You can use {file} and 259 | {format} in the string to represent the location of the 260 | generated image file and image format. 261 | """ 262 | ) 263 | 264 | callable_image_handler = Any( 265 | config=True, 266 | help=""" 267 | Callable object called via 'callable' image handler with one 268 | argument, `data`, which is `msg["content"]["data"]` where 269 | `msg` is the message from iopub channel. For example, you can 270 | find base64 encoded PNG data as `data['image/png']`. If your function 271 | can't handle the data supplied, it should return `False` to indicate 272 | this. 273 | """ 274 | ) 275 | 276 | mime_preference = List( 277 | default_value=['image/png', 'image/jpeg', 'image/svg+xml'], 278 | config=True, help= 279 | """ 280 | Preferred object representation MIME type in order. First 281 | matched MIME type will be used. 282 | """ 283 | ) 284 | 285 | use_kernel_is_complete = Bool(True, config=True, 286 | help="""Whether to use the kernel's is_complete message 287 | handling. If False, then the frontend will use its 288 | own is_complete handler. 289 | """ 290 | ) 291 | kernel_is_complete_timeout = Float(1, config=True, 292 | help="""Timeout (in seconds) for giving up on a kernel's is_complete 293 | response. 294 | 295 | If the kernel does not respond at any point within this time, 296 | the kernel will no longer be asked if code is complete, and the 297 | console will default to the built-in is_complete test. 298 | """ 299 | ) 300 | 301 | # This is configurable on JupyterConsoleApp; this copy is not configurable 302 | # to avoid a duplicate config option. 303 | confirm_exit = Bool(True, 304 | help="""Set to display confirmation dialog on exit. 305 | You can always use 'exit' or 'quit', to force a 306 | direct exit without any confirmation. 307 | """, 308 | ) 309 | 310 | display_completions = Enum( 311 | ("column", "multicolumn", "readlinelike"), 312 | help=( 313 | "Options for displaying tab completions, 'column', 'multicolumn', and " 314 | "'readlinelike'. These options are for `prompt_toolkit`, see " 315 | "`prompt_toolkit` documentation for more information." 316 | ), 317 | default_value="multicolumn", 318 | ).tag(config=True) 319 | 320 | prompt_includes_vi_mode = Bool(True, 321 | help="Display the current vi mode (when using vi editing mode)." 322 | ).tag(config=True) 323 | 324 | highlight_matching_brackets = Bool(True, help="Highlight matching brackets.",).tag( 325 | config=True 326 | ) 327 | 328 | manager = Instance("jupyter_client.KernelManager", allow_none=True) 329 | client = Instance("jupyter_client.KernelClient", allow_none=True) 330 | 331 | def _client_changed(self, name, old, new): 332 | self.session_id = new.session.session 333 | session_id = Unicode() 334 | 335 | def _banner1_default(self): 336 | return "Jupyter Console {version}\n".format(version=__version__) 337 | 338 | simple_prompt = Bool(False, 339 | help="""Use simple fallback prompt. Features may be limited.""" 340 | ).tag(config=True) 341 | 342 | def __init__(self, **kwargs): 343 | # This is where traits with a config_key argument are updated 344 | # from the values on config. 345 | super(ZMQTerminalInteractiveShell, self).__init__(**kwargs) 346 | self.configurables = [self] 347 | 348 | self.init_history() 349 | self.init_completer() 350 | self.init_io() 351 | 352 | self.init_kernel_info() 353 | self.init_prompt_toolkit_cli() 354 | self.keep_running = True 355 | self.execution_count = 1 356 | 357 | def init_completer(self): 358 | """Initialize the completion machinery. 359 | 360 | This creates completion machinery that can be used by client code, 361 | either interactively in-process (typically triggered by the readline 362 | library), programmatically (such as in test suites) or out-of-process 363 | (typically over the network by remote frontends). 364 | """ 365 | self.Completer = ZMQCompleter(self, self.client, config=self.config) 366 | 367 | def init_history(self): 368 | """Sets up the command history. """ 369 | self.history_manager = ZMQHistoryManager(client=self.client) 370 | self.configurables.append(self.history_manager) 371 | 372 | def vi_mode(self): 373 | if (getattr(self, 'editing_mode', None) == 'vi' 374 | and self.prompt_includes_vi_mode): 375 | return '['+str(self.pt_cli.app.vi_state.input_mode)[3:6]+'] ' 376 | return '' 377 | 378 | def get_prompt_tokens(self, ec=None): 379 | if ec is None: 380 | ec = self.execution_count 381 | return [ 382 | (Token.Prompt, self.vi_mode()), 383 | (Token.Prompt, 'In ['), 384 | (Token.PromptNum, str(ec)), 385 | (Token.Prompt, ']: '), 386 | ] 387 | 388 | def get_continuation_tokens(self, width): 389 | return [ 390 | (Token.Prompt, (" " * (width - 5)) + "...: "), 391 | ] 392 | 393 | def get_out_prompt_tokens(self): 394 | return [ 395 | (Token.OutPrompt, 'Out['), 396 | (Token.OutPromptNum, str(self.execution_count)), 397 | (Token.OutPrompt, ']: ') 398 | ] 399 | 400 | def print_out_prompt(self): 401 | tokens = self.get_out_prompt_tokens() 402 | print_formatted_text(PygmentsTokens(tokens), end='', 403 | style = self.pt_cli.app.style) 404 | 405 | def get_remote_prompt_tokens(self): 406 | return [ 407 | (Token.RemotePrompt, self.other_output_prefix), 408 | ] 409 | 410 | def print_remote_prompt(self, ec=None): 411 | tokens = self.get_remote_prompt_tokens() + self.get_prompt_tokens(ec=ec) 412 | print_formatted_text( 413 | PygmentsTokens(tokens), end="", style=self.pt_cli.app.style 414 | ) 415 | 416 | @property 417 | def pt_complete_style(self): 418 | return { 419 | "multicolumn": CompleteStyle.MULTI_COLUMN, 420 | "column": CompleteStyle.COLUMN, 421 | "readlinelike": CompleteStyle.READLINE_LIKE, 422 | }[self.display_completions] 423 | 424 | kernel_info: DictType[str, AnyType] = {} 425 | 426 | def init_kernel_info(self): 427 | """Wait for a kernel to be ready, and store kernel info""" 428 | timeout = self.kernel_timeout 429 | tic = time.time() 430 | self.client.hb_channel.unpause() 431 | msg_id = self.client.kernel_info() 432 | while True: 433 | try: 434 | reply = self.client.get_shell_msg(timeout=1) 435 | except Empty as e: 436 | if (time.time() - tic) > timeout: 437 | raise RuntimeError("Kernel didn't respond to kernel_info_request") from e 438 | else: 439 | if reply['parent_header'].get('msg_id') == msg_id: 440 | self.kernel_info = reply['content'] 441 | return 442 | 443 | def show_banner(self): 444 | print(self.banner.format(version=__version__, 445 | kernel_banner=self.kernel_info.get('banner', '')),end='',flush=True) 446 | 447 | def init_prompt_toolkit_cli(self): 448 | if self.simple_prompt or ('JUPYTER_CONSOLE_TEST' in os.environ): 449 | # Simple restricted interface for tests so we can find prompts with 450 | # pexpect. Multi-line input not supported. 451 | async def prompt(): 452 | prompt = 'In [%d]: ' % self.execution_count 453 | raw = await async_input(prompt) 454 | return raw 455 | self.prompt_for_code = prompt 456 | self.print_out_prompt = \ 457 | lambda: print('Out[%d]: ' % self.execution_count, end='') 458 | return 459 | 460 | kb = KeyBindings() 461 | insert_mode = vi_insert_mode | emacs_insert_mode 462 | 463 | @kb.add("enter", filter=(has_focus(DEFAULT_BUFFER) 464 | & ~has_selection 465 | & insert_mode 466 | )) 467 | def _(event): 468 | b = event.current_buffer 469 | d = b.document 470 | if not (d.on_last_line or d.cursor_position_row >= d.line_count 471 | - d.empty_line_count_at_the_end()): 472 | b.newline() 473 | return 474 | 475 | # Pressing enter flushes any pending display. This also ensures 476 | # the displayed execution_count is correct. 477 | self.handle_iopub() 478 | 479 | more, indent = self.check_complete(d.text) 480 | 481 | if (not more) and b.accept_handler: 482 | b.validate_and_handle() 483 | else: 484 | b.insert_text('\n' + indent) 485 | 486 | @kb.add("c-c", filter=has_focus(DEFAULT_BUFFER)) 487 | def _(event): 488 | event.current_buffer.reset() 489 | 490 | @kb.add("c-\\", filter=has_focus(DEFAULT_BUFFER)) 491 | def _(event): 492 | raise EOFError 493 | 494 | @kb.add("c-z", filter=Condition(lambda: suspend_to_background_supported())) 495 | def _(event): 496 | event.cli.suspend_to_background() 497 | 498 | @kb.add("c-o", filter=(has_focus(DEFAULT_BUFFER) & emacs_insert_mode)) 499 | def _(event): 500 | event.current_buffer.insert_text("\n") 501 | 502 | # Pre-populate history from IPython's history database 503 | history = InMemoryHistory() 504 | last_cell = u"" 505 | for _, _, cell in self.history_manager.get_tail(self.history_load_length, 506 | include_latest=True): 507 | # Ignore blank lines and consecutive duplicates 508 | cell = cell.rstrip() 509 | if cell and (cell != last_cell): 510 | history.append_string(cell) 511 | 512 | style_overrides = { 513 | Token.Prompt: '#009900', 514 | Token.PromptNum: '#00ff00 bold', 515 | Token.OutPrompt: '#ff2200', 516 | Token.OutPromptNum: '#ff0000 bold', 517 | Token.RemotePrompt: '#999900', 518 | } 519 | if self.highlighting_style: 520 | style_cls = get_style_by_name(self.highlighting_style) 521 | else: 522 | style_cls = get_style_by_name('default') 523 | # The default theme needs to be visible on both a dark background 524 | # and a light background, because we can't tell what the terminal 525 | # looks like. These tweaks to the default theme help with that. 526 | style_overrides.update({ 527 | Token.Number: '#007700', 528 | Token.Operator: 'noinherit', 529 | Token.String: '#BB6622', 530 | Token.Name.Function: '#2080D0', 531 | Token.Name.Class: 'bold #2080D0', 532 | Token.Name.Namespace: 'bold #2080D0', 533 | }) 534 | style_overrides.update(self.highlighting_style_overrides) 535 | style = merge_styles([ 536 | style_from_pygments_cls(style_cls), 537 | style_from_pygments_dict(style_overrides), 538 | ]) 539 | 540 | editing_mode = getattr(EditingMode, self.editing_mode.upper()) 541 | langinfo = self.kernel_info.get('language_info', {}) 542 | lexer = langinfo.get('pygments_lexer', langinfo.get('name', 'text')) 543 | 544 | # If enabled in the settings, highlight matching brackets 545 | # when the DEFAULT_BUFFER has the focus 546 | input_processors = [ConditionalProcessor( 547 | processor=HighlightMatchingBracketProcessor(chars='[](){}'), 548 | filter=has_focus(DEFAULT_BUFFER) & ~is_done & 549 | Condition(lambda: self.highlight_matching_brackets)) 550 | ] 551 | 552 | # Tell prompt_toolkit to use the asyncio event loop. 553 | # Obsolete in prompt_toolkit.v3 554 | if not PTK3: 555 | use_asyncio_event_loop() 556 | 557 | self.pt_cli = PromptSession( 558 | message=(lambda: PygmentsTokens(self.get_prompt_tokens())), 559 | multiline=True, 560 | complete_style=self.pt_complete_style, 561 | editing_mode=editing_mode, 562 | lexer=PygmentsLexer(get_pygments_lexer(lexer)), 563 | prompt_continuation=( 564 | lambda width, lineno, is_soft_wrap: PygmentsTokens( 565 | self.get_continuation_tokens(width) 566 | ) 567 | ), 568 | key_bindings=kb, 569 | history=history, 570 | completer=JupyterPTCompleter(self.Completer), 571 | enable_history_search=True, 572 | style=style, 573 | input_processors=input_processors, 574 | color_depth=(ColorDepth.TRUE_COLOR if self.true_color else None), 575 | ) 576 | 577 | async def prompt_for_code(self): 578 | if self.next_input: 579 | default = self.next_input 580 | self.next_input = None 581 | else: 582 | default = '' 583 | 584 | if PTK3: 585 | text = await self.pt_cli.prompt_async(default=default) 586 | else: 587 | text = await self.pt_cli.prompt(default=default, async_=True) 588 | 589 | return text 590 | 591 | def init_io(self): 592 | if sys.platform not in {'win32', 'cli'}: 593 | return 594 | 595 | import colorama 596 | colorama.init() 597 | 598 | def check_complete(self, code): 599 | if self.use_kernel_is_complete: 600 | msg_id = self.client.is_complete(code) 601 | try: 602 | return self.handle_is_complete_reply(msg_id, 603 | timeout=self.kernel_is_complete_timeout) 604 | except SyntaxError: 605 | return False, "" 606 | else: 607 | lines = code.splitlines() 608 | if len(lines): 609 | more = (lines[-1] != "") 610 | return more, "" 611 | else: 612 | return False, "" 613 | 614 | def ask_exit(self): 615 | self.keep_running = False 616 | 617 | # This is set from payloads in handle_execute_reply 618 | next_input = None 619 | 620 | def pre_prompt(self): 621 | if self.next_input: 622 | # We can't set the buffer here, because it will be reset just after 623 | # this. Adding a callable to pre_run_callables does what we need 624 | # after the buffer is reset. 625 | s = self.next_input 626 | 627 | def set_doc(): 628 | self.pt_cli.app.buffer.document = Document(s) 629 | if hasattr(self.pt_cli, 'pre_run_callables'): 630 | self.pt_cli.app.pre_run_callables.append(set_doc) 631 | else: 632 | # Older version of prompt_toolkit; it's OK to set the document 633 | # directly here. 634 | set_doc() 635 | self.next_input = None 636 | 637 | async def interact(self, loop=None, display_banner=None): 638 | while self.keep_running: 639 | print('\n', end='') 640 | 641 | try: 642 | code = await self.prompt_for_code() 643 | except EOFError: 644 | if (not self.confirm_exit) or \ 645 | ask_yes_no('Do you really want to exit ([y]/n)?', 'y', 'n'): 646 | self.ask_exit() 647 | 648 | else: 649 | if code: 650 | self.run_cell(code, store_history=True) 651 | 652 | async def _main_task(self): 653 | loop = asyncio.get_running_loop() 654 | tasks = [asyncio.create_task(self.interact(loop=loop))] 655 | 656 | if self.include_other_output: 657 | # only poll the iopub channel asynchronously if we 658 | # wish to include external content 659 | tasks.append(asyncio.create_task(self.handle_external_iopub(loop=loop))) 660 | 661 | _, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED) 662 | 663 | for task in pending: 664 | task.cancel() 665 | try: 666 | await asyncio.gather(*pending) 667 | except asyncio.CancelledError: 668 | pass 669 | 670 | def mainloop(self): 671 | self.keepkernel = not self.own_kernel 672 | # An extra layer of protection in case someone mashing Ctrl-C breaks 673 | # out of our internal code. 674 | while True: 675 | try: 676 | asyncio.run(self._main_task()) 677 | break 678 | except KeyboardInterrupt: 679 | print("\nKeyboardInterrupt escaped interact()\n") 680 | 681 | if self._eventloop: 682 | self._eventloop.close() 683 | if self.keepkernel and not self.own_kernel: 684 | print('keeping kernel alive') 685 | elif self.keepkernel and self.own_kernel: 686 | print("owning kernel, cannot keep it alive") 687 | self.client.shutdown() 688 | else: 689 | print("Shutting down kernel") 690 | self.client.shutdown() 691 | 692 | def run_cell(self, cell, store_history=True): 693 | """Run a complete IPython cell. 694 | 695 | Parameters 696 | ---------- 697 | cell : str 698 | The code (including IPython code such as %magic functions) to run. 699 | store_history : bool 700 | If True, the raw and translated cell will be stored in IPython's 701 | history. For user code calling back into IPython's machinery, this 702 | should be set to False. 703 | """ 704 | if (not cell) or cell.isspace(): 705 | # pressing enter flushes any pending display 706 | self.handle_iopub() 707 | return 708 | 709 | # flush stale replies, which could have been ignored, due to missed heartbeats 710 | while run_sync(self.client.shell_channel.msg_ready)(): 711 | run_sync(self.client.shell_channel.get_msg)() 712 | # execute takes 'hidden', which is the inverse of store_hist 713 | msg_id = self.client.execute(cell, not store_history) 714 | 715 | # first thing is wait for any side effects (output, stdin, etc.) 716 | self._executing = True 717 | self._execution_state = "busy" 718 | while self._execution_state != 'idle' and self.client.is_alive(): 719 | try: 720 | self.handle_input_request(msg_id, timeout=0.05) 721 | except Empty: 722 | # display intermediate print statements, etc. 723 | self.handle_iopub(msg_id) 724 | except ZMQError as e: 725 | # Carry on if polling was interrupted by a signal 726 | if e.errno != errno.EINTR: 727 | raise 728 | 729 | # after all of that is done, wait for the execute reply 730 | while self.client.is_alive(): 731 | try: 732 | self.handle_execute_reply(msg_id, timeout=0.05) 733 | except Empty: 734 | pass 735 | else: 736 | break 737 | self._executing = False 738 | 739 | #----------------- 740 | # message handlers 741 | #----------------- 742 | 743 | def handle_execute_reply(self, msg_id, timeout=None): 744 | kwargs = {"timeout": timeout} 745 | msg = run_sync(self.client.shell_channel.get_msg)(**kwargs) 746 | if msg["parent_header"].get("msg_id", None) == msg_id: 747 | 748 | self.handle_iopub(msg_id) 749 | 750 | content = msg["content"] 751 | status = content['status'] 752 | 753 | if status == "aborted": 754 | sys.stdout.write("Aborted\n") 755 | return 756 | elif status == 'ok': 757 | # handle payloads 758 | for item in content.get("payload", []): 759 | source = item['source'] 760 | if source == 'page': 761 | page.page(item['data']['text/plain']) 762 | elif source == 'set_next_input': 763 | self.next_input = item['text'] 764 | elif source == 'ask_exit': 765 | self.keepkernel = item.get('keepkernel', False) 766 | self.ask_exit() 767 | 768 | elif status == 'error': 769 | pass 770 | 771 | self.execution_count = int(content["execution_count"] + 1) 772 | 773 | def handle_is_complete_reply(self, msg_id, timeout=None): 774 | """ 775 | Wait for a repsonse from the kernel, and return two values: 776 | more? - (boolean) should the frontend ask for more input 777 | indent - an indent string to prefix the input 778 | Overloaded methods may want to examine the comeplete source. Its is 779 | in the self._source_lines_buffered list. 780 | """ 781 | ## Get the is_complete response: 782 | msg = None 783 | try: 784 | kwargs = {"timeout": timeout} 785 | msg = run_sync(self.client.shell_channel.get_msg)(**kwargs) 786 | except Empty: 787 | warn('The kernel did not respond to an is_complete_request. ' 788 | 'Setting `use_kernel_is_complete` to False.') 789 | self.use_kernel_is_complete = False 790 | return False, "" 791 | ## Handle response: 792 | if msg["parent_header"].get("msg_id", None) != msg_id: 793 | warn('The kernel did not respond properly to an is_complete_request: %s.' % str(msg)) 794 | return False, "" 795 | else: 796 | status = msg["content"].get("status", None) 797 | indent = msg["content"].get("indent", "") 798 | ## Return more? and indent string 799 | if status == "complete": 800 | return False, indent 801 | elif status == "incomplete": 802 | return True, indent 803 | elif status == "invalid": 804 | raise SyntaxError() 805 | elif status == "unknown": 806 | return False, indent 807 | else: 808 | warn('The kernel sent an invalid is_complete_reply status: "%s".' % status) 809 | return False, indent 810 | 811 | include_other_output = Bool(False, config=True, 812 | help="""Whether to include output from clients 813 | other than this one sharing the same kernel. 814 | """ 815 | ) 816 | other_output_prefix = Unicode("Remote ", config=True, 817 | help="""Prefix to add to outputs coming from clients other than this one. 818 | 819 | Only relevant if include_other_output is True. 820 | """ 821 | ) 822 | 823 | def from_here(self, msg): 824 | """Return whether a message is from this session""" 825 | return msg['parent_header'].get("session", self.session_id) == self.session_id 826 | 827 | def include_output(self, msg): 828 | """Return whether we should include a given output message""" 829 | from_here = self.from_here(msg) 830 | if msg['msg_type'] == 'execute_input': 831 | # only echo inputs not from here 832 | return self.include_other_output and not from_here 833 | 834 | if self.include_other_output: 835 | return True 836 | else: 837 | return from_here 838 | 839 | async def handle_external_iopub(self, loop=None): 840 | while self.keep_running: 841 | # we need to check for keep_running from time to time 842 | poll_result = await ensure_async(self.client.iopub_channel.socket.poll(0)) 843 | if poll_result: 844 | self.handle_iopub() 845 | await asyncio.sleep(0.5) 846 | 847 | def handle_iopub(self, msg_id=''): 848 | """Process messages on the IOPub channel 849 | 850 | This method consumes and processes messages on the IOPub channel, 851 | such as stdout, stderr, execute_result and status. 852 | 853 | It only displays output that is caused by this session. 854 | """ 855 | while run_sync(self.client.iopub_channel.msg_ready)(): 856 | sub_msg = run_sync(self.client.iopub_channel.get_msg)() 857 | msg_type = sub_msg['header']['msg_type'] 858 | 859 | # Update execution_count in case it changed in another session 860 | if msg_type == "execute_input": 861 | self.execution_count = int(sub_msg["content"]["execution_count"]) + 1 862 | 863 | if self.include_output(sub_msg): 864 | if msg_type == 'status': 865 | self._execution_state = sub_msg["content"]["execution_state"] 866 | 867 | elif msg_type == 'stream': 868 | if sub_msg["content"]["name"] == "stdout": 869 | if self._pending_clearoutput: 870 | print("\r", end="") 871 | self._pending_clearoutput = False 872 | print(sub_msg["content"]["text"], end="") 873 | sys.stdout.flush() 874 | elif sub_msg["content"]["name"] == "stderr": 875 | if self._pending_clearoutput: 876 | print("\r", file=sys.stderr, end="") 877 | self._pending_clearoutput = False 878 | print(sub_msg["content"]["text"], file=sys.stderr, end="") 879 | sys.stderr.flush() 880 | 881 | elif msg_type == 'execute_result': 882 | if self._pending_clearoutput: 883 | print("\r", end="") 884 | self._pending_clearoutput = False 885 | self.execution_count = int(sub_msg["content"]["execution_count"]) 886 | if not self.from_here(sub_msg): 887 | sys.stdout.write(self.other_output_prefix) 888 | format_dict = sub_msg["content"]["data"] 889 | self.handle_rich_data(format_dict) 890 | 891 | if 'text/plain' not in format_dict: 892 | continue 893 | 894 | # prompt_toolkit writes the prompt at a slightly lower level, 895 | # so flush streams first to ensure correct ordering. 896 | sys.stdout.flush() 897 | sys.stderr.flush() 898 | self.print_out_prompt() 899 | text_repr = format_dict['text/plain'] 900 | if '\n' in text_repr: 901 | # For multi-line results, start a new line after prompt 902 | print() 903 | print(text_repr) 904 | 905 | # Remote: add new prompt 906 | if not self.from_here(sub_msg): 907 | sys.stdout.write('\n') 908 | sys.stdout.flush() 909 | self.print_remote_prompt() 910 | 911 | elif msg_type == 'display_data': 912 | data = sub_msg["content"]["data"] 913 | handled = self.handle_rich_data(data) 914 | if not handled: 915 | if not self.from_here(sub_msg): 916 | sys.stdout.write(self.other_output_prefix) 917 | # if it was an image, we handled it by now 918 | if 'text/plain' in data: 919 | print(data['text/plain']) 920 | 921 | # If execute input: print it 922 | elif msg_type == 'execute_input': 923 | content = sub_msg['content'] 924 | ec = content.get('execution_count', self.execution_count - 1) 925 | 926 | # New line 927 | sys.stdout.write('\n') 928 | sys.stdout.flush() 929 | 930 | # With `Remote In [3]: ` 931 | self.print_remote_prompt(ec=ec) 932 | 933 | # And the code 934 | sys.stdout.write(content['code'] + '\n') 935 | 936 | elif msg_type == 'clear_output': 937 | if sub_msg["content"]["wait"]: 938 | self._pending_clearoutput = True 939 | else: 940 | print("\r", end="") 941 | 942 | elif msg_type == 'error': 943 | for frame in sub_msg["content"]["traceback"]: 944 | print(frame, file=sys.stderr) 945 | 946 | _imagemime = { 947 | 'image/png': 'png', 948 | 'image/jpeg': 'jpeg', 949 | 'image/svg+xml': 'svg', 950 | } 951 | 952 | def handle_rich_data(self, data): 953 | for mime in self.mime_preference: 954 | if mime in data and mime in self._imagemime: 955 | if self.handle_image(data, mime): 956 | return True 957 | return False 958 | 959 | def handle_image(self, data, mime): 960 | handler = getattr( 961 | self, 'handle_image_{0}'.format(self.image_handler), None) 962 | if handler: 963 | return handler(data, mime) 964 | 965 | def handle_image_PIL(self, data, mime): 966 | if mime not in ('image/png', 'image/jpeg'): 967 | return False 968 | try: 969 | from PIL import Image, ImageShow 970 | except ImportError: 971 | return False 972 | raw = base64.decodebytes(data[mime].encode('ascii')) 973 | img = Image.open(BytesIO(raw)) 974 | return ImageShow.show(img) 975 | 976 | def handle_image_stream(self, data, mime): 977 | raw = base64.decodebytes(data[mime].encode('ascii')) 978 | imageformat = self._imagemime[mime] 979 | fmt = dict(format=imageformat) 980 | args = [s.format(**fmt) for s in self.stream_image_handler] 981 | with subprocess.Popen(args, stdin=subprocess.PIPE, 982 | stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) as proc: 983 | proc.communicate(raw) 984 | return (proc.returncode == 0) 985 | 986 | def handle_image_tempfile(self, data, mime): 987 | raw = base64.decodebytes(data[mime].encode('ascii')) 988 | imageformat = self._imagemime[mime] 989 | filename = 'tmp.{0}'.format(imageformat) 990 | with TemporaryDirectory() as tempdir: 991 | fullpath = os.path.join(tempdir, filename) 992 | with open(fullpath, 'wb') as f: 993 | f.write(raw) 994 | fmt = dict(file=fullpath, format=imageformat) 995 | args = [s.format(**fmt) for s in self.tempfile_image_handler] 996 | rc = subprocess.call(args, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 997 | return (rc == 0) 998 | 999 | def handle_image_callable(self, data, mime): 1000 | res = self.callable_image_handler(data) 1001 | if res is not False: 1002 | # If handler func returns e.g. None, assume it has handled the data. 1003 | res = True 1004 | return res 1005 | 1006 | def handle_input_request(self, msg_id, timeout=0.1): 1007 | """ Method to capture raw_input 1008 | """ 1009 | req = run_sync(self.client.stdin_channel.get_msg)(timeout=timeout) 1010 | # in case any iopub came while we were waiting: 1011 | self.handle_iopub(msg_id) 1012 | if msg_id == req["parent_header"].get("msg_id"): 1013 | # wrap SIGINT handler 1014 | real_handler = signal.getsignal(signal.SIGINT) 1015 | 1016 | def double_int(sig, frame): 1017 | # call real handler (forwards sigint to kernel), 1018 | # then raise local interrupt, stopping local raw_input 1019 | real_handler(sig, frame) 1020 | raise KeyboardInterrupt 1021 | signal.signal(signal.SIGINT, double_int) 1022 | content = req['content'] 1023 | read = getpass if content.get('password', False) else input 1024 | try: 1025 | raw_data = read(content["prompt"]) 1026 | except EOFError: 1027 | # turn EOFError into EOF character 1028 | raw_data = '\x04' 1029 | except KeyboardInterrupt: 1030 | sys.stdout.write('\n') 1031 | return 1032 | finally: 1033 | # restore SIGINT handler 1034 | signal.signal(signal.SIGINT, real_handler) 1035 | 1036 | # only send stdin reply if there *was not* another request 1037 | # or execution finished while we were reading. 1038 | if not (run_sync(self.client.stdin_channel.msg_ready)() or 1039 | run_sync(self.client.shell_channel.msg_ready)()): 1040 | self.client.input(raw_data) 1041 | -------------------------------------------------------------------------------- /jupyter_console/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jupyter/jupyter_console/fddbc42d2e0be85feace1fe783a05e2b569fceae/jupyter_console/tests/__init__.py -------------------------------------------------------------------------------- /jupyter_console/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture(autouse=True) 5 | def env_setup(monkeypatch): 6 | monkeypatch.setenv("JUPYTER_CONSOLE_TEST", "1") 7 | -------------------------------------------------------------------------------- /jupyter_console/tests/test_console.py: -------------------------------------------------------------------------------- 1 | """Tests for two-process terminal frontend""" 2 | 3 | # Copyright (c) Jupyter Development Team. 4 | # Distributed under the terms of the Modified BSD License. 5 | 6 | import os 7 | import shutil 8 | import sys 9 | import tempfile 10 | from subprocess import check_output 11 | 12 | from flaky import flaky 13 | import pytest 14 | 15 | from traitlets.tests.utils import check_help_all_output 16 | 17 | 18 | should_skip = sys.platform == "win32" or sys.version_info < (3,8) or sys.version_info[:2] == (3, 10) # noqa 19 | 20 | 21 | @flaky 22 | @pytest.mark.skipif(should_skip, reason="not supported") 23 | def test_console_starts(): 24 | """test that `jupyter console` starts a terminal""" 25 | p, pexpect, t = start_console() 26 | p.sendline("5") 27 | p.expect([r"Out\[\d+\]: 5", pexpect.EOF], timeout=t) 28 | p.expect([r"In \[\d+\]", pexpect.EOF], timeout=t) 29 | stop_console(p, pexpect, t) 30 | 31 | def test_help_output(): 32 | """jupyter console --help-all works""" 33 | check_help_all_output('jupyter_console') 34 | 35 | 36 | @flaky 37 | @pytest.mark.skipif(should_skip, reason="not supported") 38 | def test_display_text(): 39 | "Ensure display protocol plain/text key is supported" 40 | # equivalent of: 41 | # 42 | # x = %lsmagic 43 | # from IPython.display import display; display(x); 44 | p, pexpect, t = start_console() 45 | p.sendline('x = %lsmagic') 46 | p.expect(r'In \[\d+\]', timeout=t) 47 | p.sendline('from IPython.display import display; display(x);') 48 | p.expect(r'Available line magics:', timeout=t) 49 | p.expect(r'In \[\d+\]', timeout=t) 50 | stop_console(p, pexpect, t) 51 | 52 | def stop_console(p, pexpect, t): 53 | "Stop a running `jupyter console` running via pexpect" 54 | # send ctrl-D;ctrl-D to exit 55 | p.sendeof() 56 | p.sendeof() 57 | p.expect([pexpect.EOF, pexpect.TIMEOUT], timeout=t) 58 | if p.isalive(): 59 | p.terminate() 60 | 61 | 62 | def start_console(): 63 | "Start `jupyter console` using pexpect" 64 | import pexpect 65 | 66 | args = ['-m', 'jupyter_console', '--colors=NoColor'] 67 | cmd = sys.executable 68 | env = os.environ.copy() 69 | env["JUPYTER_CONSOLE_TEST"] = "1" 70 | env["PROMPT_TOOLKIT_NO_CPR"] = "1" 71 | 72 | try: 73 | p = pexpect.spawn(cmd, args=args, env=env) 74 | except IOError: 75 | pytest.skip("Couldn't find command %s" % cmd) 76 | 77 | # timeout after two minutes 78 | t = 120 79 | p.expect(r"In \[\d+\]", timeout=t) 80 | return p, pexpect, t 81 | 82 | 83 | def test_multiprocessing(): 84 | p, pexpect, t = start_console() 85 | p.sendline('') 86 | 87 | 88 | def test_generate_config(): 89 | """jupyter console --generate-config works""" 90 | td = tempfile.mkdtemp() 91 | try: 92 | check_output([sys.executable, '-m', 'jupyter_console', '--generate-config'], 93 | env={'JUPYTER_CONFIG_DIR': td}, 94 | ) 95 | assert os.path.isfile(os.path.join(td, 'jupyter_console_config.py')) 96 | finally: 97 | shutil.rmtree(td) 98 | -------------------------------------------------------------------------------- /jupyter_console/tests/test_image_handler.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) IPython Development Team. 2 | # Distributed under the terms of the Modified BSD License. 3 | 4 | import base64 5 | import os 6 | import sys 7 | from tempfile import TemporaryDirectory 8 | import unittest 9 | from unittest.mock import patch 10 | 11 | import pytest 12 | 13 | from jupyter_console.ptshell import ZMQTerminalInteractiveShell 14 | 15 | 16 | SCRIPT_PATH = os.path.join( 17 | os.path.abspath(os.path.dirname(__file__)), 'writetofile.py') 18 | 19 | class NonCommunicatingShell(ZMQTerminalInteractiveShell): 20 | """A testing shell class that doesn't attempt to communicate with the kernel""" 21 | def init_kernel_info(self): 22 | pass 23 | 24 | 25 | class ZMQTerminalInteractiveShellTestCase(unittest.TestCase): 26 | 27 | def setUp(self): 28 | self.shell = NonCommunicatingShell() 29 | self.raw = b'dummy data' 30 | self.mime = 'image/png' 31 | self.data = {self.mime: base64.encodebytes(self.raw).decode('ascii')} 32 | 33 | def test_call_pil_by_default(self): 34 | pil_called_with = [] 35 | 36 | def pil_called(data, mime): 37 | pil_called_with.append(data) 38 | 39 | def raise_if_called(*args, **kwds): 40 | assert False 41 | 42 | shell = self.shell 43 | shell.handle_image_PIL = pil_called 44 | shell.handle_image_stream = raise_if_called 45 | shell.handle_image_tempfile = raise_if_called 46 | shell.handle_image_callable = raise_if_called 47 | 48 | shell.handle_image(None, None) # arguments are dummy 49 | assert len(pil_called_with) == 1 50 | 51 | def test_handle_image_PIL(self): 52 | pytest.importorskip('PIL') 53 | from PIL import Image, ImageShow 54 | 55 | open_called_with = [] 56 | show_called_with = [] 57 | 58 | def fake_open(arg): 59 | open_called_with.append(arg) 60 | 61 | def fake_show(img): 62 | show_called_with.append(img) 63 | 64 | with patch.object(Image, 'open', fake_open), \ 65 | patch.object(ImageShow, 'show', fake_show): 66 | self.shell.handle_image_PIL(self.data, self.mime) 67 | 68 | self.assertEqual(len(open_called_with), 1) 69 | self.assertEqual(len(show_called_with), 1) 70 | self.assertEqual(open_called_with[0].getvalue(), self.raw) 71 | 72 | def check_handler_with_file(self, inpath, handler): 73 | shell = self.shell 74 | configname = '{0}_image_handler'.format(handler) 75 | funcname = 'handle_image_{0}'.format(handler) 76 | 77 | assert hasattr(shell, configname) 78 | assert hasattr(shell, funcname) 79 | 80 | with TemporaryDirectory() as tmpdir: 81 | outpath = os.path.join(tmpdir, 'data') 82 | cmd = [sys.executable, SCRIPT_PATH, inpath, outpath] 83 | setattr(shell, configname, cmd) 84 | getattr(shell, funcname)(self.data, self.mime) 85 | # cmd is called and file is closed. So it's safe to open now. 86 | with open(outpath, 'rb') as file: 87 | transferred = file.read() 88 | 89 | self.assertEqual(transferred, self.raw) 90 | 91 | def test_handle_image_stream(self): 92 | self.check_handler_with_file('-', 'stream') 93 | 94 | def test_handle_image_tempfile(self): 95 | self.check_handler_with_file('{file}', 'tempfile') 96 | 97 | def test_handle_image_callable(self): 98 | called_with = [] 99 | self.shell.callable_image_handler = called_with.append 100 | self.shell.handle_image_callable(self.data, self.mime) 101 | self.assertEqual(len(called_with), 1) 102 | assert called_with[0] is self.data 103 | -------------------------------------------------------------------------------- /jupyter_console/tests/writetofile.py: -------------------------------------------------------------------------------- 1 | #----------------------------------------------------------------------------- 2 | # Copyright (C) 2012 The IPython Development Team 3 | # 4 | # Distributed under the terms of the BSD License. The full license is in 5 | # the file LICENSE, distributed as part of this software. 6 | #----------------------------------------------------------------------------- 7 | 8 | """ 9 | Copy data from input file to output file for testing. 10 | 11 | Command line usage: 12 | 13 | python writetofile.py INPUT OUTPUT 14 | 15 | Binary data from INPUT file is copied to OUTPUT file. 16 | If INPUT is '-', stdin is used. 17 | 18 | """ 19 | 20 | if __name__ == '__main__': 21 | import sys 22 | (inpath, outpath) = sys.argv[1:] 23 | 24 | if inpath == '-': 25 | infile = sys.stdin.buffer 26 | else: 27 | infile = open(inpath, 'rb') 28 | 29 | open(outpath, 'w+b').write(infile.read()) 30 | -------------------------------------------------------------------------------- /jupyter_console/utils.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import typing as t 3 | from jupyter_core.utils import run_sync as _run_sync, ensure_async # noqa 4 | 5 | 6 | T = t.TypeVar("T") 7 | 8 | 9 | def run_sync(coro: t.Callable[..., t.Union[T, t.Awaitable[T]]]) -> t.Callable[..., T]: 10 | """Wraps coroutine in a function that blocks until it has executed. 11 | 12 | Parameters 13 | ---------- 14 | coro : coroutine-function 15 | The coroutine-function to be executed. 16 | 17 | Returns 18 | ------- 19 | result : 20 | Whatever the coroutine-function returns. 21 | """ 22 | if not inspect.iscoroutinefunction(coro): 23 | return t.cast(t.Callable[..., T], coro) 24 | return _run_sync(coro) 25 | 26 | -------------------------------------------------------------------------------- /jupyter_console/zmqhistory.py: -------------------------------------------------------------------------------- 1 | """ ZMQ Kernel History accessor and manager. """ 2 | # ----------------------------------------------------------------------------- 3 | # Copyright (C) 2010-2011 The IPython Development Team. 4 | # 5 | # Distributed under the terms of the BSD License. 6 | # 7 | # The full license is in the file LICENSE, distributed with this software. 8 | # ----------------------------------------------------------------------------- 9 | 10 | # ----------------------------------------------------------------------------- 11 | # Imports 12 | # ----------------------------------------------------------------------------- 13 | 14 | from IPython.core.history import HistoryAccessorBase 15 | from traitlets import Dict, List 16 | 17 | from queue import Empty # Py 3 18 | 19 | 20 | class ZMQHistoryManager(HistoryAccessorBase): 21 | """History accessor and manager for ZMQ-based kernels""" 22 | input_hist_parsed = List([""]) 23 | output_hist = Dict() 24 | dir_hist = List() 25 | output_hist_reprs = Dict() 26 | 27 | def __init__(self, client): 28 | """ 29 | Class to load the command-line history from a ZMQ-based kernel, 30 | and access the history. 31 | 32 | Parameters 33 | ---------- 34 | 35 | client: `IPython.kernel.KernelClient` 36 | The kernel client in order to request the history. 37 | """ 38 | self.client = client 39 | 40 | def _load_history(self, raw=True, output=False, hist_access_type='range', 41 | **kwargs): 42 | """ 43 | Load the history over ZMQ from the kernel. Wraps the history 44 | messaging with loop to wait to get history results. 45 | """ 46 | history = [] 47 | if hasattr(self.client, "history"): 48 | # In tests, KernelClient may not have a history method 49 | msg_id = self.client.history(raw=raw, output=output, 50 | hist_access_type=hist_access_type, 51 | **kwargs) 52 | while True: 53 | try: 54 | reply = self.client.get_shell_msg(timeout=1) 55 | except Empty: 56 | break 57 | else: 58 | if reply['parent_header'].get('msg_id') == msg_id: 59 | history = reply['content'].get('history', []) 60 | break 61 | return history 62 | 63 | def get_tail(self, n=10, raw=True, output=False, include_latest=False): 64 | return self._load_history(hist_access_type='tail', n=n, raw=raw, 65 | output=output) 66 | 67 | def search(self, pattern="*", raw=True, search_raw=True, 68 | output=False, n=None, unique=False): 69 | return self._load_history(hist_access_type='search', pattern=pattern, 70 | raw=raw, search_raw=search_raw, 71 | output=output, n=n, unique=unique) 72 | 73 | def get_range(self, session, start=1, stop=None, raw=True, output=False): 74 | return self._load_history(hist_access_type='range', raw=raw, 75 | output=output, start=start, stop=stop, 76 | session=session) 77 | 78 | def get_range_by_str(self, rangestr, raw=True, output=False): 79 | return self._load_history(hist_access_type='range', raw=raw, 80 | output=output, rangestr=rangestr) 81 | 82 | def end_session(self): 83 | """ 84 | Nothing to do for ZMQ-based histories. 85 | """ 86 | pass 87 | 88 | def reset(self, new_session=True): 89 | """ 90 | Nothing to do for ZMQ-based histories. 91 | """ 92 | pass 93 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling>=1.5"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "jupyter-console" 7 | dynamic = ["version"] 8 | description = "Jupyter terminal console" 9 | readme = "README.md" 10 | license = { file= "LICENSE" } 11 | requires-python = ">=3.7" 12 | authors = [ 13 | { name = "Jupyter Development Team", email = "jupyter@googlegroups.com" }, 14 | ] 15 | keywords = [ 16 | "Interactive", 17 | "Interpreter", 18 | "Shell", 19 | "Web", 20 | ] 21 | classifiers = [ 22 | "Intended Audience :: Developers", 23 | "Intended Audience :: Science/Research", 24 | "Intended Audience :: System Administrators", 25 | "License :: OSI Approved :: BSD License", 26 | "Programming Language :: Python", 27 | "Programming Language :: Python :: 3", 28 | "Programming Language :: Python :: 3.7", 29 | "Programming Language :: Python :: 3.8", 30 | "Programming Language :: Python :: 3.9", 31 | "Programming Language :: Python :: 3.10", 32 | "Programming Language :: Python :: 3.11", 33 | ] 34 | dependencies = [ 35 | "ipykernel>=6.14", 36 | "ipython", 37 | "jupyter_client>=7.0.0", 38 | "jupyter_core>=4.12,!=5.0.*", 39 | "prompt_toolkit>=3.0.30", 40 | "pygments", 41 | "pyzmq>=17", 42 | "traitlets>=5.4", 43 | ] 44 | 45 | [project.optional-dependencies] 46 | test = [ 47 | "pexpect", 48 | "pytest", 49 | "flaky", 50 | ] 51 | 52 | [project.scripts] 53 | jupyter-console = "jupyter_console.app:main" 54 | 55 | [project.urls] 56 | Homepage = "https://jupyter.org" 57 | 58 | [tool.hatch.version] 59 | path = "jupyter_console/_version.py" 60 | 61 | [tool.pytest.ini_options] 62 | addopts = "--durations=10" 63 | 64 | [tool.flake8] 65 | max-line-length = "99" 66 | ignore = "W291, E266, E265, E128, E251, E402, E124, E302, W293, E231, E222, W503, E126, E121, W391, E226, E127, W504" 67 | 68 | [tool.mypy] 69 | python_version = "3.8" 70 | ignore_missing_imports = true 71 | follow_imports = "silent" 72 | -------------------------------------------------------------------------------- /scripts/jupyter-console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from jupyter_console import app 3 | 4 | if __name__ == '__main__': 5 | app.main() 6 | --------------------------------------------------------------------------------