├── .bumpversion.cfg ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── devtools ├── README.md ├── Triage messages.ipynb └── unpack_har.py ├── integration-tests └── basic-execution.ipynb ├── pick_kernel ├── __init__.py ├── __main__.py ├── exceptions.py ├── kernel.py ├── kernelbase.py ├── kernelspec.py ├── subkernels.py ├── tests │ ├── __init__.py │ └── test_subkernel.py └── version.py ├── requirements-dev.txt ├── requirements.txt ├── setup.py └── tox.ini /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.0.4 3 | commit = True 4 | tag = True 5 | tag_name = {new_version} 6 | 7 | [bumpversion:file:pick_kernel/version.py] 8 | 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Any har files 132 | *.har 133 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | sudo: false 3 | matrix: 4 | include: 5 | - python: 3.7 6 | env: TOXENV=py37 7 | - python: 3.8 8 | env: TOXENV=py38 9 | install: 10 | - pip install tox coverage codecov 11 | script: 12 | - tox -e $TOXENV 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | Examples of behavior that contributes to creating a positive environment include: 8 | 9 | - Using welcoming and inclusive language 10 | - Being respectful of differing viewpoints and experiences 11 | - Gracefully accepting constructive criticism 12 | - Focusing on what is best for the community 13 | - Showing empathy towards other community members 14 | 15 | Examples of unacceptable behavior by participants include: 16 | 17 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 18 | - Trolling, insulting/derogatory comments, and personal or political attacks 19 | - Public or private harassment 20 | - Publishing others’ private information, such as a physical or electronic address, without explicit permission 21 | - Other conduct which could reasonably be considered inappropriate in a professional setting 22 | 23 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 24 | 25 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 26 | 27 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. 28 | 29 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 30 | 31 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer at [rgbkrk@gmail.com]. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. 32 | 33 | This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available from http://contributor-covenant.org/version/1/4/ 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Kyle Kelley 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![pick](https://user-images.githubusercontent.com/836375/80047181-94e7df80-84c1-11ea-9fea-f2d8f0fc0258.png) 2 | 3 | Customize your kernels on launch! 4 | 5 | **Pick**'s magic lets you customize your virtual environments, conda environments, and Jupyter kernels. If you have mountains of data or models, you can use Pick to easily climb to peak efficiency -- composable, on-the-fly environments and kernels created within a notebook cell. 6 | 7 | _Not ready for wide installation yet. If you're ready to try it out, start with the development version detailed below._ 8 | 9 | ## Requirements 10 | 11 | - Python 3.7 12 | 13 | ## Development 14 | 15 | Clone the repository and install it in dev mode: 16 | 17 | ``` 18 | git clone https://github.com/rgbkrk/pick 19 | cd pick 20 | pip3 install -e . 21 | ``` 22 | 23 | Create the kernelspec 24 | 25 | ``` 26 | pick-install --user 27 | ``` 28 | 29 | ## Purpose & Background 30 | 31 | When working in a notebook, we want a way to choose resources to launch in the background, create a kernel environment, and inform the user when the kernel and custom resources are ready. Creating a conda environment, launching a spark cluster, or running an ipython kernel inside of a conda environment are examples of things we wanted to do from within a notebook. 32 | 33 | When we were looking at initial design, we considered the approach of changing the Jupyter APIs to allow setting additional parameters on launch. This would have required changes at the `/api/kernelspecs`, `/api/kernel`, each of the UIs, and even the way that kernels are launched by the notebook server, jupyter client, jupyter console, and papermill. Possible, but we wanted something simpler for notebook users and developers. 34 | 35 | Instead, we decided to use magic -- kernel magic. In the style of other cell magics, we wanted Pick to give users a flexible kernel magic in a notebook cell. 36 | 37 | As an example, here's how a kernel magic for creating a kernel that uses a conda environment would work: 38 | 39 | ``` 40 | %%kernel.conda-environment 41 | 42 | name: example-environment 43 | channels: 44 | - conda-forge 45 | dependencies: 46 | - numpy 47 | - psutil 48 | - matplotlib 49 | - dill 50 | - pandas 51 | - bokeh 52 | - dask 53 | ``` 54 | 55 | In action, it works like this: 56 | 57 | ![image](https://user-images.githubusercontent.com/836375/80048778-0164dd80-84c6-11ea-8d6c-a90ec45aefcc.png) 58 | 59 | While Pick is working to get the magic's underlying kernel ready, Pick emits logs to the frontend and keeps the notebook UI responsive. 60 | 61 | A similar example with Pick and kernel magics includes setting up spark and its associated environment variables in advance of launching the ipython kernel. Pick opens up possibilities for other creative ways of starting a kernel from an environment. 62 | 63 | ## Truth: It's a kernel proxy! 64 | 65 | Pick's design focuses on making it really easy for a user to configure a kernel without having to mess with extensions across all the myriad jupyter projects or executing tasks from the command line. Pick works as a regular Jupyter kernel with the additional ability to create, customize, and manage a "child" kernel. 66 | 67 | We set up Pick's initial communications protocol with the jupyter client, whether that be the notebook server or papermill. Next, the `%%kernel.*` magic runs, and we launch the "real" kernel. 68 | 69 | ![image](https://user-images.githubusercontent.com/836375/80049663-5b66a280-84c8-11ea-8b21-b1be6481b053.png) 70 | -------------------------------------------------------------------------------- /devtools/README.md: -------------------------------------------------------------------------------- 1 | # Devtools 2 | 3 | `unpack-har.py` will take a HAR file with Jupyter websocket messages and produce an easier to work with JSON file of just the websocket messages in this format: 4 | 5 | ``` 6 | { 7 | "message": { 8 | "header": { 9 | "msg_id": "55f747b71a324c8f8aff32b77174ea59", 10 | "username": "username", 11 | "session": "00f9e2047f51428289b37ee7e40a3e70", 12 | "msg_type": "kernel_info_request", 13 | "version": "5.2" 14 | }, 15 | "metadata": {}, 16 | "content": {}, 17 | "buffers": [], 18 | "parent_header": {}, 19 | "channel": "shell" 20 | }, 21 | "type": "send", 22 | "time": 1603477338.5674412 23 | }, 24 | ``` 25 | 26 | ## Getting the HAR 27 | 28 | To get the HAR file, open the notebook page with the Network tab open in the chrome console. Do everything you want to capture, then select "WS" and right click on the channels and click "Save all as HAR with content". 29 | 30 | ![image](https://user-images.githubusercontent.com/836375/97236537-89dc7780-17a2-11eb-8233-1a383e55a770.png) 31 | -------------------------------------------------------------------------------- /devtools/Triage messages.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import json\n", 10 | "from IPython.display import JSON, HTML\n", 11 | "from unpack_har import unpack_messages" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "with open(\"./kernel-messages.har\") as h:\n", 21 | " messages = unpack_messages(json.load(h))" 22 | ] 23 | }, 24 | { 25 | "cell_type": "code", 26 | "execution_count": 3, 27 | "metadata": {}, 28 | "outputs": [ 29 | { 30 | "data": { 31 | "text/plain": [ 32 | "{'payload': {'header': {'msg_id': '55f747b71a324c8f8aff32b77174ea59',\n", 33 | " 'username': 'username',\n", 34 | " 'session': '00f9e2047f51428289b37ee7e40a3e70',\n", 35 | " 'msg_type': 'kernel_info_request',\n", 36 | " 'version': '5.2'},\n", 37 | " 'metadata': {},\n", 38 | " 'content': {},\n", 39 | " 'buffers': [],\n", 40 | " 'parent_header': {},\n", 41 | " 'channel': 'shell'},\n", 42 | " 'type': 'send',\n", 43 | " 'time': 1603477338.5674412}" 44 | ] 45 | }, 46 | "execution_count": 3, 47 | "metadata": {}, 48 | "output_type": "execute_result" 49 | } 50 | ], 51 | "source": [ 52 | "messages[0]" 53 | ] 54 | }, 55 | { 56 | "cell_type": "code", 57 | "execution_count": 156, 58 | "metadata": { 59 | "scrolled": false 60 | }, 61 | "outputs": [ 62 | { 63 | "data": { 64 | "text/html": [ 65 | "
\n", 66 | "
\n", 67 | " kernel_info_request \n", 68 | " \n", 69 | "
\n",
  70 |        "    {\n",
  71 |        "    \"payload\": {\n",
  72 |        "        \"header\": {\n",
  73 |        "            \"msg_id\": \"55f747b71a324c8f8aff32b77174ea59\",\n",
  74 |        "            \"username\": \"username\",\n",
  75 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
  76 |        "            \"msg_type\": \"kernel_info_request\",\n",
  77 |        "            \"version\": \"5.2\"\n",
  78 |        "        },\n",
  79 |        "        \"metadata\": {},\n",
  80 |        "        \"content\": {},\n",
  81 |        "        \"buffers\": [],\n",
  82 |        "        \"parent_header\": {},\n",
  83 |        "        \"channel\": \"shell\"\n",
  84 |        "    },\n",
  85 |        "    \"type\": \"send\",\n",
  86 |        "    \"time\": 1603477338.5674412\n",
  87 |        "}\n",
  88 |        "    
\n", 89 | "
\n", 90 | " \n", 91 | " \n", 92 | "
" 93 | ], 94 | "text/plain": [ 95 | "" 96 | ] 97 | }, 98 | "metadata": {}, 99 | "output_type": "display_data" 100 | }, 101 | { 102 | "data": { 103 | "text/html": [ 104 | "
\n", 105 | "
\n", 106 | " status ⛏\n", 107 | " \n", 108 | "
\n",
 109 |        "    {\n",
 110 |        "    \"payload\": {\n",
 111 |        "        \"header\": {\n",
 112 |        "            \"msg_id\": \"4fc0537c-931a491942536b3213eecb89_24\",\n",
 113 |        "            \"msg_type\": \"status\",\n",
 114 |        "            \"username\": \"kylek\",\n",
 115 |        "            \"session\": \"4fc0537c-931a491942536b3213eecb89\",\n",
 116 |        "            \"date\": \"2020-10-23T18:22:17.716769Z\",\n",
 117 |        "            \"version\": \"5.3\"\n",
 118 |        "        },\n",
 119 |        "        \"msg_id\": \"4fc0537c-931a491942536b3213eecb89_24\",\n",
 120 |        "        \"msg_type\": \"status\",\n",
 121 |        "        \"parent_header\": {\n",
 122 |        "            \"msg_id\": \"05b26982-468f3a92db94b93716413a6d_4\",\n",
 123 |        "            \"msg_type\": \"shutdown_request\",\n",
 124 |        "            \"username\": \"kylek\",\n",
 125 |        "            \"session\": \"05b26982-468f3a92db94b93716413a6d\",\n",
 126 |        "            \"date\": \"2020-10-23T18:22:17.715751Z\",\n",
 127 |        "            \"version\": \"5.3\"\n",
 128 |        "        },\n",
 129 |        "        \"metadata\": {\n",
 130 |        "            \"picky\": true\n",
 131 |        "        },\n",
 132 |        "        \"content\": {\n",
 133 |        "            \"execution_state\": \"busy\"\n",
 134 |        "        },\n",
 135 |        "        \"buffers\": [],\n",
 136 |        "        \"channel\": \"iopub\"\n",
 137 |        "    },\n",
 138 |        "    \"type\": \"receive\",\n",
 139 |        "    \"time\": 1603477338.5678272\n",
 140 |        "}\n",
 141 |        "    
\n", 142 | "
\n", 143 | " \n", 144 | " busy\n", 145 | "
" 146 | ], 147 | "text/plain": [ 148 | "" 149 | ] 150 | }, 151 | "metadata": {}, 152 | "output_type": "display_data" 153 | }, 154 | { 155 | "data": { 156 | "text/html": [ 157 | "
\n", 158 | "
\n", 159 | " shutdown_reply \n", 160 | " \n", 161 | "
\n",
 162 |        "    {\n",
 163 |        "    \"payload\": {\n",
 164 |        "        \"header\": {\n",
 165 |        "            \"msg_id\": \"4fc0537c-931a491942536b3213eecb89_26\",\n",
 166 |        "            \"msg_type\": \"shutdown_reply\",\n",
 167 |        "            \"username\": \"kylek\",\n",
 168 |        "            \"session\": \"4fc0537c-931a491942536b3213eecb89\",\n",
 169 |        "            \"date\": \"2020-10-23T18:22:17.717163Z\",\n",
 170 |        "            \"version\": \"5.3\"\n",
 171 |        "        },\n",
 172 |        "        \"msg_id\": \"4fc0537c-931a491942536b3213eecb89_26\",\n",
 173 |        "        \"msg_type\": \"shutdown_reply\",\n",
 174 |        "        \"parent_header\": {\n",
 175 |        "            \"msg_id\": \"05b26982-468f3a92db94b93716413a6d_4\",\n",
 176 |        "            \"msg_type\": \"shutdown_request\",\n",
 177 |        "            \"username\": \"kylek\",\n",
 178 |        "            \"session\": \"05b26982-468f3a92db94b93716413a6d\",\n",
 179 |        "            \"date\": \"2020-10-23T18:22:17.715751Z\",\n",
 180 |        "            \"version\": \"5.3\"\n",
 181 |        "        },\n",
 182 |        "        \"metadata\": {},\n",
 183 |        "        \"content\": {\n",
 184 |        "            \"status\": \"ok\",\n",
 185 |        "            \"restart\": true\n",
 186 |        "        },\n",
 187 |        "        \"buffers\": [],\n",
 188 |        "        \"channel\": \"iopub\"\n",
 189 |        "    },\n",
 190 |        "    \"type\": \"receive\",\n",
 191 |        "    \"time\": 1603477338.5704582\n",
 192 |        "}\n",
 193 |        "    
\n", 194 | "
\n", 195 | " \n", 196 | " \n", 197 | "
" 198 | ], 199 | "text/plain": [ 200 | "" 201 | ] 202 | }, 203 | "metadata": {}, 204 | "output_type": "display_data" 205 | }, 206 | { 207 | "data": { 208 | "text/html": [ 209 | "
\n", 210 | "
\n", 211 | " status ⛏\n", 212 | " \n", 213 | "
\n",
 214 |        "    {\n",
 215 |        "    \"payload\": {\n",
 216 |        "        \"header\": {\n",
 217 |        "            \"msg_id\": \"4fc0537c-931a491942536b3213eecb89_27\",\n",
 218 |        "            \"msg_type\": \"status\",\n",
 219 |        "            \"username\": \"kylek\",\n",
 220 |        "            \"session\": \"4fc0537c-931a491942536b3213eecb89\",\n",
 221 |        "            \"date\": \"2020-10-23T18:22:17.718477Z\",\n",
 222 |        "            \"version\": \"5.3\"\n",
 223 |        "        },\n",
 224 |        "        \"msg_id\": \"4fc0537c-931a491942536b3213eecb89_27\",\n",
 225 |        "        \"msg_type\": \"status\",\n",
 226 |        "        \"parent_header\": {\n",
 227 |        "            \"msg_id\": \"05b26982-468f3a92db94b93716413a6d_4\",\n",
 228 |        "            \"msg_type\": \"shutdown_request\",\n",
 229 |        "            \"username\": \"kylek\",\n",
 230 |        "            \"session\": \"05b26982-468f3a92db94b93716413a6d\",\n",
 231 |        "            \"date\": \"2020-10-23T18:22:17.715751Z\",\n",
 232 |        "            \"version\": \"5.3\"\n",
 233 |        "        },\n",
 234 |        "        \"metadata\": {\n",
 235 |        "            \"picky\": true\n",
 236 |        "        },\n",
 237 |        "        \"content\": {\n",
 238 |        "            \"execution_state\": \"idle\"\n",
 239 |        "        },\n",
 240 |        "        \"buffers\": [],\n",
 241 |        "        \"channel\": \"iopub\"\n",
 242 |        "    },\n",
 243 |        "    \"type\": \"receive\",\n",
 244 |        "    \"time\": 1603477338.5707362\n",
 245 |        "}\n",
 246 |        "    
\n", 247 | "
\n", 248 | " \n", 249 | " idle\n", 250 | "
" 251 | ], 252 | "text/plain": [ 253 | "" 254 | ] 255 | }, 256 | "metadata": {}, 257 | "output_type": "display_data" 258 | }, 259 | { 260 | "data": { 261 | "text/html": [ 262 | "
\n", 263 | "
\n", 264 | " status ⛏\n", 265 | " \n", 266 | "
\n",
 267 |        "    {\n",
 268 |        "    \"payload\": {\n",
 269 |        "        \"header\": {\n",
 270 |        "            \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_1\",\n",
 271 |        "            \"msg_type\": \"status\",\n",
 272 |        "            \"username\": \"kylek\",\n",
 273 |        "            \"session\": \"944eddb8-eadbf46080214a953ceb1055\",\n",
 274 |        "            \"date\": \"2020-10-23T18:22:18.550266Z\",\n",
 275 |        "            \"version\": \"5.3\"\n",
 276 |        "        },\n",
 277 |        "        \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_1\",\n",
 278 |        "        \"msg_type\": \"status\",\n",
 279 |        "        \"parent_header\": {\n",
 280 |        "            \"msg_id\": \"05b26982-468f3a92db94b93716413a6d_5\",\n",
 281 |        "            \"msg_type\": \"kernel_info_request\",\n",
 282 |        "            \"username\": \"kylek\",\n",
 283 |        "            \"session\": \"05b26982-468f3a92db94b93716413a6d\",\n",
 284 |        "            \"date\": \"2020-10-23T18:22:17.930106Z\",\n",
 285 |        "            \"version\": \"5.3\"\n",
 286 |        "        },\n",
 287 |        "        \"metadata\": {\n",
 288 |        "            \"picky\": true\n",
 289 |        "        },\n",
 290 |        "        \"content\": {\n",
 291 |        "            \"execution_state\": \"busy\"\n",
 292 |        "        },\n",
 293 |        "        \"buffers\": [],\n",
 294 |        "        \"channel\": \"iopub\"\n",
 295 |        "    },\n",
 296 |        "    \"type\": \"receive\",\n",
 297 |        "    \"time\": 1603477338.5715992\n",
 298 |        "}\n",
 299 |        "    
\n", 300 | "
\n", 301 | " \n", 302 | " busy\n", 303 | "
" 304 | ], 305 | "text/plain": [ 306 | "" 307 | ] 308 | }, 309 | "metadata": {}, 310 | "output_type": "display_data" 311 | }, 312 | { 313 | "data": { 314 | "text/html": [ 315 | "
\n", 316 | "
\n", 317 | " status ⛏\n", 318 | " \n", 319 | "
\n",
 320 |        "    {\n",
 321 |        "    \"payload\": {\n",
 322 |        "        \"header\": {\n",
 323 |        "            \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_3\",\n",
 324 |        "            \"msg_type\": \"status\",\n",
 325 |        "            \"username\": \"kylek\",\n",
 326 |        "            \"session\": \"944eddb8-eadbf46080214a953ceb1055\",\n",
 327 |        "            \"date\": \"2020-10-23T18:22:18.551197Z\",\n",
 328 |        "            \"version\": \"5.3\"\n",
 329 |        "        },\n",
 330 |        "        \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_3\",\n",
 331 |        "        \"msg_type\": \"status\",\n",
 332 |        "        \"parent_header\": {\n",
 333 |        "            \"msg_id\": \"05b26982-468f3a92db94b93716413a6d_5\",\n",
 334 |        "            \"msg_type\": \"kernel_info_request\",\n",
 335 |        "            \"username\": \"kylek\",\n",
 336 |        "            \"session\": \"05b26982-468f3a92db94b93716413a6d\",\n",
 337 |        "            \"date\": \"2020-10-23T18:22:17.930106Z\",\n",
 338 |        "            \"version\": \"5.3\"\n",
 339 |        "        },\n",
 340 |        "        \"metadata\": {\n",
 341 |        "            \"picky\": true\n",
 342 |        "        },\n",
 343 |        "        \"content\": {\n",
 344 |        "            \"execution_state\": \"idle\"\n",
 345 |        "        },\n",
 346 |        "        \"buffers\": [],\n",
 347 |        "        \"channel\": \"iopub\"\n",
 348 |        "    },\n",
 349 |        "    \"type\": \"receive\",\n",
 350 |        "    \"time\": 1603477338.572976\n",
 351 |        "}\n",
 352 |        "    
\n", 353 | "
\n", 354 | " \n", 355 | " idle\n", 356 | "
" 357 | ], 358 | "text/plain": [ 359 | "" 360 | ] 361 | }, 362 | "metadata": {}, 363 | "output_type": "display_data" 364 | }, 365 | { 366 | "data": { 367 | "text/html": [ 368 | "
\n", 369 | "
\n", 370 | " status ⛏\n", 371 | " \n", 372 | "
\n",
 373 |        "    {\n",
 374 |        "    \"payload\": {\n",
 375 |        "        \"header\": {\n",
 376 |        "            \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_4\",\n",
 377 |        "            \"msg_type\": \"status\",\n",
 378 |        "            \"username\": \"kylek\",\n",
 379 |        "            \"session\": \"944eddb8-eadbf46080214a953ceb1055\",\n",
 380 |        "            \"date\": \"2020-10-23T18:22:18.569480Z\",\n",
 381 |        "            \"version\": \"5.3\"\n",
 382 |        "        },\n",
 383 |        "        \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_4\",\n",
 384 |        "        \"msg_type\": \"status\",\n",
 385 |        "        \"parent_header\": {\n",
 386 |        "            \"msg_id\": \"55f747b71a324c8f8aff32b77174ea59\",\n",
 387 |        "            \"username\": \"username\",\n",
 388 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
 389 |        "            \"msg_type\": \"kernel_info_request\",\n",
 390 |        "            \"version\": \"5.2\",\n",
 391 |        "            \"date\": \"2020-10-23T18:22:18.569383Z\"\n",
 392 |        "        },\n",
 393 |        "        \"metadata\": {\n",
 394 |        "            \"picky\": true\n",
 395 |        "        },\n",
 396 |        "        \"content\": {\n",
 397 |        "            \"execution_state\": \"busy\"\n",
 398 |        "        },\n",
 399 |        "        \"buffers\": [],\n",
 400 |        "        \"channel\": \"iopub\"\n",
 401 |        "    },\n",
 402 |        "    \"type\": \"receive\",\n",
 403 |        "    \"time\": 1603477338.5736332\n",
 404 |        "}\n",
 405 |        "    
\n", 406 | "
\n", 407 | " \n", 408 | " busy\n", 409 | "
" 410 | ], 411 | "text/plain": [ 412 | "" 413 | ] 414 | }, 415 | "metadata": {}, 416 | "output_type": "display_data" 417 | }, 418 | { 419 | "data": { 420 | "text/html": [ 421 | "
\n", 422 | "
\n", 423 | " status ⛏\n", 424 | " \n", 425 | "
\n",
 426 |        "    {\n",
 427 |        "    \"payload\": {\n",
 428 |        "        \"header\": {\n",
 429 |        "            \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_6\",\n",
 430 |        "            \"msg_type\": \"status\",\n",
 431 |        "            \"username\": \"kylek\",\n",
 432 |        "            \"session\": \"944eddb8-eadbf46080214a953ceb1055\",\n",
 433 |        "            \"date\": \"2020-10-23T18:22:18.570831Z\",\n",
 434 |        "            \"version\": \"5.3\"\n",
 435 |        "        },\n",
 436 |        "        \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_6\",\n",
 437 |        "        \"msg_type\": \"status\",\n",
 438 |        "        \"parent_header\": {\n",
 439 |        "            \"msg_id\": \"55f747b71a324c8f8aff32b77174ea59\",\n",
 440 |        "            \"username\": \"username\",\n",
 441 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
 442 |        "            \"msg_type\": \"kernel_info_request\",\n",
 443 |        "            \"version\": \"5.2\",\n",
 444 |        "            \"date\": \"2020-10-23T18:22:18.569383Z\"\n",
 445 |        "        },\n",
 446 |        "        \"metadata\": {\n",
 447 |        "            \"picky\": true\n",
 448 |        "        },\n",
 449 |        "        \"content\": {\n",
 450 |        "            \"execution_state\": \"idle\"\n",
 451 |        "        },\n",
 452 |        "        \"buffers\": [],\n",
 453 |        "        \"channel\": \"iopub\"\n",
 454 |        "    },\n",
 455 |        "    \"type\": \"receive\",\n",
 456 |        "    \"time\": 1603477338.5742242\n",
 457 |        "}\n",
 458 |        "    
\n", 459 | "
\n", 460 | " \n", 461 | " idle\n", 462 | "
" 463 | ], 464 | "text/plain": [ 465 | "" 466 | ] 467 | }, 468 | "metadata": {}, 469 | "output_type": "display_data" 470 | }, 471 | { 472 | "data": { 473 | "text/html": [ 474 | "
\n", 475 | "
\n", 476 | " kernel_info_reply \n", 477 | " \n", 478 | "
\n",
 479 |        "    {\n",
 480 |        "    \"payload\": {\n",
 481 |        "        \"header\": {\n",
 482 |        "            \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_5\",\n",
 483 |        "            \"msg_type\": \"kernel_info_reply\",\n",
 484 |        "            \"username\": \"kylek\",\n",
 485 |        "            \"session\": \"944eddb8-eadbf46080214a953ceb1055\",\n",
 486 |        "            \"date\": \"2020-10-23T18:22:18.569736Z\",\n",
 487 |        "            \"version\": \"5.3\"\n",
 488 |        "        },\n",
 489 |        "        \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_5\",\n",
 490 |        "        \"msg_type\": \"kernel_info_reply\",\n",
 491 |        "        \"parent_header\": {\n",
 492 |        "            \"msg_id\": \"55f747b71a324c8f8aff32b77174ea59\",\n",
 493 |        "            \"username\": \"username\",\n",
 494 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
 495 |        "            \"msg_type\": \"kernel_info_request\",\n",
 496 |        "            \"version\": \"5.2\",\n",
 497 |        "            \"date\": \"2020-10-23T18:22:18.569383Z\"\n",
 498 |        "        },\n",
 499 |        "        \"metadata\": {},\n",
 500 |        "        \"content\": {\n",
 501 |        "            \"status\": \"ok\",\n",
 502 |        "            \"protocol_version\": \"5.3\",\n",
 503 |        "            \"implementation\": \"picky\",\n",
 504 |        "            \"implementation_version\": \"0.1\",\n",
 505 |        "            \"language_info\": {\n",
 506 |        "                \"name\": \"python\",\n",
 507 |        "                \"version\": \"3.7.7\",\n",
 508 |        "                \"mimetype\": \"text/x-python\",\n",
 509 |        "                \"codemirror_mode\": {\n",
 510 |        "                    \"name\": \"ipython\",\n",
 511 |        "                    \"version\": 3\n",
 512 |        "                },\n",
 513 |        "                \"pygments_lexer\": \"ipython3\",\n",
 514 |        "                \"nbconvert_exporter\": \"python\",\n",
 515 |        "                \"file_extension\": \".py\"\n",
 516 |        "            },\n",
 517 |        "            \"banner\": \"Pick, the kernel for choosy users! \\u26cf \\n\\nRead more about it at https://github.com/nteract/pick\\n    \",\n",
 518 |        "            \"help_links\": []\n",
 519 |        "        },\n",
 520 |        "        \"buffers\": [],\n",
 521 |        "        \"channel\": \"shell\"\n",
 522 |        "    },\n",
 523 |        "    \"type\": \"receive\",\n",
 524 |        "    \"time\": 1603477338.5757573\n",
 525 |        "}\n",
 526 |        "    
\n", 527 | "
\n", 528 | " \n", 529 | " \n", 530 | "
" 531 | ], 532 | "text/plain": [ 533 | "" 534 | ] 535 | }, 536 | "metadata": {}, 537 | "output_type": "display_data" 538 | }, 539 | { 540 | "data": { 541 | "text/html": [ 542 | "
\n", 543 | "
\n", 544 | " kernel_info_request \n", 545 | " \n", 546 | "
\n",
 547 |        "    {\n",
 548 |        "    \"payload\": {\n",
 549 |        "        \"header\": {\n",
 550 |        "            \"msg_id\": \"a4828916e3504f7fbc658644feac1079\",\n",
 551 |        "            \"username\": \"username\",\n",
 552 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
 553 |        "            \"msg_type\": \"kernel_info_request\",\n",
 554 |        "            \"version\": \"5.2\"\n",
 555 |        "        },\n",
 556 |        "        \"metadata\": {},\n",
 557 |        "        \"content\": {},\n",
 558 |        "        \"buffers\": [],\n",
 559 |        "        \"parent_header\": {},\n",
 560 |        "        \"channel\": \"shell\"\n",
 561 |        "    },\n",
 562 |        "    \"type\": \"send\",\n",
 563 |        "    \"time\": 1603477365.3038068\n",
 564 |        "}\n",
 565 |        "    
\n", 566 | "
\n", 567 | " \n", 568 | " \n", 569 | "
" 570 | ], 571 | "text/plain": [ 572 | "" 573 | ] 574 | }, 575 | "metadata": {}, 576 | "output_type": "display_data" 577 | }, 578 | { 579 | "data": { 580 | "text/html": [ 581 | "
\n", 582 | "
\n", 583 | " status ⛏\n", 584 | " \n", 585 | "
\n",
 586 |        "    {\n",
 587 |        "    \"payload\": {\n",
 588 |        "        \"header\": {\n",
 589 |        "            \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_7\",\n",
 590 |        "            \"msg_type\": \"status\",\n",
 591 |        "            \"username\": \"kylek\",\n",
 592 |        "            \"session\": \"944eddb8-eadbf46080214a953ceb1055\",\n",
 593 |        "            \"date\": \"2020-10-23T18:22:39.483654Z\",\n",
 594 |        "            \"version\": \"5.3\"\n",
 595 |        "        },\n",
 596 |        "        \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_7\",\n",
 597 |        "        \"msg_type\": \"status\",\n",
 598 |        "        \"parent_header\": {\n",
 599 |        "            \"msg_id\": \"05b26982-468f3a92db94b93716413a6d_6\",\n",
 600 |        "            \"msg_type\": \"shutdown_request\",\n",
 601 |        "            \"username\": \"kylek\",\n",
 602 |        "            \"session\": \"05b26982-468f3a92db94b93716413a6d\",\n",
 603 |        "            \"date\": \"2020-10-23T18:22:39.482433Z\",\n",
 604 |        "            \"version\": \"5.3\"\n",
 605 |        "        },\n",
 606 |        "        \"metadata\": {\n",
 607 |        "            \"picky\": true\n",
 608 |        "        },\n",
 609 |        "        \"content\": {\n",
 610 |        "            \"execution_state\": \"busy\"\n",
 611 |        "        },\n",
 612 |        "        \"buffers\": [],\n",
 613 |        "        \"channel\": \"iopub\"\n",
 614 |        "    },\n",
 615 |        "    \"type\": \"receive\",\n",
 616 |        "    \"time\": 1603477365.3041358\n",
 617 |        "}\n",
 618 |        "    
\n", 619 | "
\n", 620 | " \n", 621 | " busy\n", 622 | "
" 623 | ], 624 | "text/plain": [ 625 | "" 626 | ] 627 | }, 628 | "metadata": {}, 629 | "output_type": "display_data" 630 | }, 631 | { 632 | "data": { 633 | "text/html": [ 634 | "
\n", 635 | "
\n", 636 | " shutdown_reply \n", 637 | " \n", 638 | "
\n",
 639 |        "    {\n",
 640 |        "    \"payload\": {\n",
 641 |        "        \"header\": {\n",
 642 |        "            \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_9\",\n",
 643 |        "            \"msg_type\": \"shutdown_reply\",\n",
 644 |        "            \"username\": \"kylek\",\n",
 645 |        "            \"session\": \"944eddb8-eadbf46080214a953ceb1055\",\n",
 646 |        "            \"date\": \"2020-10-23T18:22:39.484242Z\",\n",
 647 |        "            \"version\": \"5.3\"\n",
 648 |        "        },\n",
 649 |        "        \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_9\",\n",
 650 |        "        \"msg_type\": \"shutdown_reply\",\n",
 651 |        "        \"parent_header\": {\n",
 652 |        "            \"msg_id\": \"05b26982-468f3a92db94b93716413a6d_6\",\n",
 653 |        "            \"msg_type\": \"shutdown_request\",\n",
 654 |        "            \"username\": \"kylek\",\n",
 655 |        "            \"session\": \"05b26982-468f3a92db94b93716413a6d\",\n",
 656 |        "            \"date\": \"2020-10-23T18:22:39.482433Z\",\n",
 657 |        "            \"version\": \"5.3\"\n",
 658 |        "        },\n",
 659 |        "        \"metadata\": {},\n",
 660 |        "        \"content\": {\n",
 661 |        "            \"status\": \"ok\",\n",
 662 |        "            \"restart\": true\n",
 663 |        "        },\n",
 664 |        "        \"buffers\": [],\n",
 665 |        "        \"channel\": \"iopub\"\n",
 666 |        "    },\n",
 667 |        "    \"type\": \"receive\",\n",
 668 |        "    \"time\": 1603477365.3047147\n",
 669 |        "}\n",
 670 |        "    
\n", 671 | "
\n", 672 | " \n", 673 | " \n", 674 | "
" 675 | ], 676 | "text/plain": [ 677 | "" 678 | ] 679 | }, 680 | "metadata": {}, 681 | "output_type": "display_data" 682 | }, 683 | { 684 | "data": { 685 | "text/html": [ 686 | "
\n", 687 | "
\n", 688 | " status ⛏\n", 689 | " \n", 690 | "
\n",
 691 |        "    {\n",
 692 |        "    \"payload\": {\n",
 693 |        "        \"header\": {\n",
 694 |        "            \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_10\",\n",
 695 |        "            \"msg_type\": \"status\",\n",
 696 |        "            \"username\": \"kylek\",\n",
 697 |        "            \"session\": \"944eddb8-eadbf46080214a953ceb1055\",\n",
 698 |        "            \"date\": \"2020-10-23T18:22:39.486100Z\",\n",
 699 |        "            \"version\": \"5.3\"\n",
 700 |        "        },\n",
 701 |        "        \"msg_id\": \"944eddb8-eadbf46080214a953ceb1055_10\",\n",
 702 |        "        \"msg_type\": \"status\",\n",
 703 |        "        \"parent_header\": {\n",
 704 |        "            \"msg_id\": \"05b26982-468f3a92db94b93716413a6d_6\",\n",
 705 |        "            \"msg_type\": \"shutdown_request\",\n",
 706 |        "            \"username\": \"kylek\",\n",
 707 |        "            \"session\": \"05b26982-468f3a92db94b93716413a6d\",\n",
 708 |        "            \"date\": \"2020-10-23T18:22:39.482433Z\",\n",
 709 |        "            \"version\": \"5.3\"\n",
 710 |        "        },\n",
 711 |        "        \"metadata\": {\n",
 712 |        "            \"picky\": true\n",
 713 |        "        },\n",
 714 |        "        \"content\": {\n",
 715 |        "            \"execution_state\": \"idle\"\n",
 716 |        "        },\n",
 717 |        "        \"buffers\": [],\n",
 718 |        "        \"channel\": \"iopub\"\n",
 719 |        "    },\n",
 720 |        "    \"type\": \"receive\",\n",
 721 |        "    \"time\": 1603477365.3049397\n",
 722 |        "}\n",
 723 |        "    
\n", 724 | "
\n", 725 | " \n", 726 | " idle\n", 727 | "
" 728 | ], 729 | "text/plain": [ 730 | "" 731 | ] 732 | }, 733 | "metadata": {}, 734 | "output_type": "display_data" 735 | }, 736 | { 737 | "data": { 738 | "text/html": [ 739 | "
\n", 740 | "
\n", 741 | " status ⛏\n", 742 | " \n", 743 | "
\n",
 744 |        "    {\n",
 745 |        "    \"payload\": {\n",
 746 |        "        \"header\": {\n",
 747 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_0\",\n",
 748 |        "            \"msg_type\": \"status\",\n",
 749 |        "            \"username\": \"kylek\",\n",
 750 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
 751 |        "            \"date\": \"2020-10-23T18:22:45.174359Z\",\n",
 752 |        "            \"version\": \"5.3\"\n",
 753 |        "        },\n",
 754 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_0\",\n",
 755 |        "        \"msg_type\": \"status\",\n",
 756 |        "        \"parent_header\": {},\n",
 757 |        "        \"metadata\": {\n",
 758 |        "            \"picky\": true\n",
 759 |        "        },\n",
 760 |        "        \"content\": {\n",
 761 |        "            \"execution_state\": \"starting\"\n",
 762 |        "        },\n",
 763 |        "        \"buffers\": [],\n",
 764 |        "        \"channel\": \"iopub\"\n",
 765 |        "    },\n",
 766 |        "    \"type\": \"receive\",\n",
 767 |        "    \"time\": 1603477365.3054578\n",
 768 |        "}\n",
 769 |        "    
\n", 770 | "
\n", 771 | " \n", 772 | " starting\n", 773 | "
" 774 | ], 775 | "text/plain": [ 776 | "" 777 | ] 778 | }, 779 | "metadata": {}, 780 | "output_type": "display_data" 781 | }, 782 | { 783 | "data": { 784 | "text/html": [ 785 | "
\n", 786 | "
\n", 787 | " kernel_info_request \n", 788 | " \n", 789 | "
\n",
 790 |        "    {\n",
 791 |        "    \"payload\": {\n",
 792 |        "        \"header\": {\n",
 793 |        "            \"msg_id\": \"7fdd6b51012b4b8a857d59400e340388\",\n",
 794 |        "            \"username\": \"username\",\n",
 795 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
 796 |        "            \"msg_type\": \"kernel_info_request\",\n",
 797 |        "            \"version\": \"5.2\"\n",
 798 |        "        },\n",
 799 |        "        \"metadata\": {},\n",
 800 |        "        \"content\": {},\n",
 801 |        "        \"buffers\": [],\n",
 802 |        "        \"parent_header\": {},\n",
 803 |        "        \"channel\": \"shell\"\n",
 804 |        "    },\n",
 805 |        "    \"type\": \"send\",\n",
 806 |        "    \"time\": 1603477365.3078668\n",
 807 |        "}\n",
 808 |        "    
\n", 809 | "
\n", 810 | " \n", 811 | " \n", 812 | "
" 813 | ], 814 | "text/plain": [ 815 | "" 816 | ] 817 | }, 818 | "metadata": {}, 819 | "output_type": "display_data" 820 | }, 821 | { 822 | "data": { 823 | "text/html": [ 824 | "
\n", 825 | "
\n", 826 | " status ⛏\n", 827 | " \n", 828 | "
\n",
 829 |        "    {\n",
 830 |        "    \"payload\": {\n",
 831 |        "        \"header\": {\n",
 832 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_1\",\n",
 833 |        "            \"msg_type\": \"status\",\n",
 834 |        "            \"username\": \"kylek\",\n",
 835 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
 836 |        "            \"date\": \"2020-10-23T18:22:45.285739Z\",\n",
 837 |        "            \"version\": \"5.3\"\n",
 838 |        "        },\n",
 839 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_1\",\n",
 840 |        "        \"msg_type\": \"status\",\n",
 841 |        "        \"parent_header\": {\n",
 842 |        "            \"msg_id\": \"05b26982-468f3a92db94b93716413a6d_7\",\n",
 843 |        "            \"msg_type\": \"kernel_info_request\",\n",
 844 |        "            \"username\": \"kylek\",\n",
 845 |        "            \"session\": \"05b26982-468f3a92db94b93716413a6d\",\n",
 846 |        "            \"date\": \"2020-10-23T18:22:44.675852Z\",\n",
 847 |        "            \"version\": \"5.3\"\n",
 848 |        "        },\n",
 849 |        "        \"metadata\": {\n",
 850 |        "            \"picky\": true\n",
 851 |        "        },\n",
 852 |        "        \"content\": {\n",
 853 |        "            \"execution_state\": \"busy\"\n",
 854 |        "        },\n",
 855 |        "        \"buffers\": [],\n",
 856 |        "        \"channel\": \"iopub\"\n",
 857 |        "    },\n",
 858 |        "    \"type\": \"receive\",\n",
 859 |        "    \"time\": 1603477365.3082848\n",
 860 |        "}\n",
 861 |        "    
\n", 862 | "
\n", 863 | " \n", 864 | " busy\n", 865 | "
" 866 | ], 867 | "text/plain": [ 868 | "" 869 | ] 870 | }, 871 | "metadata": {}, 872 | "output_type": "display_data" 873 | }, 874 | { 875 | "data": { 876 | "text/html": [ 877 | "
\n", 878 | "
\n", 879 | " status ⛏\n", 880 | " \n", 881 | "
\n",
 882 |        "    {\n",
 883 |        "    \"payload\": {\n",
 884 |        "        \"header\": {\n",
 885 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_3\",\n",
 886 |        "            \"msg_type\": \"status\",\n",
 887 |        "            \"username\": \"kylek\",\n",
 888 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
 889 |        "            \"date\": \"2020-10-23T18:22:45.286664Z\",\n",
 890 |        "            \"version\": \"5.3\"\n",
 891 |        "        },\n",
 892 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_3\",\n",
 893 |        "        \"msg_type\": \"status\",\n",
 894 |        "        \"parent_header\": {\n",
 895 |        "            \"msg_id\": \"05b26982-468f3a92db94b93716413a6d_7\",\n",
 896 |        "            \"msg_type\": \"kernel_info_request\",\n",
 897 |        "            \"username\": \"kylek\",\n",
 898 |        "            \"session\": \"05b26982-468f3a92db94b93716413a6d\",\n",
 899 |        "            \"date\": \"2020-10-23T18:22:44.675852Z\",\n",
 900 |        "            \"version\": \"5.3\"\n",
 901 |        "        },\n",
 902 |        "        \"metadata\": {\n",
 903 |        "            \"picky\": true\n",
 904 |        "        },\n",
 905 |        "        \"content\": {\n",
 906 |        "            \"execution_state\": \"idle\"\n",
 907 |        "        },\n",
 908 |        "        \"buffers\": [],\n",
 909 |        "        \"channel\": \"iopub\"\n",
 910 |        "    },\n",
 911 |        "    \"type\": \"receive\",\n",
 912 |        "    \"time\": 1603477365.3088827\n",
 913 |        "}\n",
 914 |        "    
\n", 915 | "
\n", 916 | " \n", 917 | " idle\n", 918 | "
" 919 | ], 920 | "text/plain": [ 921 | "" 922 | ] 923 | }, 924 | "metadata": {}, 925 | "output_type": "display_data" 926 | }, 927 | { 928 | "data": { 929 | "text/html": [ 930 | "
\n", 931 | "
\n", 932 | " status ⛏\n", 933 | " \n", 934 | "
\n",
 935 |        "    {\n",
 936 |        "    \"payload\": {\n",
 937 |        "        \"header\": {\n",
 938 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_4\",\n",
 939 |        "            \"msg_type\": \"status\",\n",
 940 |        "            \"username\": \"kylek\",\n",
 941 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
 942 |        "            \"date\": \"2020-10-23T18:22:45.305674Z\",\n",
 943 |        "            \"version\": \"5.3\"\n",
 944 |        "        },\n",
 945 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_4\",\n",
 946 |        "        \"msg_type\": \"status\",\n",
 947 |        "        \"parent_header\": {\n",
 948 |        "            \"msg_id\": \"a4828916e3504f7fbc658644feac1079\",\n",
 949 |        "            \"username\": \"username\",\n",
 950 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
 951 |        "            \"msg_type\": \"kernel_info_request\",\n",
 952 |        "            \"version\": \"5.2\",\n",
 953 |        "            \"date\": \"2020-10-23T18:22:45.305457Z\"\n",
 954 |        "        },\n",
 955 |        "        \"metadata\": {\n",
 956 |        "            \"picky\": true\n",
 957 |        "        },\n",
 958 |        "        \"content\": {\n",
 959 |        "            \"execution_state\": \"busy\"\n",
 960 |        "        },\n",
 961 |        "        \"buffers\": [],\n",
 962 |        "        \"channel\": \"iopub\"\n",
 963 |        "    },\n",
 964 |        "    \"type\": \"receive\",\n",
 965 |        "    \"time\": 1603477365.3116658\n",
 966 |        "}\n",
 967 |        "    
\n", 968 | "
\n", 969 | " \n", 970 | " busy\n", 971 | "
" 972 | ], 973 | "text/plain": [ 974 | "" 975 | ] 976 | }, 977 | "metadata": {}, 978 | "output_type": "display_data" 979 | }, 980 | { 981 | "data": { 982 | "text/html": [ 983 | "
\n", 984 | "
\n", 985 | " status ⛏\n", 986 | " \n", 987 | "
\n",
 988 |        "    {\n",
 989 |        "    \"payload\": {\n",
 990 |        "        \"header\": {\n",
 991 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_6\",\n",
 992 |        "            \"msg_type\": \"status\",\n",
 993 |        "            \"username\": \"kylek\",\n",
 994 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
 995 |        "            \"date\": \"2020-10-23T18:22:45.307321Z\",\n",
 996 |        "            \"version\": \"5.3\"\n",
 997 |        "        },\n",
 998 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_6\",\n",
 999 |        "        \"msg_type\": \"status\",\n",
1000 |        "        \"parent_header\": {\n",
1001 |        "            \"msg_id\": \"a4828916e3504f7fbc658644feac1079\",\n",
1002 |        "            \"username\": \"username\",\n",
1003 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1004 |        "            \"msg_type\": \"kernel_info_request\",\n",
1005 |        "            \"version\": \"5.2\",\n",
1006 |        "            \"date\": \"2020-10-23T18:22:45.305457Z\"\n",
1007 |        "        },\n",
1008 |        "        \"metadata\": {\n",
1009 |        "            \"picky\": true\n",
1010 |        "        },\n",
1011 |        "        \"content\": {\n",
1012 |        "            \"execution_state\": \"idle\"\n",
1013 |        "        },\n",
1014 |        "        \"buffers\": [],\n",
1015 |        "        \"channel\": \"iopub\"\n",
1016 |        "    },\n",
1017 |        "    \"type\": \"receive\",\n",
1018 |        "    \"time\": 1603477365.3123877\n",
1019 |        "}\n",
1020 |        "    
\n", 1021 | "
\n", 1022 | " \n", 1023 | " idle\n", 1024 | "
" 1025 | ], 1026 | "text/plain": [ 1027 | "" 1028 | ] 1029 | }, 1030 | "metadata": {}, 1031 | "output_type": "display_data" 1032 | }, 1033 | { 1034 | "data": { 1035 | "text/html": [ 1036 | "
\n", 1037 | "
\n", 1038 | " kernel_info_reply \n", 1039 | " \n", 1040 | "
\n",
1041 |        "    {\n",
1042 |        "    \"payload\": {\n",
1043 |        "        \"header\": {\n",
1044 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_5\",\n",
1045 |        "            \"msg_type\": \"kernel_info_reply\",\n",
1046 |        "            \"username\": \"kylek\",\n",
1047 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1048 |        "            \"date\": \"2020-10-23T18:22:45.306167Z\",\n",
1049 |        "            \"version\": \"5.3\"\n",
1050 |        "        },\n",
1051 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_5\",\n",
1052 |        "        \"msg_type\": \"kernel_info_reply\",\n",
1053 |        "        \"parent_header\": {\n",
1054 |        "            \"msg_id\": \"a4828916e3504f7fbc658644feac1079\",\n",
1055 |        "            \"username\": \"username\",\n",
1056 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1057 |        "            \"msg_type\": \"kernel_info_request\",\n",
1058 |        "            \"version\": \"5.2\",\n",
1059 |        "            \"date\": \"2020-10-23T18:22:45.305457Z\"\n",
1060 |        "        },\n",
1061 |        "        \"metadata\": {},\n",
1062 |        "        \"content\": {\n",
1063 |        "            \"status\": \"ok\",\n",
1064 |        "            \"protocol_version\": \"5.3\",\n",
1065 |        "            \"implementation\": \"picky\",\n",
1066 |        "            \"implementation_version\": \"0.1\",\n",
1067 |        "            \"language_info\": {\n",
1068 |        "                \"name\": \"python\",\n",
1069 |        "                \"version\": \"3.7.7\",\n",
1070 |        "                \"mimetype\": \"text/x-python\",\n",
1071 |        "                \"codemirror_mode\": {\n",
1072 |        "                    \"name\": \"ipython\",\n",
1073 |        "                    \"version\": 3\n",
1074 |        "                },\n",
1075 |        "                \"pygments_lexer\": \"ipython3\",\n",
1076 |        "                \"nbconvert_exporter\": \"python\",\n",
1077 |        "                \"file_extension\": \".py\"\n",
1078 |        "            },\n",
1079 |        "            \"banner\": \"Pick, the kernel for choosy users! \\u26cf \\n\\nRead more about it at https://github.com/nteract/pick\\n    \",\n",
1080 |        "            \"help_links\": []\n",
1081 |        "        },\n",
1082 |        "        \"buffers\": [],\n",
1083 |        "        \"channel\": \"shell\"\n",
1084 |        "    },\n",
1085 |        "    \"type\": \"receive\",\n",
1086 |        "    \"time\": 1603477365.3129609\n",
1087 |        "}\n",
1088 |        "    
\n", 1089 | "
\n", 1090 | " \n", 1091 | " \n", 1092 | "
" 1093 | ], 1094 | "text/plain": [ 1095 | "" 1096 | ] 1097 | }, 1098 | "metadata": {}, 1099 | "output_type": "display_data" 1100 | }, 1101 | { 1102 | "data": { 1103 | "text/html": [ 1104 | "
\n", 1105 | "
\n", 1106 | " execute_request \n", 1107 | " \n", 1108 | "
\n",
1109 |        "    {\n",
1110 |        "    \"payload\": {\n",
1111 |        "        \"header\": {\n",
1112 |        "            \"msg_id\": \"f3efda7b9d744c08882d648f5e3643e2\",\n",
1113 |        "            \"username\": \"username\",\n",
1114 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1115 |        "            \"msg_type\": \"execute_request\",\n",
1116 |        "            \"version\": \"5.2\"\n",
1117 |        "        },\n",
1118 |        "        \"metadata\": {},\n",
1119 |        "        \"content\": {\n",
1120 |        "            \"code\": \"%%kernel.ipykernel\\n\\n-c\\n\\\"import binascii; data = binascii.hexlify(b'Did it work?')\\\"\",\n",
1121 |        "            \"silent\": false,\n",
1122 |        "            \"store_history\": true,\n",
1123 |        "            \"user_expressions\": {},\n",
1124 |        "            \"allow_stdin\": true,\n",
1125 |        "            \"stop_on_error\": true\n",
1126 |        "        },\n",
1127 |        "        \"buffers\": [],\n",
1128 |        "        \"parent_header\": {},\n",
1129 |        "        \"channel\": \"shell\"\n",
1130 |        "    },\n",
1131 |        "    \"type\": \"send\",\n",
1132 |        "    \"time\": 1603477365.325199\n",
1133 |        "}\n",
1134 |        "    
\n", 1135 | "
\n", 1136 | " \n", 1137 | " \n", 1138 | "
" 1139 | ], 1140 | "text/plain": [ 1141 | "" 1142 | ] 1143 | }, 1144 | "metadata": {}, 1145 | "output_type": "display_data" 1146 | }, 1147 | { 1148 | "data": { 1149 | "text/html": [ 1150 | "
\n", 1151 | "
\n", 1152 | " execute_request \n", 1153 | " \n", 1154 | "
\n",
1155 |        "    {\n",
1156 |        "    \"payload\": {\n",
1157 |        "        \"header\": {\n",
1158 |        "            \"msg_id\": \"ec62cdc3f1a94a22a8e878bdd3dbda49\",\n",
1159 |        "            \"username\": \"username\",\n",
1160 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1161 |        "            \"msg_type\": \"execute_request\",\n",
1162 |        "            \"version\": \"5.2\"\n",
1163 |        "        },\n",
1164 |        "        \"metadata\": {},\n",
1165 |        "        \"content\": {\n",
1166 |        "            \"code\": \"if(binascii.unhexlify(data) != b'Did it work?'):\\n    raise Exception(\\\"Kernel received the wrong data\\\")\\nelse:\\n    print(\\\"All set!\\\")\",\n",
1167 |        "            \"silent\": false,\n",
1168 |        "            \"store_history\": true,\n",
1169 |        "            \"user_expressions\": {},\n",
1170 |        "            \"allow_stdin\": true,\n",
1171 |        "            \"stop_on_error\": true\n",
1172 |        "        },\n",
1173 |        "        \"buffers\": [],\n",
1174 |        "        \"parent_header\": {},\n",
1175 |        "        \"channel\": \"shell\"\n",
1176 |        "    },\n",
1177 |        "    \"type\": \"send\",\n",
1178 |        "    \"time\": 1603477365.3317337\n",
1179 |        "}\n",
1180 |        "    
\n", 1181 | "
\n", 1182 | " \n", 1183 | " \n", 1184 | "
" 1185 | ], 1186 | "text/plain": [ 1187 | "" 1188 | ] 1189 | }, 1190 | "metadata": {}, 1191 | "output_type": "display_data" 1192 | }, 1193 | { 1194 | "data": { 1195 | "text/html": [ 1196 | "
\n", 1197 | "
\n", 1198 | " execute_request \n", 1199 | " \n", 1200 | "
\n",
1201 |        "    {\n",
1202 |        "    \"payload\": {\n",
1203 |        "        \"header\": {\n",
1204 |        "            \"msg_id\": \"60acf4f4d79642e990a24478e064f2e9\",\n",
1205 |        "            \"username\": \"username\",\n",
1206 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1207 |        "            \"msg_type\": \"execute_request\",\n",
1208 |        "            \"version\": \"5.2\"\n",
1209 |        "        },\n",
1210 |        "        \"metadata\": {},\n",
1211 |        "        \"content\": {\n",
1212 |        "            \"code\": \"display('hello')\",\n",
1213 |        "            \"silent\": false,\n",
1214 |        "            \"store_history\": true,\n",
1215 |        "            \"user_expressions\": {},\n",
1216 |        "            \"allow_stdin\": true,\n",
1217 |        "            \"stop_on_error\": true\n",
1218 |        "        },\n",
1219 |        "        \"buffers\": [],\n",
1220 |        "        \"parent_header\": {},\n",
1221 |        "        \"channel\": \"shell\"\n",
1222 |        "    },\n",
1223 |        "    \"type\": \"send\",\n",
1224 |        "    \"time\": 1603477365.3378448\n",
1225 |        "}\n",
1226 |        "    
\n", 1227 | "
\n", 1228 | " \n", 1229 | " \n", 1230 | "
" 1231 | ], 1232 | "text/plain": [ 1233 | "" 1234 | ] 1235 | }, 1236 | "metadata": {}, 1237 | "output_type": "display_data" 1238 | }, 1239 | { 1240 | "data": { 1241 | "text/html": [ 1242 | "
\n", 1243 | "
\n", 1244 | " status ⛏\n", 1245 | " \n", 1246 | "
\n",
1247 |        "    {\n",
1248 |        "    \"payload\": {\n",
1249 |        "        \"header\": {\n",
1250 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_7\",\n",
1251 |        "            \"msg_type\": \"status\",\n",
1252 |        "            \"username\": \"kylek\",\n",
1253 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1254 |        "            \"date\": \"2020-10-23T18:22:45.312894Z\",\n",
1255 |        "            \"version\": \"5.3\"\n",
1256 |        "        },\n",
1257 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_7\",\n",
1258 |        "        \"msg_type\": \"status\",\n",
1259 |        "        \"parent_header\": {\n",
1260 |        "            \"msg_id\": \"7fdd6b51012b4b8a857d59400e340388\",\n",
1261 |        "            \"username\": \"username\",\n",
1262 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1263 |        "            \"msg_type\": \"kernel_info_request\",\n",
1264 |        "            \"version\": \"5.2\",\n",
1265 |        "            \"date\": \"2020-10-23T18:22:45.312787Z\"\n",
1266 |        "        },\n",
1267 |        "        \"metadata\": {\n",
1268 |        "            \"picky\": true\n",
1269 |        "        },\n",
1270 |        "        \"content\": {\n",
1271 |        "            \"execution_state\": \"busy\"\n",
1272 |        "        },\n",
1273 |        "        \"buffers\": [],\n",
1274 |        "        \"channel\": \"iopub\"\n",
1275 |        "    },\n",
1276 |        "    \"type\": \"receive\",\n",
1277 |        "    \"time\": 1603477365.3608587\n",
1278 |        "}\n",
1279 |        "    
\n", 1280 | "
\n", 1281 | " \n", 1282 | " busy\n", 1283 | "
" 1284 | ], 1285 | "text/plain": [ 1286 | "" 1287 | ] 1288 | }, 1289 | "metadata": {}, 1290 | "output_type": "display_data" 1291 | }, 1292 | { 1293 | "data": { 1294 | "text/html": [ 1295 | "
\n", 1296 | "
\n", 1297 | " kernel_info_reply \n", 1298 | " \n", 1299 | "
\n",
1300 |        "    {\n",
1301 |        "    \"payload\": {\n",
1302 |        "        \"header\": {\n",
1303 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_8\",\n",
1304 |        "            \"msg_type\": \"kernel_info_reply\",\n",
1305 |        "            \"username\": \"kylek\",\n",
1306 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1307 |        "            \"date\": \"2020-10-23T18:22:45.313225Z\",\n",
1308 |        "            \"version\": \"5.3\"\n",
1309 |        "        },\n",
1310 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_8\",\n",
1311 |        "        \"msg_type\": \"kernel_info_reply\",\n",
1312 |        "        \"parent_header\": {\n",
1313 |        "            \"msg_id\": \"7fdd6b51012b4b8a857d59400e340388\",\n",
1314 |        "            \"username\": \"username\",\n",
1315 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1316 |        "            \"msg_type\": \"kernel_info_request\",\n",
1317 |        "            \"version\": \"5.2\",\n",
1318 |        "            \"date\": \"2020-10-23T18:22:45.312787Z\"\n",
1319 |        "        },\n",
1320 |        "        \"metadata\": {},\n",
1321 |        "        \"content\": {\n",
1322 |        "            \"status\": \"ok\",\n",
1323 |        "            \"protocol_version\": \"5.3\",\n",
1324 |        "            \"implementation\": \"picky\",\n",
1325 |        "            \"implementation_version\": \"0.1\",\n",
1326 |        "            \"language_info\": {\n",
1327 |        "                \"name\": \"python\",\n",
1328 |        "                \"version\": \"3.7.7\",\n",
1329 |        "                \"mimetype\": \"text/x-python\",\n",
1330 |        "                \"codemirror_mode\": {\n",
1331 |        "                    \"name\": \"ipython\",\n",
1332 |        "                    \"version\": 3\n",
1333 |        "                },\n",
1334 |        "                \"pygments_lexer\": \"ipython3\",\n",
1335 |        "                \"nbconvert_exporter\": \"python\",\n",
1336 |        "                \"file_extension\": \".py\"\n",
1337 |        "            },\n",
1338 |        "            \"banner\": \"Pick, the kernel for choosy users! \\u26cf \\n\\nRead more about it at https://github.com/nteract/pick\\n    \",\n",
1339 |        "            \"help_links\": []\n",
1340 |        "        },\n",
1341 |        "        \"buffers\": [],\n",
1342 |        "        \"channel\": \"shell\"\n",
1343 |        "    },\n",
1344 |        "    \"type\": \"receive\",\n",
1345 |        "    \"time\": 1603477365.3618639\n",
1346 |        "}\n",
1347 |        "    
\n", 1348 | "
\n", 1349 | " \n", 1350 | " \n", 1351 | "
" 1352 | ], 1353 | "text/plain": [ 1354 | "" 1355 | ] 1356 | }, 1357 | "metadata": {}, 1358 | "output_type": "display_data" 1359 | }, 1360 | { 1361 | "data": { 1362 | "text/html": [ 1363 | "
\n", 1364 | "
\n", 1365 | " status ⛏\n", 1366 | " \n", 1367 | "
\n",
1368 |        "    {\n",
1369 |        "    \"payload\": {\n",
1370 |        "        \"header\": {\n",
1371 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_9\",\n",
1372 |        "            \"msg_type\": \"status\",\n",
1373 |        "            \"username\": \"kylek\",\n",
1374 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1375 |        "            \"date\": \"2020-10-23T18:22:45.314510Z\",\n",
1376 |        "            \"version\": \"5.3\"\n",
1377 |        "        },\n",
1378 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_9\",\n",
1379 |        "        \"msg_type\": \"status\",\n",
1380 |        "        \"parent_header\": {\n",
1381 |        "            \"msg_id\": \"7fdd6b51012b4b8a857d59400e340388\",\n",
1382 |        "            \"username\": \"username\",\n",
1383 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1384 |        "            \"msg_type\": \"kernel_info_request\",\n",
1385 |        "            \"version\": \"5.2\",\n",
1386 |        "            \"date\": \"2020-10-23T18:22:45.312787Z\"\n",
1387 |        "        },\n",
1388 |        "        \"metadata\": {\n",
1389 |        "            \"picky\": true\n",
1390 |        "        },\n",
1391 |        "        \"content\": {\n",
1392 |        "            \"execution_state\": \"idle\"\n",
1393 |        "        },\n",
1394 |        "        \"buffers\": [],\n",
1395 |        "        \"channel\": \"iopub\"\n",
1396 |        "    },\n",
1397 |        "    \"type\": \"receive\",\n",
1398 |        "    \"time\": 1603477365.3667607\n",
1399 |        "}\n",
1400 |        "    
\n", 1401 | "
\n", 1402 | " \n", 1403 | " idle\n", 1404 | "
" 1405 | ], 1406 | "text/plain": [ 1407 | "" 1408 | ] 1409 | }, 1410 | "metadata": {}, 1411 | "output_type": "display_data" 1412 | }, 1413 | { 1414 | "data": { 1415 | "text/html": [ 1416 | "
\n", 1417 | "
\n", 1418 | " status ⛏\n", 1419 | " \n", 1420 | "
\n",
1421 |        "    {\n",
1422 |        "    \"payload\": {\n",
1423 |        "        \"header\": {\n",
1424 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_10\",\n",
1425 |        "            \"msg_type\": \"status\",\n",
1426 |        "            \"username\": \"kylek\",\n",
1427 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1428 |        "            \"date\": \"2020-10-23T18:22:45.327164Z\",\n",
1429 |        "            \"version\": \"5.3\"\n",
1430 |        "        },\n",
1431 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_10\",\n",
1432 |        "        \"msg_type\": \"status\",\n",
1433 |        "        \"parent_header\": {\n",
1434 |        "            \"msg_id\": \"f3efda7b9d744c08882d648f5e3643e2\",\n",
1435 |        "            \"username\": \"username\",\n",
1436 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1437 |        "            \"msg_type\": \"execute_request\",\n",
1438 |        "            \"version\": \"5.2\",\n",
1439 |        "            \"date\": \"2020-10-23T18:22:45.327037Z\"\n",
1440 |        "        },\n",
1441 |        "        \"metadata\": {\n",
1442 |        "            \"picky\": true\n",
1443 |        "        },\n",
1444 |        "        \"content\": {\n",
1445 |        "            \"execution_state\": \"busy\"\n",
1446 |        "        },\n",
1447 |        "        \"buffers\": [],\n",
1448 |        "        \"channel\": \"iopub\"\n",
1449 |        "    },\n",
1450 |        "    \"type\": \"receive\",\n",
1451 |        "    \"time\": 1603477365.3675249\n",
1452 |        "}\n",
1453 |        "    
\n", 1454 | "
\n", 1455 | " \n", 1456 | " busy\n", 1457 | "
" 1458 | ], 1459 | "text/plain": [ 1460 | "" 1461 | ] 1462 | }, 1463 | "metadata": {}, 1464 | "output_type": "display_data" 1465 | }, 1466 | { 1467 | "data": { 1468 | "text/html": [ 1469 | "
\n", 1470 | "
\n", 1471 | " status ⛏\n", 1472 | " \n", 1473 | "
\n",
1474 |        "    {\n",
1475 |        "    \"payload\": {\n",
1476 |        "        \"header\": {\n",
1477 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_11\",\n",
1478 |        "            \"msg_type\": \"status\",\n",
1479 |        "            \"username\": \"kylek\",\n",
1480 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1481 |        "            \"date\": \"2020-10-23T18:22:45.328494Z\",\n",
1482 |        "            \"version\": \"5.3\"\n",
1483 |        "        },\n",
1484 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_11\",\n",
1485 |        "        \"msg_type\": \"status\",\n",
1486 |        "        \"parent_header\": {\n",
1487 |        "            \"msg_id\": \"f3efda7b9d744c08882d648f5e3643e2\",\n",
1488 |        "            \"username\": \"username\",\n",
1489 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1490 |        "            \"msg_type\": \"execute_request\",\n",
1491 |        "            \"version\": \"5.2\",\n",
1492 |        "            \"date\": \"2020-10-23T18:22:45.327037Z\"\n",
1493 |        "        },\n",
1494 |        "        \"metadata\": {\n",
1495 |        "            \"picky\": true\n",
1496 |        "        },\n",
1497 |        "        \"content\": {\n",
1498 |        "            \"execution_state\": \"idle\"\n",
1499 |        "        },\n",
1500 |        "        \"buffers\": [],\n",
1501 |        "        \"channel\": \"iopub\"\n",
1502 |        "    },\n",
1503 |        "    \"type\": \"receive\",\n",
1504 |        "    \"time\": 1603477365.3681078\n",
1505 |        "}\n",
1506 |        "    
\n", 1507 | "
\n", 1508 | " \n", 1509 | " idle\n", 1510 | "
" 1511 | ], 1512 | "text/plain": [ 1513 | "" 1514 | ] 1515 | }, 1516 | "metadata": {}, 1517 | "output_type": "display_data" 1518 | }, 1519 | { 1520 | "data": { 1521 | "text/html": [ 1522 | "
\n", 1523 | "
\n", 1524 | " status ⛏\n", 1525 | " \n", 1526 | "
\n",
1527 |        "    {\n",
1528 |        "    \"payload\": {\n",
1529 |        "        \"header\": {\n",
1530 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_12\",\n",
1531 |        "            \"msg_type\": \"status\",\n",
1532 |        "            \"username\": \"kylek\",\n",
1533 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1534 |        "            \"date\": \"2020-10-23T18:22:45.329492Z\",\n",
1535 |        "            \"version\": \"5.3\"\n",
1536 |        "        },\n",
1537 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_12\",\n",
1538 |        "        \"msg_type\": \"status\",\n",
1539 |        "        \"parent_header\": {\n",
1540 |        "            \"msg_id\": \"f3efda7b9d744c08882d648f5e3643e2\",\n",
1541 |        "            \"username\": \"username\",\n",
1542 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1543 |        "            \"msg_type\": \"execute_request\",\n",
1544 |        "            \"version\": \"5.2\",\n",
1545 |        "            \"date\": \"2020-10-23T18:22:45.327037Z\"\n",
1546 |        "        },\n",
1547 |        "        \"metadata\": {\n",
1548 |        "            \"picky\": true\n",
1549 |        "        },\n",
1550 |        "        \"content\": {\n",
1551 |        "            \"execution_state\": \"busy\"\n",
1552 |        "        },\n",
1553 |        "        \"buffers\": [],\n",
1554 |        "        \"channel\": \"iopub\"\n",
1555 |        "    },\n",
1556 |        "    \"type\": \"receive\",\n",
1557 |        "    \"time\": 1603477365.3685887\n",
1558 |        "}\n",
1559 |        "    
\n", 1560 | "
\n", 1561 | " \n", 1562 | " busy\n", 1563 | "
" 1564 | ], 1565 | "text/plain": [ 1566 | "" 1567 | ] 1568 | }, 1569 | "metadata": {}, 1570 | "output_type": "display_data" 1571 | }, 1572 | { 1573 | "data": { 1574 | "text/html": [ 1575 | "
\n", 1576 | "
\n", 1577 | " execute_input \n", 1578 | " \n", 1579 | "
\n",
1580 |        "    {\n",
1581 |        "    \"payload\": {\n",
1582 |        "        \"header\": {\n",
1583 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_13\",\n",
1584 |        "            \"msg_type\": \"execute_input\",\n",
1585 |        "            \"username\": \"kylek\",\n",
1586 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1587 |        "            \"date\": \"2020-10-23T18:22:45.329946Z\",\n",
1588 |        "            \"version\": \"5.3\"\n",
1589 |        "        },\n",
1590 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_13\",\n",
1591 |        "        \"msg_type\": \"execute_input\",\n",
1592 |        "        \"parent_header\": {\n",
1593 |        "            \"msg_id\": \"f3efda7b9d744c08882d648f5e3643e2\",\n",
1594 |        "            \"username\": \"username\",\n",
1595 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1596 |        "            \"msg_type\": \"execute_request\",\n",
1597 |        "            \"version\": \"5.2\",\n",
1598 |        "            \"date\": \"2020-10-23T18:22:45.327037Z\"\n",
1599 |        "        },\n",
1600 |        "        \"metadata\": {},\n",
1601 |        "        \"content\": {\n",
1602 |        "            \"code\": \"%%kernel.ipykernel\\n\\n-c\\n\\\"import binascii; data = binascii.hexlify(b'Did it work?')\\\"\",\n",
1603 |        "            \"execution_count\": 0\n",
1604 |        "        },\n",
1605 |        "        \"buffers\": [],\n",
1606 |        "        \"channel\": \"iopub\"\n",
1607 |        "    },\n",
1608 |        "    \"type\": \"receive\",\n",
1609 |        "    \"time\": 1603477365.3691359\n",
1610 |        "}\n",
1611 |        "    
\n", 1612 | "
\n", 1613 | " \n", 1614 | " \n", 1615 | "
" 1616 | ], 1617 | "text/plain": [ 1618 | "" 1619 | ] 1620 | }, 1621 | "metadata": {}, 1622 | "output_type": "display_data" 1623 | }, 1624 | { 1625 | "data": { 1626 | "text/html": [ 1627 | "
\n", 1628 | "
\n", 1629 | " display_data \n", 1630 | " \n", 1631 | "
\n",
1632 |        "    {\n",
1633 |        "    \"payload\": {\n",
1634 |        "        \"header\": {\n",
1635 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_14\",\n",
1636 |        "            \"msg_type\": \"display_data\",\n",
1637 |        "            \"username\": \"kylek\",\n",
1638 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1639 |        "            \"date\": \"2020-10-23T18:22:45.338539Z\",\n",
1640 |        "            \"version\": \"5.3\"\n",
1641 |        "        },\n",
1642 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_14\",\n",
1643 |        "        \"msg_type\": \"display_data\",\n",
1644 |        "        \"parent_header\": {\n",
1645 |        "            \"msg_id\": \"f3efda7b9d744c08882d648f5e3643e2\",\n",
1646 |        "            \"username\": \"username\",\n",
1647 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1648 |        "            \"msg_type\": \"execute_request\",\n",
1649 |        "            \"version\": \"5.2\",\n",
1650 |        "            \"date\": \"2020-10-23T18:22:45.327037Z\"\n",
1651 |        "        },\n",
1652 |        "        \"metadata\": {},\n",
1653 |        "        \"content\": {\n",
1654 |        "            \"data\": {\n",
1655 |        "                \"text/plain\": \"\",\n",
1656 |        "                \"text/markdown\": \"Launching customized runtime...\"\n",
1657 |        "            },\n",
1658 |        "            \"metadata\": {},\n",
1659 |        "            \"transient\": {\n",
1660 |        "                \"display_id\": \"13f93a5254779819\"\n",
1661 |        "            }\n",
1662 |        "        },\n",
1663 |        "        \"buffers\": [],\n",
1664 |        "        \"channel\": \"iopub\"\n",
1665 |        "    },\n",
1666 |        "    \"type\": \"receive\",\n",
1667 |        "    \"time\": 1603477365.3694038\n",
1668 |        "}\n",
1669 |        "    
\n", 1670 | "
\n", 1671 | " \n", 1672 | " \n", 1673 | "
" 1674 | ], 1675 | "text/plain": [ 1676 | "" 1677 | ] 1678 | }, 1679 | "metadata": {}, 1680 | "output_type": "display_data" 1681 | }, 1682 | { 1683 | "data": { 1684 | "text/html": [ 1685 | "
\n", 1686 | "
\n", 1687 | " status ⛏\n", 1688 | " \n", 1689 | "
\n",
1690 |        "    {\n",
1691 |        "    \"payload\": {\n",
1692 |        "        \"header\": {\n",
1693 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_15\",\n",
1694 |        "            \"msg_type\": \"status\",\n",
1695 |        "            \"username\": \"kylek\",\n",
1696 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1697 |        "            \"date\": \"2020-10-23T18:22:45.356505Z\",\n",
1698 |        "            \"version\": \"5.3\"\n",
1699 |        "        },\n",
1700 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_15\",\n",
1701 |        "        \"msg_type\": \"status\",\n",
1702 |        "        \"parent_header\": {\n",
1703 |        "            \"msg_id\": \"ec62cdc3f1a94a22a8e878bdd3dbda49\",\n",
1704 |        "            \"username\": \"username\",\n",
1705 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1706 |        "            \"msg_type\": \"execute_request\",\n",
1707 |        "            \"version\": \"5.2\",\n",
1708 |        "            \"date\": \"2020-10-23T18:22:45.356350Z\"\n",
1709 |        "        },\n",
1710 |        "        \"metadata\": {\n",
1711 |        "            \"picky\": true\n",
1712 |        "        },\n",
1713 |        "        \"content\": {\n",
1714 |        "            \"execution_state\": \"busy\"\n",
1715 |        "        },\n",
1716 |        "        \"buffers\": [],\n",
1717 |        "        \"channel\": \"iopub\"\n",
1718 |        "    },\n",
1719 |        "    \"type\": \"receive\",\n",
1720 |        "    \"time\": 1603477365.38546\n",
1721 |        "}\n",
1722 |        "    
\n", 1723 | "
\n", 1724 | " \n", 1725 | " busy\n", 1726 | "
" 1727 | ], 1728 | "text/plain": [ 1729 | "" 1730 | ] 1731 | }, 1732 | "metadata": {}, 1733 | "output_type": "display_data" 1734 | }, 1735 | { 1736 | "data": { 1737 | "text/html": [ 1738 | "
\n", 1739 | "
\n", 1740 | " status ⛏\n", 1741 | " \n", 1742 | "
\n",
1743 |        "    {\n",
1744 |        "    \"payload\": {\n",
1745 |        "        \"header\": {\n",
1746 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_16\",\n",
1747 |        "            \"msg_type\": \"status\",\n",
1748 |        "            \"username\": \"kylek\",\n",
1749 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1750 |        "            \"date\": \"2020-10-23T18:22:45.357579Z\",\n",
1751 |        "            \"version\": \"5.3\"\n",
1752 |        "        },\n",
1753 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_16\",\n",
1754 |        "        \"msg_type\": \"status\",\n",
1755 |        "        \"parent_header\": {\n",
1756 |        "            \"msg_id\": \"ec62cdc3f1a94a22a8e878bdd3dbda49\",\n",
1757 |        "            \"username\": \"username\",\n",
1758 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1759 |        "            \"msg_type\": \"execute_request\",\n",
1760 |        "            \"version\": \"5.2\",\n",
1761 |        "            \"date\": \"2020-10-23T18:22:45.356350Z\"\n",
1762 |        "        },\n",
1763 |        "        \"metadata\": {\n",
1764 |        "            \"picky\": true\n",
1765 |        "        },\n",
1766 |        "        \"content\": {\n",
1767 |        "            \"execution_state\": \"idle\"\n",
1768 |        "        },\n",
1769 |        "        \"buffers\": [],\n",
1770 |        "        \"channel\": \"iopub\"\n",
1771 |        "    },\n",
1772 |        "    \"type\": \"receive\",\n",
1773 |        "    \"time\": 1603477365.3861287\n",
1774 |        "}\n",
1775 |        "    
\n", 1776 | "
\n", 1777 | " \n", 1778 | " idle\n", 1779 | "
" 1780 | ], 1781 | "text/plain": [ 1782 | "" 1783 | ] 1784 | }, 1785 | "metadata": {}, 1786 | "output_type": "display_data" 1787 | }, 1788 | { 1789 | "data": { 1790 | "text/html": [ 1791 | "
\n", 1792 | "
\n", 1793 | " status ⛏\n", 1794 | " \n", 1795 | "
\n",
1796 |        "    {\n",
1797 |        "    \"payload\": {\n",
1798 |        "        \"header\": {\n",
1799 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_17\",\n",
1800 |        "            \"msg_type\": \"status\",\n",
1801 |        "            \"username\": \"kylek\",\n",
1802 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1803 |        "            \"date\": \"2020-10-23T18:22:45.358355Z\",\n",
1804 |        "            \"version\": \"5.3\"\n",
1805 |        "        },\n",
1806 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_17\",\n",
1807 |        "        \"msg_type\": \"status\",\n",
1808 |        "        \"parent_header\": {\n",
1809 |        "            \"msg_id\": \"ec62cdc3f1a94a22a8e878bdd3dbda49\",\n",
1810 |        "            \"username\": \"username\",\n",
1811 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1812 |        "            \"msg_type\": \"execute_request\",\n",
1813 |        "            \"version\": \"5.2\",\n",
1814 |        "            \"date\": \"2020-10-23T18:22:45.356350Z\"\n",
1815 |        "        },\n",
1816 |        "        \"metadata\": {\n",
1817 |        "            \"picky\": true\n",
1818 |        "        },\n",
1819 |        "        \"content\": {\n",
1820 |        "            \"execution_state\": \"busy\"\n",
1821 |        "        },\n",
1822 |        "        \"buffers\": [],\n",
1823 |        "        \"channel\": \"iopub\"\n",
1824 |        "    },\n",
1825 |        "    \"type\": \"receive\",\n",
1826 |        "    \"time\": 1603477365.3867438\n",
1827 |        "}\n",
1828 |        "    
\n", 1829 | "
\n", 1830 | " \n", 1831 | " busy\n", 1832 | "
" 1833 | ], 1834 | "text/plain": [ 1835 | "" 1836 | ] 1837 | }, 1838 | "metadata": {}, 1839 | "output_type": "display_data" 1840 | }, 1841 | { 1842 | "data": { 1843 | "text/html": [ 1844 | "
\n", 1845 | "
\n", 1846 | " status ⛏\n", 1847 | " \n", 1848 | "
\n",
1849 |        "    {\n",
1850 |        "    \"payload\": {\n",
1851 |        "        \"header\": {\n",
1852 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_18\",\n",
1853 |        "            \"msg_type\": \"status\",\n",
1854 |        "            \"username\": \"kylek\",\n",
1855 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1856 |        "            \"date\": \"2020-10-23T18:22:45.359764Z\",\n",
1857 |        "            \"version\": \"5.3\"\n",
1858 |        "        },\n",
1859 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_18\",\n",
1860 |        "        \"msg_type\": \"status\",\n",
1861 |        "        \"parent_header\": {\n",
1862 |        "            \"msg_id\": \"60acf4f4d79642e990a24478e064f2e9\",\n",
1863 |        "            \"username\": \"username\",\n",
1864 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1865 |        "            \"msg_type\": \"execute_request\",\n",
1866 |        "            \"version\": \"5.2\",\n",
1867 |        "            \"date\": \"2020-10-23T18:22:45.359649Z\"\n",
1868 |        "        },\n",
1869 |        "        \"metadata\": {\n",
1870 |        "            \"picky\": true\n",
1871 |        "        },\n",
1872 |        "        \"content\": {\n",
1873 |        "            \"execution_state\": \"busy\"\n",
1874 |        "        },\n",
1875 |        "        \"buffers\": [],\n",
1876 |        "        \"channel\": \"iopub\"\n",
1877 |        "    },\n",
1878 |        "    \"type\": \"receive\",\n",
1879 |        "    \"time\": 1603477365.3872838\n",
1880 |        "}\n",
1881 |        "    
\n", 1882 | "
\n", 1883 | " \n", 1884 | " busy\n", 1885 | "
" 1886 | ], 1887 | "text/plain": [ 1888 | "" 1889 | ] 1890 | }, 1891 | "metadata": {}, 1892 | "output_type": "display_data" 1893 | }, 1894 | { 1895 | "data": { 1896 | "text/html": [ 1897 | "
\n", 1898 | "
\n", 1899 | " status ⛏\n", 1900 | " \n", 1901 | "
\n",
1902 |        "    {\n",
1903 |        "    \"payload\": {\n",
1904 |        "        \"header\": {\n",
1905 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_19\",\n",
1906 |        "            \"msg_type\": \"status\",\n",
1907 |        "            \"username\": \"kylek\",\n",
1908 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1909 |        "            \"date\": \"2020-10-23T18:22:45.361038Z\",\n",
1910 |        "            \"version\": \"5.3\"\n",
1911 |        "        },\n",
1912 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_19\",\n",
1913 |        "        \"msg_type\": \"status\",\n",
1914 |        "        \"parent_header\": {\n",
1915 |        "            \"msg_id\": \"60acf4f4d79642e990a24478e064f2e9\",\n",
1916 |        "            \"username\": \"username\",\n",
1917 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1918 |        "            \"msg_type\": \"execute_request\",\n",
1919 |        "            \"version\": \"5.2\",\n",
1920 |        "            \"date\": \"2020-10-23T18:22:45.359649Z\"\n",
1921 |        "        },\n",
1922 |        "        \"metadata\": {\n",
1923 |        "            \"picky\": true\n",
1924 |        "        },\n",
1925 |        "        \"content\": {\n",
1926 |        "            \"execution_state\": \"idle\"\n",
1927 |        "        },\n",
1928 |        "        \"buffers\": [],\n",
1929 |        "        \"channel\": \"iopub\"\n",
1930 |        "    },\n",
1931 |        "    \"type\": \"receive\",\n",
1932 |        "    \"time\": 1603477365.3879359\n",
1933 |        "}\n",
1934 |        "    
\n", 1935 | "
\n", 1936 | " \n", 1937 | " idle\n", 1938 | "
" 1939 | ], 1940 | "text/plain": [ 1941 | "" 1942 | ] 1943 | }, 1944 | "metadata": {}, 1945 | "output_type": "display_data" 1946 | }, 1947 | { 1948 | "data": { 1949 | "text/html": [ 1950 | "
\n", 1951 | "
\n", 1952 | " status ⛏\n", 1953 | " \n", 1954 | "
\n",
1955 |        "    {\n",
1956 |        "    \"payload\": {\n",
1957 |        "        \"header\": {\n",
1958 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_20\",\n",
1959 |        "            \"msg_type\": \"status\",\n",
1960 |        "            \"username\": \"kylek\",\n",
1961 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
1962 |        "            \"date\": \"2020-10-23T18:22:45.362397Z\",\n",
1963 |        "            \"version\": \"5.3\"\n",
1964 |        "        },\n",
1965 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_20\",\n",
1966 |        "        \"msg_type\": \"status\",\n",
1967 |        "        \"parent_header\": {\n",
1968 |        "            \"msg_id\": \"60acf4f4d79642e990a24478e064f2e9\",\n",
1969 |        "            \"username\": \"username\",\n",
1970 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
1971 |        "            \"msg_type\": \"execute_request\",\n",
1972 |        "            \"version\": \"5.2\",\n",
1973 |        "            \"date\": \"2020-10-23T18:22:45.359649Z\"\n",
1974 |        "        },\n",
1975 |        "        \"metadata\": {\n",
1976 |        "            \"picky\": true\n",
1977 |        "        },\n",
1978 |        "        \"content\": {\n",
1979 |        "            \"execution_state\": \"busy\"\n",
1980 |        "        },\n",
1981 |        "        \"buffers\": [],\n",
1982 |        "        \"channel\": \"iopub\"\n",
1983 |        "    },\n",
1984 |        "    \"type\": \"receive\",\n",
1985 |        "    \"time\": 1603477365.3886318\n",
1986 |        "}\n",
1987 |        "    
\n", 1988 | "
\n", 1989 | " \n", 1990 | " busy\n", 1991 | "
" 1992 | ], 1993 | "text/plain": [ 1994 | "" 1995 | ] 1996 | }, 1997 | "metadata": {}, 1998 | "output_type": "display_data" 1999 | }, 2000 | { 2001 | "data": { 2002 | "text/html": [ 2003 | "
\n", 2004 | "
\n", 2005 | " status 😈\n", 2006 | " \n", 2007 | "
\n",
2008 |        "    {\n",
2009 |        "    \"payload\": {\n",
2010 |        "        \"header\": {\n",
2011 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_0\",\n",
2012 |        "            \"msg_type\": \"status\",\n",
2013 |        "            \"username\": \"kylek\",\n",
2014 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2015 |        "            \"date\": \"2020-10-23T18:22:45.858304Z\",\n",
2016 |        "            \"version\": \"5.3\"\n",
2017 |        "        },\n",
2018 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_0\",\n",
2019 |        "        \"msg_type\": \"status\",\n",
2020 |        "        \"parent_header\": {},\n",
2021 |        "        \"metadata\": {},\n",
2022 |        "        \"content\": {\n",
2023 |        "            \"execution_state\": \"starting\"\n",
2024 |        "        },\n",
2025 |        "        \"buffers\": [],\n",
2026 |        "        \"channel\": \"iopub\"\n",
2027 |        "    },\n",
2028 |        "    \"type\": \"receive\",\n",
2029 |        "    \"time\": 1603477365.860345\n",
2030 |        "}\n",
2031 |        "    
\n", 2032 | "
\n", 2033 | " \n", 2034 | " starting\n", 2035 | "
" 2036 | ], 2037 | "text/plain": [ 2038 | "" 2039 | ] 2040 | }, 2041 | "metadata": {}, 2042 | "output_type": "display_data" 2043 | }, 2044 | { 2045 | "data": { 2046 | "text/html": [ 2047 | "
\n", 2048 | "
\n", 2049 | " kernel_info_request \n", 2050 | " \n", 2051 | "
\n",
2052 |        "    {\n",
2053 |        "    \"payload\": {\n",
2054 |        "        \"header\": {\n",
2055 |        "            \"msg_id\": \"cf2f754792954fb888f46b61a3edfcc4\",\n",
2056 |        "            \"username\": \"username\",\n",
2057 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2058 |        "            \"msg_type\": \"kernel_info_request\",\n",
2059 |        "            \"version\": \"5.2\"\n",
2060 |        "        },\n",
2061 |        "        \"metadata\": {},\n",
2062 |        "        \"content\": {},\n",
2063 |        "        \"buffers\": [],\n",
2064 |        "        \"parent_header\": {},\n",
2065 |        "        \"channel\": \"shell\"\n",
2066 |        "    },\n",
2067 |        "    \"type\": \"send\",\n",
2068 |        "    \"time\": 1603477365.8631568\n",
2069 |        "}\n",
2070 |        "    
\n", 2071 | "
\n", 2072 | " \n", 2073 | " \n", 2074 | "
" 2075 | ], 2076 | "text/plain": [ 2077 | "" 2078 | ] 2079 | }, 2080 | "metadata": {}, 2081 | "output_type": "display_data" 2082 | }, 2083 | { 2084 | "data": { 2085 | "text/html": [ 2086 | "
\n", 2087 | "
\n", 2088 | " status 😈\n", 2089 | " \n", 2090 | "
\n",
2091 |        "    {\n",
2092 |        "    \"payload\": {\n",
2093 |        "        \"header\": {\n",
2094 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_1\",\n",
2095 |        "            \"msg_type\": \"status\",\n",
2096 |        "            \"username\": \"kylek\",\n",
2097 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2098 |        "            \"date\": \"2020-10-23T18:22:45.859758Z\",\n",
2099 |        "            \"version\": \"5.3\"\n",
2100 |        "        },\n",
2101 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_1\",\n",
2102 |        "        \"msg_type\": \"status\",\n",
2103 |        "        \"parent_header\": {\n",
2104 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_0\",\n",
2105 |        "            \"msg_type\": \"kernel_info_request\",\n",
2106 |        "            \"username\": \"kylek\",\n",
2107 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
2108 |        "            \"date\": \"2020-10-23T18:22:45.352779Z\",\n",
2109 |        "            \"version\": \"5.3\"\n",
2110 |        "        },\n",
2111 |        "        \"metadata\": {},\n",
2112 |        "        \"content\": {\n",
2113 |        "            \"execution_state\": \"busy\"\n",
2114 |        "        },\n",
2115 |        "        \"buffers\": [],\n",
2116 |        "        \"channel\": \"iopub\"\n",
2117 |        "    },\n",
2118 |        "    \"type\": \"receive\",\n",
2119 |        "    \"time\": 1603477365.8647387\n",
2120 |        "}\n",
2121 |        "    
\n", 2122 | "
\n", 2123 | " \n", 2124 | " busy\n", 2125 | "
" 2126 | ], 2127 | "text/plain": [ 2128 | "" 2129 | ] 2130 | }, 2131 | "metadata": {}, 2132 | "output_type": "display_data" 2133 | }, 2134 | { 2135 | "data": { 2136 | "text/html": [ 2137 | "
\n", 2138 | "
\n", 2139 | " status 😈\n", 2140 | " \n", 2141 | "
\n",
2142 |        "    {\n",
2143 |        "    \"payload\": {\n",
2144 |        "        \"header\": {\n",
2145 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_3\",\n",
2146 |        "            \"msg_type\": \"status\",\n",
2147 |        "            \"username\": \"kylek\",\n",
2148 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2149 |        "            \"date\": \"2020-10-23T18:22:45.860694Z\",\n",
2150 |        "            \"version\": \"5.3\"\n",
2151 |        "        },\n",
2152 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_3\",\n",
2153 |        "        \"msg_type\": \"status\",\n",
2154 |        "        \"parent_header\": {\n",
2155 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_0\",\n",
2156 |        "            \"msg_type\": \"kernel_info_request\",\n",
2157 |        "            \"username\": \"kylek\",\n",
2158 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
2159 |        "            \"date\": \"2020-10-23T18:22:45.352779Z\",\n",
2160 |        "            \"version\": \"5.3\"\n",
2161 |        "        },\n",
2162 |        "        \"metadata\": {},\n",
2163 |        "        \"content\": {\n",
2164 |        "            \"execution_state\": \"idle\"\n",
2165 |        "        },\n",
2166 |        "        \"buffers\": [],\n",
2167 |        "        \"channel\": \"iopub\"\n",
2168 |        "    },\n",
2169 |        "    \"type\": \"receive\",\n",
2170 |        "    \"time\": 1603477365.8655088\n",
2171 |        "}\n",
2172 |        "    
\n", 2173 | "
\n", 2174 | " \n", 2175 | " idle\n", 2176 | "
" 2177 | ], 2178 | "text/plain": [ 2179 | "" 2180 | ] 2181 | }, 2182 | "metadata": {}, 2183 | "output_type": "display_data" 2184 | }, 2185 | { 2186 | "data": { 2187 | "text/html": [ 2188 | "
\n", 2189 | "
\n", 2190 | " status 😈\n", 2191 | " \n", 2192 | "
\n",
2193 |        "    {\n",
2194 |        "    \"payload\": {\n",
2195 |        "        \"header\": {\n",
2196 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_4\",\n",
2197 |        "            \"msg_type\": \"status\",\n",
2198 |        "            \"username\": \"kylek\",\n",
2199 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2200 |        "            \"date\": \"2020-10-23T18:22:45.862325Z\",\n",
2201 |        "            \"version\": \"5.3\"\n",
2202 |        "        },\n",
2203 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_4\",\n",
2204 |        "        \"msg_type\": \"status\",\n",
2205 |        "        \"parent_header\": {\n",
2206 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_1\",\n",
2207 |        "            \"msg_type\": \"kernel_info_request\",\n",
2208 |        "            \"username\": \"kylek\",\n",
2209 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
2210 |        "            \"date\": \"2020-10-23T18:22:45.354715Z\",\n",
2211 |        "            \"version\": \"5.3\"\n",
2212 |        "        },\n",
2213 |        "        \"metadata\": {},\n",
2214 |        "        \"content\": {\n",
2215 |        "            \"execution_state\": \"busy\"\n",
2216 |        "        },\n",
2217 |        "        \"buffers\": [],\n",
2218 |        "        \"channel\": \"iopub\"\n",
2219 |        "    },\n",
2220 |        "    \"type\": \"receive\",\n",
2221 |        "    \"time\": 1603477365.866006\n",
2222 |        "}\n",
2223 |        "    
\n", 2224 | "
\n", 2225 | " \n", 2226 | " busy\n", 2227 | "
" 2228 | ], 2229 | "text/plain": [ 2230 | "" 2231 | ] 2232 | }, 2233 | "metadata": {}, 2234 | "output_type": "display_data" 2235 | }, 2236 | { 2237 | "data": { 2238 | "text/html": [ 2239 | "
\n", 2240 | "
\n", 2241 | " status 😈\n", 2242 | " \n", 2243 | "
\n",
2244 |        "    {\n",
2245 |        "    \"payload\": {\n",
2246 |        "        \"header\": {\n",
2247 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_6\",\n",
2248 |        "            \"msg_type\": \"status\",\n",
2249 |        "            \"username\": \"kylek\",\n",
2250 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2251 |        "            \"date\": \"2020-10-23T18:22:45.863467Z\",\n",
2252 |        "            \"version\": \"5.3\"\n",
2253 |        "        },\n",
2254 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_6\",\n",
2255 |        "        \"msg_type\": \"status\",\n",
2256 |        "        \"parent_header\": {\n",
2257 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_1\",\n",
2258 |        "            \"msg_type\": \"kernel_info_request\",\n",
2259 |        "            \"username\": \"kylek\",\n",
2260 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
2261 |        "            \"date\": \"2020-10-23T18:22:45.354715Z\",\n",
2262 |        "            \"version\": \"5.3\"\n",
2263 |        "        },\n",
2264 |        "        \"metadata\": {},\n",
2265 |        "        \"content\": {\n",
2266 |        "            \"execution_state\": \"idle\"\n",
2267 |        "        },\n",
2268 |        "        \"buffers\": [],\n",
2269 |        "        \"channel\": \"iopub\"\n",
2270 |        "    },\n",
2271 |        "    \"type\": \"receive\",\n",
2272 |        "    \"time\": 1603477365.8668349\n",
2273 |        "}\n",
2274 |        "    
\n", 2275 | "
\n", 2276 | " \n", 2277 | " idle\n", 2278 | "
" 2279 | ], 2280 | "text/plain": [ 2281 | "" 2282 | ] 2283 | }, 2284 | "metadata": {}, 2285 | "output_type": "display_data" 2286 | }, 2287 | { 2288 | "data": { 2289 | "text/html": [ 2290 | "
\n", 2291 | "
\n", 2292 | " status ⛏\n", 2293 | " \n", 2294 | "
\n",
2295 |        "    {\n",
2296 |        "    \"payload\": {\n",
2297 |        "        \"header\": {\n",
2298 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_21\",\n",
2299 |        "            \"msg_type\": \"status\",\n",
2300 |        "            \"username\": \"kylek\",\n",
2301 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
2302 |        "            \"date\": \"2020-10-23T18:22:45.866262Z\",\n",
2303 |        "            \"version\": \"5.3\"\n",
2304 |        "        },\n",
2305 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_21\",\n",
2306 |        "        \"msg_type\": \"status\",\n",
2307 |        "        \"parent_header\": {\n",
2308 |        "            \"msg_id\": \"cf2f754792954fb888f46b61a3edfcc4\",\n",
2309 |        "            \"username\": \"username\",\n",
2310 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2311 |        "            \"msg_type\": \"kernel_info_request\",\n",
2312 |        "            \"version\": \"5.2\",\n",
2313 |        "            \"date\": \"2020-10-23T18:22:45.866186Z\"\n",
2314 |        "        },\n",
2315 |        "        \"metadata\": {\n",
2316 |        "            \"picky\": true\n",
2317 |        "        },\n",
2318 |        "        \"content\": {\n",
2319 |        "            \"execution_state\": \"busy\"\n",
2320 |        "        },\n",
2321 |        "        \"buffers\": [],\n",
2322 |        "        \"channel\": \"iopub\"\n",
2323 |        "    },\n",
2324 |        "    \"type\": \"receive\",\n",
2325 |        "    \"time\": 1603477365.8682559\n",
2326 |        "}\n",
2327 |        "    
\n", 2328 | "
\n", 2329 | " \n", 2330 | " busy\n", 2331 | "
" 2332 | ], 2333 | "text/plain": [ 2334 | "" 2335 | ] 2336 | }, 2337 | "metadata": {}, 2338 | "output_type": "display_data" 2339 | }, 2340 | { 2341 | "data": { 2342 | "text/html": [ 2343 | "
\n", 2344 | "
\n", 2345 | " status ⛏\n", 2346 | " \n", 2347 | "
\n",
2348 |        "    {\n",
2349 |        "    \"payload\": {\n",
2350 |        "        \"header\": {\n",
2351 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_23\",\n",
2352 |        "            \"msg_type\": \"status\",\n",
2353 |        "            \"username\": \"kylek\",\n",
2354 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
2355 |        "            \"date\": \"2020-10-23T18:22:45.867209Z\",\n",
2356 |        "            \"version\": \"5.3\"\n",
2357 |        "        },\n",
2358 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_23\",\n",
2359 |        "        \"msg_type\": \"status\",\n",
2360 |        "        \"parent_header\": {\n",
2361 |        "            \"msg_id\": \"cf2f754792954fb888f46b61a3edfcc4\",\n",
2362 |        "            \"username\": \"username\",\n",
2363 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2364 |        "            \"msg_type\": \"kernel_info_request\",\n",
2365 |        "            \"version\": \"5.2\",\n",
2366 |        "            \"date\": \"2020-10-23T18:22:45.866186Z\"\n",
2367 |        "        },\n",
2368 |        "        \"metadata\": {\n",
2369 |        "            \"picky\": true\n",
2370 |        "        },\n",
2371 |        "        \"content\": {\n",
2372 |        "            \"execution_state\": \"idle\"\n",
2373 |        "        },\n",
2374 |        "        \"buffers\": [],\n",
2375 |        "        \"channel\": \"iopub\"\n",
2376 |        "    },\n",
2377 |        "    \"type\": \"receive\",\n",
2378 |        "    \"time\": 1603477365.8688738\n",
2379 |        "}\n",
2380 |        "    
\n", 2381 | "
\n", 2382 | " \n", 2383 | " idle\n", 2384 | "
" 2385 | ], 2386 | "text/plain": [ 2387 | "" 2388 | ] 2389 | }, 2390 | "metadata": {}, 2391 | "output_type": "display_data" 2392 | }, 2393 | { 2394 | "data": { 2395 | "text/html": [ 2396 | "
\n", 2397 | "
\n", 2398 | " kernel_info_reply \n", 2399 | " \n", 2400 | "
\n",
2401 |        "    {\n",
2402 |        "    \"payload\": {\n",
2403 |        "        \"header\": {\n",
2404 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_22\",\n",
2405 |        "            \"msg_type\": \"kernel_info_reply\",\n",
2406 |        "            \"username\": \"kylek\",\n",
2407 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
2408 |        "            \"date\": \"2020-10-23T18:22:45.866494Z\",\n",
2409 |        "            \"version\": \"5.3\"\n",
2410 |        "        },\n",
2411 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_22\",\n",
2412 |        "        \"msg_type\": \"kernel_info_reply\",\n",
2413 |        "        \"parent_header\": {\n",
2414 |        "            \"msg_id\": \"cf2f754792954fb888f46b61a3edfcc4\",\n",
2415 |        "            \"username\": \"username\",\n",
2416 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2417 |        "            \"msg_type\": \"kernel_info_request\",\n",
2418 |        "            \"version\": \"5.2\",\n",
2419 |        "            \"date\": \"2020-10-23T18:22:45.866186Z\"\n",
2420 |        "        },\n",
2421 |        "        \"metadata\": {},\n",
2422 |        "        \"content\": {\n",
2423 |        "            \"status\": \"ok\",\n",
2424 |        "            \"protocol_version\": \"5.3\",\n",
2425 |        "            \"implementation\": \"picky\",\n",
2426 |        "            \"implementation_version\": \"0.1\",\n",
2427 |        "            \"language_info\": {\n",
2428 |        "                \"name\": \"python\",\n",
2429 |        "                \"version\": \"3.7.7\",\n",
2430 |        "                \"mimetype\": \"text/x-python\",\n",
2431 |        "                \"codemirror_mode\": {\n",
2432 |        "                    \"name\": \"ipython\",\n",
2433 |        "                    \"version\": 3\n",
2434 |        "                },\n",
2435 |        "                \"pygments_lexer\": \"ipython3\",\n",
2436 |        "                \"nbconvert_exporter\": \"python\",\n",
2437 |        "                \"file_extension\": \".py\"\n",
2438 |        "            },\n",
2439 |        "            \"banner\": \"Pick, the kernel for choosy users! \\u26cf \\n\\nRead more about it at https://github.com/nteract/pick\\n    \",\n",
2440 |        "            \"help_links\": []\n",
2441 |        "        },\n",
2442 |        "        \"buffers\": [],\n",
2443 |        "        \"channel\": \"shell\"\n",
2444 |        "    },\n",
2445 |        "    \"type\": \"receive\",\n",
2446 |        "    \"time\": 1603477365.8699808\n",
2447 |        "}\n",
2448 |        "    
\n", 2449 | "
\n", 2450 | " \n", 2451 | " \n", 2452 | "
" 2453 | ], 2454 | "text/plain": [ 2455 | "" 2456 | ] 2457 | }, 2458 | "metadata": {}, 2459 | "output_type": "display_data" 2460 | }, 2461 | { 2462 | "data": { 2463 | "text/html": [ 2464 | "
\n", 2465 | "
\n", 2466 | " update_display_data \n", 2467 | " \n", 2468 | "
\n",
2469 |        "    {\n",
2470 |        "    \"payload\": {\n",
2471 |        "        \"header\": {\n",
2472 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_24\",\n",
2473 |        "            \"msg_type\": \"update_display_data\",\n",
2474 |        "            \"username\": \"kylek\",\n",
2475 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
2476 |        "            \"date\": \"2020-10-23T18:22:46.068562Z\",\n",
2477 |        "            \"version\": \"5.3\"\n",
2478 |        "        },\n",
2479 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_24\",\n",
2480 |        "        \"msg_type\": \"update_display_data\",\n",
2481 |        "        \"parent_header\": {\n",
2482 |        "            \"msg_id\": \"f3efda7b9d744c08882d648f5e3643e2\",\n",
2483 |        "            \"username\": \"username\",\n",
2484 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2485 |        "            \"msg_type\": \"execute_request\",\n",
2486 |        "            \"version\": \"5.2\",\n",
2487 |        "            \"date\": \"2020-10-23T18:22:45.327037Z\"\n",
2488 |        "        },\n",
2489 |        "        \"metadata\": {},\n",
2490 |        "        \"content\": {\n",
2491 |        "            \"data\": {\n",
2492 |        "                \"text/plain\": \"\",\n",
2493 |        "                \"text/markdown\": \"Runtime very ready!\"\n",
2494 |        "            },\n",
2495 |        "            \"metadata\": {},\n",
2496 |        "            \"transient\": {\n",
2497 |        "                \"display_id\": \"13f93a5254779819\"\n",
2498 |        "            }\n",
2499 |        "        },\n",
2500 |        "        \"buffers\": [],\n",
2501 |        "        \"channel\": \"iopub\"\n",
2502 |        "    },\n",
2503 |        "    \"type\": \"receive\",\n",
2504 |        "    \"time\": 1603477366.0706499\n",
2505 |        "}\n",
2506 |        "    
\n", 2507 | "
\n", 2508 | " \n", 2509 | " \n", 2510 | "
" 2511 | ], 2512 | "text/plain": [ 2513 | "" 2514 | ] 2515 | }, 2516 | "metadata": {}, 2517 | "output_type": "display_data" 2518 | }, 2519 | { 2520 | "data": { 2521 | "text/html": [ 2522 | "
\n", 2523 | "
\n", 2524 | " execute_reply \n", 2525 | " \n", 2526 | "
\n",
2527 |        "    {\n",
2528 |        "    \"payload\": {\n",
2529 |        "        \"header\": {\n",
2530 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_25\",\n",
2531 |        "            \"msg_type\": \"execute_reply\",\n",
2532 |        "            \"username\": \"kylek\",\n",
2533 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
2534 |        "            \"date\": \"2020-10-23T18:22:46.068767Z\",\n",
2535 |        "            \"version\": \"5.3\"\n",
2536 |        "        },\n",
2537 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_25\",\n",
2538 |        "        \"msg_type\": \"execute_reply\",\n",
2539 |        "        \"parent_header\": {\n",
2540 |        "            \"msg_id\": \"f3efda7b9d744c08882d648f5e3643e2\",\n",
2541 |        "            \"username\": \"username\",\n",
2542 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2543 |        "            \"msg_type\": \"execute_request\",\n",
2544 |        "            \"version\": \"5.2\",\n",
2545 |        "            \"date\": \"2020-10-23T18:22:45.327037Z\"\n",
2546 |        "        },\n",
2547 |        "        \"metadata\": {\n",
2548 |        "            \"parametrized-kernel\": true,\n",
2549 |        "            \"status\": \"ok\"\n",
2550 |        "        },\n",
2551 |        "        \"content\": {\n",
2552 |        "            \"status\": \"ok\",\n",
2553 |        "            \"execution_count\": 0,\n",
2554 |        "            \"user_expressions\": {},\n",
2555 |        "            \"payload\": {}\n",
2556 |        "        },\n",
2557 |        "        \"buffers\": [],\n",
2558 |        "        \"channel\": \"shell\"\n",
2559 |        "    },\n",
2560 |        "    \"type\": \"receive\",\n",
2561 |        "    \"time\": 1603477366.0758517\n",
2562 |        "}\n",
2563 |        "    
\n", 2564 | "
\n", 2565 | " \n", 2566 | " \n", 2567 | "
" 2568 | ], 2569 | "text/plain": [ 2570 | "" 2571 | ] 2572 | }, 2573 | "metadata": {}, 2574 | "output_type": "display_data" 2575 | }, 2576 | { 2577 | "data": { 2578 | "text/html": [ 2579 | "
\n", 2580 | "
\n", 2581 | " status 😈\n", 2582 | " \n", 2583 | "
\n",
2584 |        "    {\n",
2585 |        "    \"payload\": {\n",
2586 |        "        \"header\": {\n",
2587 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_7\",\n",
2588 |        "            \"msg_type\": \"status\",\n",
2589 |        "            \"username\": \"kylek\",\n",
2590 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2591 |        "            \"date\": \"2020-10-23T18:22:46.070375Z\",\n",
2592 |        "            \"version\": \"5.3\"\n",
2593 |        "        },\n",
2594 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_7\",\n",
2595 |        "        \"msg_type\": \"status\",\n",
2596 |        "        \"parent_header\": {\n",
2597 |        "            \"msg_id\": \"ec62cdc3f1a94a22a8e878bdd3dbda49\",\n",
2598 |        "            \"username\": \"username\",\n",
2599 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2600 |        "            \"msg_type\": \"execute_request\",\n",
2601 |        "            \"version\": \"5.2\",\n",
2602 |        "            \"date\": \"2020-10-23T18:22:45.356350Z\"\n",
2603 |        "        },\n",
2604 |        "        \"metadata\": {},\n",
2605 |        "        \"content\": {\n",
2606 |        "            \"execution_state\": \"busy\"\n",
2607 |        "        },\n",
2608 |        "        \"buffers\": [],\n",
2609 |        "        \"channel\": \"iopub\"\n",
2610 |        "    },\n",
2611 |        "    \"type\": \"receive\",\n",
2612 |        "    \"time\": 1603477366.0798798\n",
2613 |        "}\n",
2614 |        "    
\n", 2615 | "
\n", 2616 | " \n", 2617 | " busy\n", 2618 | "
" 2619 | ], 2620 | "text/plain": [ 2621 | "" 2622 | ] 2623 | }, 2624 | "metadata": {}, 2625 | "output_type": "display_data" 2626 | }, 2627 | { 2628 | "data": { 2629 | "text/html": [ 2630 | "
\n", 2631 | "
\n", 2632 | " execute_input \n", 2633 | " \n", 2634 | "
\n",
2635 |        "    {\n",
2636 |        "    \"payload\": {\n",
2637 |        "        \"header\": {\n",
2638 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_8\",\n",
2639 |        "            \"msg_type\": \"execute_input\",\n",
2640 |        "            \"username\": \"kylek\",\n",
2641 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2642 |        "            \"date\": \"2020-10-23T18:22:46.070625Z\",\n",
2643 |        "            \"version\": \"5.3\"\n",
2644 |        "        },\n",
2645 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_8\",\n",
2646 |        "        \"msg_type\": \"execute_input\",\n",
2647 |        "        \"parent_header\": {\n",
2648 |        "            \"msg_id\": \"ec62cdc3f1a94a22a8e878bdd3dbda49\",\n",
2649 |        "            \"username\": \"username\",\n",
2650 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2651 |        "            \"msg_type\": \"execute_request\",\n",
2652 |        "            \"version\": \"5.2\",\n",
2653 |        "            \"date\": \"2020-10-23T18:22:45.356350Z\"\n",
2654 |        "        },\n",
2655 |        "        \"metadata\": {},\n",
2656 |        "        \"content\": {\n",
2657 |        "            \"code\": \"if(binascii.unhexlify(data) != b'Did it work?'):\\n    raise Exception(\\\"Kernel received the wrong data\\\")\\nelse:\\n    print(\\\"All set!\\\")\",\n",
2658 |        "            \"execution_count\": 1\n",
2659 |        "        },\n",
2660 |        "        \"buffers\": [],\n",
2661 |        "        \"channel\": \"iopub\"\n",
2662 |        "    },\n",
2663 |        "    \"type\": \"receive\",\n",
2664 |        "    \"time\": 1603477366.0804567\n",
2665 |        "}\n",
2666 |        "    
\n", 2667 | "
\n", 2668 | " \n", 2669 | " \n", 2670 | "
" 2671 | ], 2672 | "text/plain": [ 2673 | "" 2674 | ] 2675 | }, 2676 | "metadata": {}, 2677 | "output_type": "display_data" 2678 | }, 2679 | { 2680 | "data": { 2681 | "text/html": [ 2682 | "
\n", 2683 | "
\n", 2684 | " stream \n", 2685 | " \n", 2686 | "
\n",
2687 |        "    {\n",
2688 |        "    \"payload\": {\n",
2689 |        "        \"header\": {\n",
2690 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_9\",\n",
2691 |        "            \"msg_type\": \"stream\",\n",
2692 |        "            \"username\": \"kylek\",\n",
2693 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2694 |        "            \"date\": \"2020-10-23T18:22:46.072936Z\",\n",
2695 |        "            \"version\": \"5.3\"\n",
2696 |        "        },\n",
2697 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_9\",\n",
2698 |        "        \"msg_type\": \"stream\",\n",
2699 |        "        \"parent_header\": {\n",
2700 |        "            \"msg_id\": \"ec62cdc3f1a94a22a8e878bdd3dbda49\",\n",
2701 |        "            \"username\": \"username\",\n",
2702 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2703 |        "            \"msg_type\": \"execute_request\",\n",
2704 |        "            \"version\": \"5.2\",\n",
2705 |        "            \"date\": \"2020-10-23T18:22:45.356350Z\"\n",
2706 |        "        },\n",
2707 |        "        \"metadata\": {},\n",
2708 |        "        \"content\": {\n",
2709 |        "            \"name\": \"stdout\",\n",
2710 |        "            \"text\": \"All set!\\n\"\n",
2711 |        "        },\n",
2712 |        "        \"buffers\": [],\n",
2713 |        "        \"channel\": \"iopub\"\n",
2714 |        "    },\n",
2715 |        "    \"type\": \"receive\",\n",
2716 |        "    \"time\": 1603477366.0806417\n",
2717 |        "}\n",
2718 |        "    
\n", 2719 | "
\n", 2720 | " \n", 2721 | " \n", 2722 | "
" 2723 | ], 2724 | "text/plain": [ 2725 | "" 2726 | ] 2727 | }, 2728 | "metadata": {}, 2729 | "output_type": "display_data" 2730 | }, 2731 | { 2732 | "data": { 2733 | "text/html": [ 2734 | "
\n", 2735 | "
\n", 2736 | " status 😈\n", 2737 | " \n", 2738 | "
\n",
2739 |        "    {\n",
2740 |        "    \"payload\": {\n",
2741 |        "        \"header\": {\n",
2742 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_11\",\n",
2743 |        "            \"msg_type\": \"status\",\n",
2744 |        "            \"username\": \"kylek\",\n",
2745 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2746 |        "            \"date\": \"2020-10-23T18:22:46.075722Z\",\n",
2747 |        "            \"version\": \"5.3\"\n",
2748 |        "        },\n",
2749 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_11\",\n",
2750 |        "        \"msg_type\": \"status\",\n",
2751 |        "        \"parent_header\": {\n",
2752 |        "            \"msg_id\": \"ec62cdc3f1a94a22a8e878bdd3dbda49\",\n",
2753 |        "            \"username\": \"username\",\n",
2754 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2755 |        "            \"msg_type\": \"execute_request\",\n",
2756 |        "            \"version\": \"5.2\",\n",
2757 |        "            \"date\": \"2020-10-23T18:22:45.356350Z\"\n",
2758 |        "        },\n",
2759 |        "        \"metadata\": {},\n",
2760 |        "        \"content\": {\n",
2761 |        "            \"execution_state\": \"idle\"\n",
2762 |        "        },\n",
2763 |        "        \"buffers\": [],\n",
2764 |        "        \"channel\": \"iopub\"\n",
2765 |        "    },\n",
2766 |        "    \"type\": \"receive\",\n",
2767 |        "    \"time\": 1603477366.0842528\n",
2768 |        "}\n",
2769 |        "    
\n", 2770 | "
\n", 2771 | " \n", 2772 | " idle\n", 2773 | "
" 2774 | ], 2775 | "text/plain": [ 2776 | "" 2777 | ] 2778 | }, 2779 | "metadata": {}, 2780 | "output_type": "display_data" 2781 | }, 2782 | { 2783 | "data": { 2784 | "text/html": [ 2785 | "
\n", 2786 | "
\n", 2787 | " execute_reply \n", 2788 | " \n", 2789 | "
\n",
2790 |        "    {\n",
2791 |        "    \"payload\": {\n",
2792 |        "        \"header\": {\n",
2793 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_10\",\n",
2794 |        "            \"msg_type\": \"execute_reply\",\n",
2795 |        "            \"username\": \"kylek\",\n",
2796 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2797 |        "            \"date\": \"2020-10-23T18:22:46.074872Z\",\n",
2798 |        "            \"version\": \"5.3\"\n",
2799 |        "        },\n",
2800 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_10\",\n",
2801 |        "        \"msg_type\": \"execute_reply\",\n",
2802 |        "        \"parent_header\": {\n",
2803 |        "            \"msg_id\": \"ec62cdc3f1a94a22a8e878bdd3dbda49\",\n",
2804 |        "            \"username\": \"username\",\n",
2805 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2806 |        "            \"msg_type\": \"execute_request\",\n",
2807 |        "            \"version\": \"5.2\",\n",
2808 |        "            \"date\": \"2020-10-23T18:22:45.356350Z\"\n",
2809 |        "        },\n",
2810 |        "        \"metadata\": {\n",
2811 |        "            \"started\": \"2020-10-23T18:22:46.070591Z\",\n",
2812 |        "            \"dependencies_met\": true,\n",
2813 |        "            \"engine\": \"20dc0826-e4c6-42e4-a7e1-5b9750dd7e7e\",\n",
2814 |        "            \"status\": \"ok\"\n",
2815 |        "        },\n",
2816 |        "        \"content\": {\n",
2817 |        "            \"status\": \"ok\",\n",
2818 |        "            \"execution_count\": 1,\n",
2819 |        "            \"user_expressions\": {},\n",
2820 |        "            \"payload\": []\n",
2821 |        "        },\n",
2822 |        "        \"buffers\": [],\n",
2823 |        "        \"channel\": \"shell\"\n",
2824 |        "    },\n",
2825 |        "    \"type\": \"receive\",\n",
2826 |        "    \"time\": 1603477366.0848107\n",
2827 |        "}\n",
2828 |        "    
\n", 2829 | "
\n", 2830 | " \n", 2831 | " \n", 2832 | "
" 2833 | ], 2834 | "text/plain": [ 2835 | "" 2836 | ] 2837 | }, 2838 | "metadata": {}, 2839 | "output_type": "display_data" 2840 | }, 2841 | { 2842 | "data": { 2843 | "text/html": [ 2844 | "
\n", 2845 | "
\n", 2846 | " status 😈\n", 2847 | " \n", 2848 | "
\n",
2849 |        "    {\n",
2850 |        "    \"payload\": {\n",
2851 |        "        \"header\": {\n",
2852 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_12\",\n",
2853 |        "            \"msg_type\": \"status\",\n",
2854 |        "            \"username\": \"kylek\",\n",
2855 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2856 |        "            \"date\": \"2020-10-23T18:22:46.077760Z\",\n",
2857 |        "            \"version\": \"5.3\"\n",
2858 |        "        },\n",
2859 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_12\",\n",
2860 |        "        \"msg_type\": \"status\",\n",
2861 |        "        \"parent_header\": {\n",
2862 |        "            \"msg_id\": \"60acf4f4d79642e990a24478e064f2e9\",\n",
2863 |        "            \"username\": \"username\",\n",
2864 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2865 |        "            \"msg_type\": \"execute_request\",\n",
2866 |        "            \"version\": \"5.2\",\n",
2867 |        "            \"date\": \"2020-10-23T18:22:45.359649Z\"\n",
2868 |        "        },\n",
2869 |        "        \"metadata\": {},\n",
2870 |        "        \"content\": {\n",
2871 |        "            \"execution_state\": \"busy\"\n",
2872 |        "        },\n",
2873 |        "        \"buffers\": [],\n",
2874 |        "        \"channel\": \"iopub\"\n",
2875 |        "    },\n",
2876 |        "    \"type\": \"receive\",\n",
2877 |        "    \"time\": 1603477366.0922189\n",
2878 |        "}\n",
2879 |        "    
\n", 2880 | "
\n", 2881 | " \n", 2882 | " busy\n", 2883 | "
" 2884 | ], 2885 | "text/plain": [ 2886 | "" 2887 | ] 2888 | }, 2889 | "metadata": {}, 2890 | "output_type": "display_data" 2891 | }, 2892 | { 2893 | "data": { 2894 | "text/html": [ 2895 | "
\n", 2896 | "
\n", 2897 | " execute_input \n", 2898 | " \n", 2899 | "
\n",
2900 |        "    {\n",
2901 |        "    \"payload\": {\n",
2902 |        "        \"header\": {\n",
2903 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_13\",\n",
2904 |        "            \"msg_type\": \"execute_input\",\n",
2905 |        "            \"username\": \"kylek\",\n",
2906 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2907 |        "            \"date\": \"2020-10-23T18:22:46.078011Z\",\n",
2908 |        "            \"version\": \"5.3\"\n",
2909 |        "        },\n",
2910 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_13\",\n",
2911 |        "        \"msg_type\": \"execute_input\",\n",
2912 |        "        \"parent_header\": {\n",
2913 |        "            \"msg_id\": \"60acf4f4d79642e990a24478e064f2e9\",\n",
2914 |        "            \"username\": \"username\",\n",
2915 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2916 |        "            \"msg_type\": \"execute_request\",\n",
2917 |        "            \"version\": \"5.2\",\n",
2918 |        "            \"date\": \"2020-10-23T18:22:45.359649Z\"\n",
2919 |        "        },\n",
2920 |        "        \"metadata\": {},\n",
2921 |        "        \"content\": {\n",
2922 |        "            \"code\": \"display('hello')\",\n",
2923 |        "            \"execution_count\": 2\n",
2924 |        "        },\n",
2925 |        "        \"buffers\": [],\n",
2926 |        "        \"channel\": \"iopub\"\n",
2927 |        "    },\n",
2928 |        "    \"type\": \"receive\",\n",
2929 |        "    \"time\": 1603477366.0936527\n",
2930 |        "}\n",
2931 |        "    
\n", 2932 | "
\n", 2933 | " \n", 2934 | " \n", 2935 | "
" 2936 | ], 2937 | "text/plain": [ 2938 | "" 2939 | ] 2940 | }, 2941 | "metadata": {}, 2942 | "output_type": "display_data" 2943 | }, 2944 | { 2945 | "data": { 2946 | "text/html": [ 2947 | "
\n", 2948 | "
\n", 2949 | " display_data \n", 2950 | " \n", 2951 | "
\n",
2952 |        "    {\n",
2953 |        "    \"payload\": {\n",
2954 |        "        \"header\": {\n",
2955 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_14\",\n",
2956 |        "            \"msg_type\": \"display_data\",\n",
2957 |        "            \"username\": \"kylek\",\n",
2958 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
2959 |        "            \"date\": \"2020-10-23T18:22:46.086305Z\",\n",
2960 |        "            \"version\": \"5.3\"\n",
2961 |        "        },\n",
2962 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_14\",\n",
2963 |        "        \"msg_type\": \"display_data\",\n",
2964 |        "        \"parent_header\": {\n",
2965 |        "            \"msg_id\": \"60acf4f4d79642e990a24478e064f2e9\",\n",
2966 |        "            \"username\": \"username\",\n",
2967 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
2968 |        "            \"msg_type\": \"execute_request\",\n",
2969 |        "            \"version\": \"5.2\",\n",
2970 |        "            \"date\": \"2020-10-23T18:22:45.359649Z\"\n",
2971 |        "        },\n",
2972 |        "        \"metadata\": {},\n",
2973 |        "        \"content\": {\n",
2974 |        "            \"data\": {\n",
2975 |        "                \"text/plain\": \"'hello'\"\n",
2976 |        "            },\n",
2977 |        "            \"metadata\": {},\n",
2978 |        "            \"transient\": {}\n",
2979 |        "        },\n",
2980 |        "        \"buffers\": [],\n",
2981 |        "        \"channel\": \"iopub\"\n",
2982 |        "    },\n",
2983 |        "    \"type\": \"receive\",\n",
2984 |        "    \"time\": 1603477366.0939438\n",
2985 |        "}\n",
2986 |        "    
\n", 2987 | "
\n", 2988 | " \n", 2989 | " \n", 2990 | "
" 2991 | ], 2992 | "text/plain": [ 2993 | "" 2994 | ] 2995 | }, 2996 | "metadata": {}, 2997 | "output_type": "display_data" 2998 | }, 2999 | { 3000 | "data": { 3001 | "text/html": [ 3002 | "
\n", 3003 | "
\n", 3004 | " status 😈\n", 3005 | " \n", 3006 | "
\n",
3007 |        "    {\n",
3008 |        "    \"payload\": {\n",
3009 |        "        \"header\": {\n",
3010 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_16\",\n",
3011 |        "            \"msg_type\": \"status\",\n",
3012 |        "            \"username\": \"kylek\",\n",
3013 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
3014 |        "            \"date\": \"2020-10-23T18:22:46.088895Z\",\n",
3015 |        "            \"version\": \"5.3\"\n",
3016 |        "        },\n",
3017 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_16\",\n",
3018 |        "        \"msg_type\": \"status\",\n",
3019 |        "        \"parent_header\": {\n",
3020 |        "            \"msg_id\": \"60acf4f4d79642e990a24478e064f2e9\",\n",
3021 |        "            \"username\": \"username\",\n",
3022 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
3023 |        "            \"msg_type\": \"execute_request\",\n",
3024 |        "            \"version\": \"5.2\",\n",
3025 |        "            \"date\": \"2020-10-23T18:22:45.359649Z\"\n",
3026 |        "        },\n",
3027 |        "        \"metadata\": {},\n",
3028 |        "        \"content\": {\n",
3029 |        "            \"execution_state\": \"idle\"\n",
3030 |        "        },\n",
3031 |        "        \"buffers\": [],\n",
3032 |        "        \"channel\": \"iopub\"\n",
3033 |        "    },\n",
3034 |        "    \"type\": \"receive\",\n",
3035 |        "    \"time\": 1603477366.0971398\n",
3036 |        "}\n",
3037 |        "    
\n", 3038 | "
\n", 3039 | " \n", 3040 | " idle\n", 3041 | "
" 3042 | ], 3043 | "text/plain": [ 3044 | "" 3045 | ] 3046 | }, 3047 | "metadata": {}, 3048 | "output_type": "display_data" 3049 | }, 3050 | { 3051 | "data": { 3052 | "text/html": [ 3053 | "
\n", 3054 | "
\n", 3055 | " execute_reply \n", 3056 | " \n", 3057 | "
\n",
3058 |        "    {\n",
3059 |        "    \"payload\": {\n",
3060 |        "        \"header\": {\n",
3061 |        "            \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_15\",\n",
3062 |        "            \"msg_type\": \"execute_reply\",\n",
3063 |        "            \"username\": \"kylek\",\n",
3064 |        "            \"session\": \"80b93c4f-77c43ac65fb20612f2addc60\",\n",
3065 |        "            \"date\": \"2020-10-23T18:22:46.088080Z\",\n",
3066 |        "            \"version\": \"5.3\"\n",
3067 |        "        },\n",
3068 |        "        \"msg_id\": \"80b93c4f-77c43ac65fb20612f2addc60_15\",\n",
3069 |        "        \"msg_type\": \"execute_reply\",\n",
3070 |        "        \"parent_header\": {\n",
3071 |        "            \"msg_id\": \"60acf4f4d79642e990a24478e064f2e9\",\n",
3072 |        "            \"username\": \"username\",\n",
3073 |        "            \"session\": \"00f9e2047f51428289b37ee7e40a3e70\",\n",
3074 |        "            \"msg_type\": \"execute_request\",\n",
3075 |        "            \"version\": \"5.2\",\n",
3076 |        "            \"date\": \"2020-10-23T18:22:45.359649Z\"\n",
3077 |        "        },\n",
3078 |        "        \"metadata\": {\n",
3079 |        "            \"started\": \"2020-10-23T18:22:46.077983Z\",\n",
3080 |        "            \"dependencies_met\": true,\n",
3081 |        "            \"engine\": \"20dc0826-e4c6-42e4-a7e1-5b9750dd7e7e\",\n",
3082 |        "            \"status\": \"ok\"\n",
3083 |        "        },\n",
3084 |        "        \"content\": {\n",
3085 |        "            \"status\": \"ok\",\n",
3086 |        "            \"execution_count\": 2,\n",
3087 |        "            \"user_expressions\": {},\n",
3088 |        "            \"payload\": []\n",
3089 |        "        },\n",
3090 |        "        \"buffers\": [],\n",
3091 |        "        \"channel\": \"shell\"\n",
3092 |        "    },\n",
3093 |        "    \"type\": \"receive\",\n",
3094 |        "    \"time\": 1603477366.0977879\n",
3095 |        "}\n",
3096 |        "    
\n", 3097 | "
\n", 3098 | " \n", 3099 | " \n", 3100 | "
" 3101 | ], 3102 | "text/plain": [ 3103 | "" 3104 | ] 3105 | }, 3106 | "metadata": {}, 3107 | "output_type": "display_data" 3108 | }, 3109 | { 3110 | "data": { 3111 | "text/html": [ 3112 | "
\n", 3113 | "
\n", 3114 | " stream \n", 3115 | " \n", 3116 | "
\n",
3117 |        "    {\n",
3118 |        "    \"payload\": {\n",
3119 |        "        \"header\": {\n",
3120 |        "            \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_26\",\n",
3121 |        "            \"msg_type\": \"stream\",\n",
3122 |        "            \"username\": \"kylek\",\n",
3123 |        "            \"session\": \"6c67f41c-5d0eb4f049008db9a5098065\",\n",
3124 |        "            \"date\": \"2020-10-23T18:22:46.273335Z\",\n",
3125 |        "            \"version\": \"5.3\"\n",
3126 |        "        },\n",
3127 |        "        \"msg_id\": \"6c67f41c-5d0eb4f049008db9a5098065_26\",\n",
3128 |        "        \"msg_type\": \"stream\",\n",
3129 |        "        \"parent_header\": {},\n",
3130 |        "        \"metadata\": {},\n",
3131 |        "        \"content\": {\n",
3132 |        "            \"name\": \"stdout\",\n",
3133 |        "            \"text\": \"test\\ntest\\n\"\n",
3134 |        "        },\n",
3135 |        "        \"buffers\": [],\n",
3136 |        "        \"channel\": \"iopub\"\n",
3137 |        "    },\n",
3138 |        "    \"type\": \"receive\",\n",
3139 |        "    \"time\": 1603477366.2750268\n",
3140 |        "}\n",
3141 |        "    
\n", 3142 | "
\n", 3143 | " \n", 3144 | " \n", 3145 | "
" 3146 | ], 3147 | "text/plain": [ 3148 | "" 3149 | ] 3150 | }, 3151 | "metadata": {}, 3152 | "output_type": "display_data" 3153 | } 3154 | ], 3155 | "source": [ 3156 | "def simple_message_display(message):\n", 3157 | " \n", 3158 | " is_picky = \"picky\" in message[\"payload\"][\"metadata\"] and message[\"payload\"][\"metadata\"][\"picky\"]\n", 3159 | " \n", 3160 | " msg_type = message['payload']['header']['msg_type']\n", 3161 | " \n", 3162 | " content = \"\"\n", 3163 | " # Looking to see if status messages are being sent by the subkernel and picky itself\n", 3164 | " status_icon = \"⛏\" if is_picky else \"\"\n", 3165 | " if msg_type == \"status\":\n", 3166 | " content = message['payload']['content']['execution_state']\n", 3167 | " if not is_picky:\n", 3168 | " status_icon = \"😈\"\n", 3169 | " \n", 3170 | " icon = \" \"\n", 3171 | " if message['type'] == 'receive':\n", 3172 | " icon = f\"\"\"\"\"\"\n", 3173 | " else: \n", 3174 | " icon = f\"\"\"\"\"\"\n", 3175 | " \n", 3176 | " \n", 3177 | " return HTML(f\"\"\"
\n", 3178 | "
\n", 3179 | " {icon} {message['payload']['header']['msg_type']} {status_icon}\n", 3180 | " \n", 3181 | "
\n",
3182 |     "    {json.dumps(message, indent=4)}\n",
3183 |     "    
\n", 3184 | "
\n", 3185 | " \n", 3186 | " {content}\n", 3187 | "
\"\"\")\n", 3188 | "\n", 3189 | "\n", 3190 | "for message in messages:\n", 3191 | " display(simple_message_display(message))" 3192 | ] 3193 | }, 3194 | { 3195 | "cell_type": "code", 3196 | "execution_count": 94, 3197 | "metadata": {}, 3198 | "outputs": [ 3199 | { 3200 | "name": "stdout", 3201 | "output_type": "stream", 3202 | "text": [ 3203 | "⬅←⬅➡→➡\n" 3204 | ] 3205 | } 3206 | ], 3207 | "source": [ 3208 | "print(\"⬅←⬅\" + \"➡→➡\")" 3209 | ] 3210 | }, 3211 | { 3212 | "cell_type": "code", 3213 | "execution_count": 25, 3214 | "metadata": {}, 3215 | "outputs": [ 3216 | { 3217 | "data": { 3218 | "text/plain": [ 3219 | "[{'payload': {'header': {'msg_id': '80b93c4f-77c43ac65fb20612f2addc60_15',\n", 3220 | " 'msg_type': 'execute_reply',\n", 3221 | " 'username': 'kylek',\n", 3222 | " 'session': '80b93c4f-77c43ac65fb20612f2addc60',\n", 3223 | " 'date': '2020-10-23T18:22:46.088080Z',\n", 3224 | " 'version': '5.3'},\n", 3225 | " 'msg_id': '80b93c4f-77c43ac65fb20612f2addc60_15',\n", 3226 | " 'msg_type': 'execute_reply',\n", 3227 | " 'parent_header': {'msg_id': '60acf4f4d79642e990a24478e064f2e9',\n", 3228 | " 'username': 'username',\n", 3229 | " 'session': '00f9e2047f51428289b37ee7e40a3e70',\n", 3230 | " 'msg_type': 'execute_request',\n", 3231 | " 'version': '5.2',\n", 3232 | " 'date': '2020-10-23T18:22:45.359649Z'},\n", 3233 | " 'metadata': {'started': '2020-10-23T18:22:46.077983Z',\n", 3234 | " 'dependencies_met': True,\n", 3235 | " 'engine': '20dc0826-e4c6-42e4-a7e1-5b9750dd7e7e',\n", 3236 | " 'status': 'ok'},\n", 3237 | " 'content': {'status': 'ok',\n", 3238 | " 'execution_count': 2,\n", 3239 | " 'user_expressions': {},\n", 3240 | " 'payload': []},\n", 3241 | " 'buffers': [],\n", 3242 | " 'channel': 'shell'},\n", 3243 | " 'type': 'receive',\n", 3244 | " 'time': 1603477366.0977879}]" 3245 | ] 3246 | }, 3247 | "execution_count": 25, 3248 | "metadata": {}, 3249 | "output_type": "execute_result" 3250 | } 3251 | ], 3252 | "source": [ 3253 | "random.sample(messages, 1)" 3254 | ] 3255 | }, 3256 | { 3257 | "cell_type": "code", 3258 | "execution_count": null, 3259 | "metadata": {}, 3260 | "outputs": [], 3261 | "source": [] 3262 | } 3263 | ], 3264 | "metadata": { 3265 | "kernelspec": { 3266 | "display_name": "Python 3", 3267 | "language": "python", 3268 | "name": "python3" 3269 | }, 3270 | "language_info": { 3271 | "codemirror_mode": { 3272 | "name": "ipython", 3273 | "version": 3 3274 | }, 3275 | "file_extension": ".py", 3276 | "mimetype": "text/x-python", 3277 | "name": "python", 3278 | "nbconvert_exporter": "python", 3279 | "pygments_lexer": "ipython3", 3280 | "version": "3.7.7" 3281 | } 3282 | }, 3283 | "nbformat": 4, 3284 | "nbformat_minor": 4 3285 | } 3286 | -------------------------------------------------------------------------------- /devtools/unpack_har.py: -------------------------------------------------------------------------------- 1 | import json 2 | import click 3 | 4 | 5 | def unpack_messages(data): 6 | all_websocket_messages = [] 7 | 8 | for entry in data["log"]["entries"]: 9 | if "_webSocketMessages" in entry: 10 | # We have a collection of websocket messages 11 | for message in entry["_webSocketMessages"]: 12 | # Now we can deserialize the kernel message 13 | clean_message = { 14 | "payload": json.loads(message["data"]), 15 | "type": message["type"], 16 | "time": message["time"], 17 | } 18 | all_websocket_messages.append(clean_message) 19 | return all_websocket_messages 20 | 21 | 22 | @click.command() 23 | @click.argument("input_har", type=click.File("r")) 24 | @click.argument("output_json", type=click.File("w")) 25 | def unpack(input_har, output_json): 26 | data = json.load(input_har) 27 | 28 | unpack_messages(data) 29 | 30 | json.dump(all_websocket_messages, output_json, indent=4) 31 | 32 | 33 | if __name__ == "__main__": 34 | unpack() 35 | 36 | -------------------------------------------------------------------------------- /integration-tests/basic-execution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "%%kernel.ipykernel\n", 10 | "\n", 11 | "-c\n", 12 | "\"import binascii; data = binascii.hexlify(b'Did it work?')\"" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": null, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "if(binascii.unhexlify(data) != b'Did it work?'):\n", 22 | " raise Exception(\"Kernel received the wrong data\")\n", 23 | "else:\n", 24 | " print(\"All set!\")" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": {}, 31 | "outputs": [], 32 | "source": [ 33 | "display('hello')" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": {}, 40 | "outputs": [], 41 | "source": [] 42 | } 43 | ], 44 | "metadata": { 45 | "kernelspec": { 46 | "display_name": "Picky Python 3", 47 | "language": "python", 48 | "name": "picky-python3" 49 | }, 50 | "language_info": { 51 | "codemirror_mode": { 52 | "name": "ipython", 53 | "version": 3 54 | }, 55 | "file_extension": ".py", 56 | "mimetype": "text/x-python", 57 | "name": "python", 58 | "nbconvert_exporter": "python", 59 | "pygments_lexer": "ipython3", 60 | "version": "3.7.7" 61 | } 62 | }, 63 | "nbformat": 4, 64 | "nbformat_minor": 4 65 | } 66 | -------------------------------------------------------------------------------- /pick_kernel/__init__.py: -------------------------------------------------------------------------------- 1 | from .version import version as __version__ 2 | -------------------------------------------------------------------------------- /pick_kernel/__main__.py: -------------------------------------------------------------------------------- 1 | from .kernel import main 2 | 3 | if __name__ == "__main__": 4 | main() 5 | -------------------------------------------------------------------------------- /pick_kernel/exceptions.py: -------------------------------------------------------------------------------- 1 | class PickRegistrationException(Exception): 2 | """Raised when there is an issue registering a kernel""" 3 | -------------------------------------------------------------------------------- /pick_kernel/kernel.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import base64 3 | import os 4 | import sys 5 | from binascii import hexlify 6 | from queue import Empty 7 | from functools import partial 8 | 9 | from traceback import format_tb 10 | 11 | from tornado.ioloop import IOLoop 12 | 13 | import zmq 14 | from zmq.asyncio import Context 15 | 16 | from .kernelbase import Kernel 17 | from ipykernel.kernelapp import IPKernelApp 18 | from IPython.core.formatters import DisplayFormatter 19 | from IPython.display import Markdown 20 | 21 | 22 | from . import subkernels 23 | from .exceptions import PickRegistrationException 24 | 25 | banner = """\ 26 | Proxies to another kernel, launched underneath 27 | """ 28 | 29 | __version__ = "0.1" 30 | 31 | 32 | class KernelProxy(object): 33 | """A proxy for a single kernel 34 | 35 | The kernel's `shell` channel is used for request/reply calls to the kernel. 36 | The `KernelProxy` hooks up relay of messages on the shell channel. 37 | """ 38 | 39 | def __init__(self, manager, shell_upstream): 40 | self.manager = manager 41 | # TODO: Connect Control & STDIN 42 | self.shell = self.manager.connect_shell() 43 | # The shell channel from the wrapper kernel 44 | self.shell_upstream = shell_upstream 45 | 46 | # provide the url 47 | self.iopub_url = self.manager._make_url("iopub") 48 | IOLoop.current().add_callback(self.relay_shell) 49 | 50 | async def relay_shell(self): 51 | """Coroutine for relaying any shell replies""" 52 | while True: 53 | msg = await self.shell.recv_multipart() 54 | self.shell_upstream.send_multipart(msg) 55 | 56 | 57 | class PickyKernel(Kernel): 58 | """A kernel that accepts kernel magics which configure the environment""" 59 | 60 | implementation = "picky" 61 | implementation_version = __version__ 62 | 63 | # This banner only shows on `jupyter console` (at the command line). 64 | banner = """Pick, the kernel for choosy users! ⛏ 65 | 66 | Read more about it at https://github.com/nteract/pick 67 | """ 68 | 69 | # NOTE: This may not match the underlying kernel we launch. However, we need a default 70 | # for the initial launch. 71 | # TODO: Dynamically send over the underlying kernel's kernel_info_reply later 72 | language_info = { 73 | "name": "python", 74 | "version": sys.version.split()[0], 75 | "mimetype": "text/x-python", 76 | "codemirror_mode": {"name": "ipython", "version": sys.version_info[0]}, 77 | "pygments_lexer": "ipython3", 78 | "nbconvert_exporter": "python", 79 | "file_extension": ".py", 80 | } 81 | 82 | default_kernel = None 83 | default_config = None 84 | 85 | def __init__(self, *args, **kwargs): 86 | super().__init__(*args, **kwargs) 87 | # Ensure the kernel we work with uses Futures on recv, so we can await them 88 | self.context = ctx = Context.instance() 89 | 90 | # Our subscription to messages from the kernel we launch 91 | self.iosub = ctx.socket(zmq.SUB) 92 | self.iosub.subscribe = b"" 93 | 94 | # From kernelapp.py, shell_streams are typically shell_stream, control_stream 95 | self.shell_stream = self.shell_streams[0] 96 | 97 | # Start with no child kernel 98 | self.child_kernel = None 99 | 100 | self.kernel_config = None 101 | 102 | self.acquiring_kernel = asyncio.Lock() 103 | self.kernel_launched = asyncio.Event() 104 | 105 | self.display_formatter = DisplayFormatter() 106 | 107 | def start(self): 108 | """Start the PickyKernel and its event loop""" 109 | super().start() 110 | loop = IOLoop.current() 111 | # Collect and send all IOPub messages, for all time 112 | # TODO: Check errors from this loop and restart as needed (or shut down the kernel) 113 | loop.add_callback(self.relay_iopub_messages) 114 | 115 | async def relay_iopub_messages(self): 116 | """Relay messages received by the Picky Kernel 117 | to the consumer client (e.g. notebook) 118 | """ 119 | while True: 120 | msg = await self.iosub.recv_multipart() 121 | # Send the message up to the consumer (for example, the notebook) 122 | self.iopub_socket.send_multipart(msg) 123 | 124 | async def start_kernel(self, name=None, config=None): 125 | # Create a connection file that is named as a child of this kernel 126 | base, ext = os.path.splitext(self.parent.connection_file) 127 | connection_file = "{base}-child{ext}".format(base=base, ext=ext,) 128 | 129 | subkernel = subkernels.get_subkernel(name) 130 | 131 | try: 132 | km = await subkernel.launch( 133 | config=config, 134 | session=self.session, 135 | context=self.context, 136 | connection_file=connection_file, 137 | ) 138 | except Exception as err: 139 | self.log.error(err) 140 | 141 | kernel = KernelProxy(manager=km, shell_upstream=self.shell_stream) 142 | self.iosub.connect(kernel.iopub_url) 143 | 144 | # Make sure the kernel is really started. We do that by using 145 | # kernel_info_requests here. 146 | # 147 | # In the future we should incorporate kernel logs (output of the kernel process), then 148 | # and send back all the information back to the user as display output. 149 | 150 | # Create a temporary KernelClient for waiting for the kernel to start 151 | kc = km.client() 152 | kc.start_channels() 153 | 154 | # Wait for kernel info reply on shell channel 155 | while True: 156 | self.log.debug("querying kernel info") 157 | kc.kernel_info(reply=False) 158 | try: 159 | msg = await kc.shell_channel.get_msg(timeout=1) 160 | except Empty: 161 | pass 162 | else: 163 | if msg["msg_type"] == "kernel_info_reply": 164 | # Now we know the kernel is (mostly) ready. 165 | # However, most kernels are not quite ready at this point to send 166 | # on more execution. 167 | # 168 | # Do we wait for a further status: idle on iopub? 169 | # Wait for idle? 170 | # Wait for particular logs from the stdout of the kernel process? 171 | break 172 | 173 | if not await kc.is_alive(): 174 | # TODO: Emit child kernel death message into the notebook output 175 | self.log.error("Kernel died while launching") 176 | raise RuntimeError("Kernel died before replying to kernel_info") 177 | 178 | # Wait before sending another kernel info request 179 | await asyncio.sleep(0.1) 180 | 181 | # Flush IOPub channel on our (temporary) kernel client 182 | while True: 183 | try: 184 | msg = await kc.iopub_channel.get_msg(timeout=0.2) 185 | except Empty: 186 | break 187 | 188 | # Clean up our temporary kernel client 189 | kc.stop_channels() 190 | 191 | # Inform all waiters for the kernel that it is ready 192 | self.kernel_launched.set() 193 | 194 | return kernel 195 | 196 | async def get_kernel(self): 197 | """Get a launched child kernel""" 198 | # Ensure that the kernel is launched 199 | await self.kernel_launched.wait() 200 | if self.child_kernel is None: 201 | self.log.error("the child kernel was not available") 202 | 203 | return self.child_kernel 204 | 205 | async def queue_before_relay(self, stream, ident, parent): 206 | """Queue messages before sending between the child and parent kernels.""" 207 | if not self.kernel_launched.is_set(): 208 | self._publish_status("busy") 209 | 210 | kernel = await self.get_kernel() 211 | self._publish_status("idle") 212 | self.session.send(kernel.shell, parent, ident=ident) 213 | 214 | def display(self, obj, parent, display_id=False, update=False): 215 | """Publish a rich format of an object from our picky kernel, associated with the parent message""" 216 | data, metadata = self.display_formatter.format(obj) 217 | 218 | if metadata is None: 219 | metadata = {} 220 | 221 | transient = {} 222 | if display_id: 223 | transient = {"display_id": display_id} 224 | 225 | content = {"data": data, "metadata": metadata, "transient": transient} 226 | 227 | self.session.send( 228 | self.iopub_socket, 229 | "update_display_data" if update else "display_data", 230 | content, 231 | parent=parent, 232 | ident=self._topic("display_data"), 233 | ) 234 | 235 | def _publish_error(self, exc_info, parent=None): 236 | """send error on IOPub""" 237 | exc_type, exception, traceback = exc_info 238 | 239 | content = { 240 | "ename": exc_type.__name__, 241 | "evalue": str(exception), 242 | "traceback": format_tb(traceback), 243 | } 244 | self.session.send( 245 | self.iopub_socket, 246 | "error", 247 | content, 248 | parent=parent, 249 | ident=self._topic("error"), 250 | ) 251 | 252 | def _publish_execute_reply_error(self, exc_info, ident, parent): 253 | exc_type, exception, traceback = exc_info 254 | 255 | reply_content = { 256 | "status": "error", 257 | "ename": exc_type.__name__, 258 | "evalue": str(exception), 259 | "traceback": format_tb(traceback), 260 | # Since this isn't the underlying kernel 261 | "execution_count": None, 262 | } 263 | 264 | self.session.send( 265 | self.shell_stream, 266 | "execute_reply", 267 | reply_content, 268 | parent=parent, 269 | metadata={"status": "error"}, 270 | ident=ident, 271 | ) 272 | 273 | def _publish_status(self, status, parent=None): 274 | """send status (busy/idle) on IOPub""" 275 | self.session.send( 276 | self.iopub_socket, 277 | "status", 278 | {"execution_state": status}, 279 | parent=parent or self._parent_header, 280 | ident=self._topic("status"), 281 | metadata={"picky": True}, 282 | ) 283 | 284 | async def async_execute_request(self, stream, ident, parent): 285 | """process an execution request, sending it on to the child kernel or launching one 286 | if it has not been started. 287 | """ 288 | # Announce that we are busy handling this request 289 | self._publish_status("busy") 290 | 291 | # Only one kernel can be acquired at the same time 292 | async with self.acquiring_kernel: 293 | # Check for configuration code first 294 | content = parent["content"] 295 | code = content["code"] 296 | 297 | config, kernel_name = self.parse_cell(content["code"]) 298 | has_config = bool(config) 299 | 300 | if has_config: 301 | # Report back that we'll be running the config code 302 | # NOTE: We are, for the time being, ignoring the silent flag, store_history, etc. 303 | self._publish_execute_input(code, parent, self.execution_count) 304 | 305 | # User is attempting to run a cell with config after the kernel is started, 306 | # so we inform them and bail 307 | if has_config and self.child_kernel is not None: 308 | self.display( 309 | Markdown( 310 | f"""## Kernel already configured and launched. 311 | 312 | You can only run the `%%kernel.{kernel_name}` cell at the top of your notebook and the 313 | start of your session. Please **restart your kernel** and run the cell again if 314 | you want to change configuration. 315 | """ 316 | ), 317 | parent=parent, 318 | ) 319 | 320 | # Complete the "execution request" so the jupyter client (e.g. the notebook) thinks 321 | # execution is finished 322 | reply_content = { 323 | "status": "error", 324 | # Since our result is not part of `In` or `Out`, ensure 325 | # that the execution count is unset 326 | "execution_count": None, 327 | "user_expressions": {}, 328 | "payload": {}, 329 | } 330 | 331 | metadata = { 332 | "parametrized-kernel": True, 333 | "status": reply_content["status"], 334 | } 335 | 336 | self.session.send( 337 | stream, 338 | "execute_reply", 339 | reply_content, 340 | parent, 341 | metadata=metadata, 342 | ident=ident, 343 | ) 344 | self._publish_status("idle") 345 | return 346 | 347 | kernel_display_id = hexlify(os.urandom(8)).decode("ascii") 348 | 349 | # Launching a custom kernel 350 | if self.child_kernel is None and has_config: 351 | # Start a kernel now 352 | # If there's config set, we launch with that config 353 | 354 | # If the user is requesting the kernel with config, launch it! 355 | self.display( 356 | Markdown("Launching customized runtime..."), 357 | display_id=kernel_display_id, 358 | parent=parent, 359 | ) 360 | try: 361 | self.child_kernel = await self.start_kernel(kernel_name, config) 362 | except PickRegistrationException as err: 363 | # Get access to the exception info prior to doing any potential awaiting 364 | exc_info = sys.exc_info() 365 | self.log.info(exc_info) 366 | 367 | separator = "\n" 368 | 369 | self.display( 370 | Markdown( 371 | f"""## There is no kernel magic named `{kernel_name}` 372 | 373 | These are the available kernels: 374 | 375 | {separator.join([f"* `{kernel}`" for kernel in subkernels.list_subkernels()])} 376 | 377 | """ 378 | ), 379 | display_id=kernel_display_id, 380 | update=True, 381 | parent=parent, 382 | ) 383 | self.log.error(err) 384 | self._publish_execute_reply_error( 385 | exc_info, ident=ident, parent=parent 386 | ) 387 | self._publish_status("idle", parent=parent) 388 | return 389 | 390 | except Exception as err: 391 | self.log.error(err) 392 | exc_info = sys.exc_info() 393 | self._publish_error(exc_info, parent=parent) 394 | self._publish_execute_reply_error( 395 | exc_info, ident=ident, parent=parent 396 | ) 397 | self._publish_status("idle", parent=parent) 398 | return 399 | 400 | self.display( 401 | Markdown("Runtime ready!"), 402 | display_id=kernel_display_id, 403 | parent=parent, 404 | update=True, 405 | ) 406 | 407 | # Complete the "execution request" so the jupyter client (e.g. the notebook) thinks 408 | # execution is finished 409 | reply_content = { 410 | "status": "ok", 411 | # Our kernel setup is always the zero-th execution (In[] starts at 1) 412 | "execution_count": 0, 413 | # Note: user_expressions are not supported on our kernel creation magic 414 | "user_expressions": {}, 415 | "payload": {}, 416 | } 417 | 418 | metadata = { 419 | "parametrized-kernel": True, 420 | "status": reply_content["status"], 421 | } 422 | 423 | self.session.send( 424 | stream, 425 | "execute_reply", 426 | reply_content, 427 | parent, 428 | metadata=metadata, 429 | ident=ident, 430 | ) 431 | 432 | # With that, we're all done launching the customized kernel and 433 | # pushing updates on the kernel to the user. 434 | return 435 | 436 | # Start the default kernel to run code 437 | if self.child_kernel is None: 438 | self.display( 439 | Markdown("Preparing default kernel..."), 440 | parent=parent, 441 | display_id=kernel_display_id, 442 | ) 443 | self.child_kernel = await self.start_kernel( 444 | self.default_kernel, self.default_config 445 | ) 446 | self.display( 447 | # Wipe out the previous message. 448 | # NOTE: The Jupyter notebook frontend ignores the case of an empty output for 449 | # an update_display_data so we have to publish some empty content instead. 450 | Markdown(""), 451 | parent=parent, 452 | display_id=kernel_display_id, 453 | update=True, 454 | ) 455 | 456 | self.session.send(self.child_kernel.shell, parent, ident=ident) 457 | 458 | def parse_cell(self, cell): 459 | if not cell.startswith("%%kernel."): 460 | return None, None 461 | 462 | try: 463 | # Split off our config from the kernel magic name 464 | magic_name, raw_config = cell.split("\n", 1) 465 | 466 | kernel_name = magic_name.split("%%kernel.")[1] 467 | 468 | return raw_config, kernel_name 469 | except Exception as err: 470 | self.log.error(err) 471 | return None, None 472 | 473 | def _log_task_exceptions(self, task, *args, **kwargs): 474 | try: 475 | task_exception = task.exception() 476 | if task_exception: 477 | self.log.error(task_exception) 478 | self.log.error(task.get_stack(limit=5)) 479 | except asyncio.CancelledError as err: 480 | self.log.error(err) 481 | except Exception as err: 482 | self.log.error(err) 483 | 484 | def relay_execute_to_kernel(self, stream, ident, parent): 485 | # Shove this execution onto the task queue 486 | task = asyncio.create_task(self.async_execute_request(stream, ident, parent)) 487 | task.add_done_callback(partial(self._log_task_exceptions, task)) 488 | 489 | def relay_to_kernel(self, stream, ident, parent): 490 | # relay_to_kernel is synchronous, and we rely on an asynchronous start 491 | # so we create each kernel message as a task... 492 | task = asyncio.create_task(self.queue_before_relay(stream, ident, parent)) 493 | task.add_done_callback(partial(self._log_task_exceptions, task)) 494 | 495 | execute_request = relay_execute_to_kernel 496 | inspect_request = relay_to_kernel 497 | complete_request = relay_to_kernel 498 | 499 | 500 | class PickyKernelApp(IPKernelApp): 501 | """A kernel application for starting a `PickyKernel`, a proxying kernel with options.""" 502 | 503 | kernel_class = PickyKernel 504 | # TODO: Uncomment this to disable IO Capture of this kernel 505 | # outstream_class = None 506 | 507 | 508 | main = PickyKernelApp.launch_instance 509 | -------------------------------------------------------------------------------- /pick_kernel/kernelbase.py: -------------------------------------------------------------------------------- 1 | """Base class for a kernel that talks to frontends over 0MQ. 2 | 3 | This is an active fork of the kernelbase from ipykernel, to make it an async kernelbase. 4 | 5 | One of the issues we've been running into is that some of the functions are classic 6 | synchronous blocking python calls and some are asynchronous. This leads to a mismatch 7 | in timing/coordination. 8 | 9 | """ 10 | 11 | # Plan: create an async kernelbase 12 | # 13 | 14 | # Modified from ipykernel's kernelbase.py 15 | # Copyright (c) IPython Development Team. 16 | # Distributed under the terms of the Modified BSD License. 17 | 18 | from datetime import datetime 19 | from functools import partial 20 | import itertools 21 | import logging 22 | from signal import signal, default_int_handler, SIGINT 23 | import sys 24 | import time 25 | import uuid 26 | 27 | try: 28 | # jupyter_client >= 5, use tz-aware now 29 | from jupyter_client.session import utcnow as now 30 | except ImportError: 31 | # jupyter_client < 5, use local now() 32 | now = datetime.now 33 | 34 | from tornado import ioloop 35 | from tornado import gen 36 | from tornado.queues import PriorityQueue, QueueEmpty 37 | import zmq 38 | from zmq.eventloop.zmqstream import ZMQStream 39 | 40 | from traitlets.config.configurable import SingletonConfigurable 41 | from IPython.core.error import StdinNotImplementedError 42 | from ipython_genutils import py3compat 43 | from ipython_genutils.py3compat import unicode_type, string_types 44 | from ipykernel.jsonutil import json_clean 45 | from traitlets import ( 46 | Any, 47 | Instance, 48 | Float, 49 | Dict, 50 | List, 51 | Set, 52 | Integer, 53 | Unicode, 54 | Bool, 55 | observe, 56 | default, 57 | ) 58 | 59 | from jupyter_client.session import Session 60 | 61 | from ipykernel._version import kernel_protocol_version 62 | 63 | CONTROL_PRIORITY = 1 64 | SHELL_PRIORITY = 10 65 | ABORT_PRIORITY = 20 66 | 67 | 68 | class Kernel(SingletonConfigurable): 69 | 70 | # --------------------------------------------------------------------------- 71 | # Kernel interface 72 | # --------------------------------------------------------------------------- 73 | 74 | # attribute to override with a GUI 75 | eventloop = Any(None) 76 | 77 | @observe("eventloop") 78 | def _update_eventloop(self, change): 79 | """schedule call to eventloop from IOLoop""" 80 | loop = ioloop.IOLoop.current() 81 | if change.new is not None: 82 | loop.add_callback(self.enter_eventloop) 83 | 84 | session = Instance(Session, allow_none=True) 85 | profile_dir = Instance("IPython.core.profiledir.ProfileDir", allow_none=True) 86 | shell_streams = List() 87 | control_stream = Instance(ZMQStream, allow_none=True) 88 | iopub_socket = Any() 89 | iopub_thread = Any() 90 | stdin_socket = Any() 91 | log = Instance(logging.Logger, allow_none=True) 92 | 93 | # identities: 94 | int_id = Integer(-1) 95 | ident = Unicode() 96 | 97 | @default("ident") 98 | def _default_ident(self): 99 | return unicode_type(uuid.uuid4()) 100 | 101 | # This should be overridden by wrapper kernels that implement any real 102 | # language. 103 | language_info = {} 104 | 105 | # any links that should go in the help menu 106 | help_links = List() 107 | 108 | # Private interface 109 | 110 | _darwin_app_nap = Bool( 111 | True, 112 | help="""Whether to use appnope for compatibility with OS X App Nap. 113 | 114 | Only affects OS X >= 10.9. 115 | """, 116 | ).tag(config=True) 117 | 118 | # track associations with current request 119 | _allow_stdin = Bool(False) 120 | _parent_header = Dict() 121 | _parent_ident = Any(b"") 122 | # Time to sleep after flushing the stdout/err buffers in each execute 123 | # cycle. While this introduces a hard limit on the minimal latency of the 124 | # execute cycle, it helps prevent output synchronization problems for 125 | # clients. 126 | # Units are in seconds. The minimum zmq latency on local host is probably 127 | # ~150 microseconds, set this to 500us for now. We may need to increase it 128 | # a little if it's not enough after more interactive testing. 129 | _execute_sleep = Float(0.0005).tag(config=True) 130 | 131 | # Frequency of the kernel's event loop. 132 | # Units are in seconds, kernel subclasses for GUI toolkits may need to 133 | # adapt to milliseconds. 134 | _poll_interval = Float(0.01).tag(config=True) 135 | 136 | stop_on_error_timeout = Float( 137 | 0.1, 138 | config=True, 139 | help="""time (in seconds) to wait for messages to arrive 140 | when aborting queued requests after an error. 141 | 142 | Requests that arrive within this window after an error 143 | will be cancelled. 144 | 145 | Increase in the event of unusually slow network 146 | causing significant delays, 147 | which can manifest as e.g. "Run all" in a notebook 148 | aborting some, but not all, messages after an error. 149 | """, 150 | ) 151 | 152 | # If the shutdown was requested over the network, we leave here the 153 | # necessary reply message so it can be sent by our registered atexit 154 | # handler. This ensures that the reply is only sent to clients truly at 155 | # the end of our shutdown process (which happens after the underlying 156 | # IPython shell's own shutdown). 157 | _shutdown_message = None 158 | 159 | # This is a dict of port number that the kernel is listening on. It is set 160 | # by record_ports and used by connect_request. 161 | _recorded_ports = Dict() 162 | 163 | # set of aborted msg_ids 164 | aborted = Set() 165 | 166 | # Track execution count here. For IPython, we override this to use the 167 | # execution count we store in the shell. 168 | execution_count = 0 169 | 170 | msg_types = [ 171 | "execute_request", 172 | "complete_request", 173 | "inspect_request", 174 | "history_request", 175 | "comm_info_request", 176 | "kernel_info_request", 177 | "connect_request", 178 | "shutdown_request", 179 | "is_complete_request", 180 | # deprecated: 181 | "apply_request", 182 | ] 183 | # add deprecated ipyparallel control messages 184 | control_msg_types = msg_types + ["clear_request", "abort_request"] 185 | 186 | def __init__(self, **kwargs): 187 | super(Kernel, self).__init__(**kwargs) 188 | # Build dict of handlers for message types 189 | self.shell_handlers = {} 190 | for msg_type in self.msg_types: 191 | self.shell_handlers[msg_type] = getattr(self, msg_type) 192 | 193 | self.control_handlers = {} 194 | for msg_type in self.control_msg_types: 195 | self.control_handlers[msg_type] = getattr(self, msg_type) 196 | 197 | @gen.coroutine 198 | def dispatch_control(self, msg): 199 | """dispatch control requests""" 200 | idents, msg = self.session.feed_identities(msg, copy=False) 201 | try: 202 | msg = self.session.deserialize(msg, content=True, copy=False) 203 | except: 204 | self.log.error("Invalid Control Message", exc_info=True) 205 | return 206 | 207 | self.log.debug("Control received: %s", msg) 208 | 209 | # Set the parent message for side effects. 210 | self.set_parent(idents, msg) 211 | self._publish_status(u"busy") 212 | if self._aborting: 213 | self._send_abort_reply(self.control_stream, msg, idents) 214 | self._publish_status(u"idle") 215 | return 216 | 217 | header = msg["header"] 218 | msg_type = header["msg_type"] 219 | 220 | handler = self.control_handlers.get(msg_type, None) 221 | if handler is None: 222 | self.log.error("UNKNOWN CONTROL MESSAGE TYPE: %r", msg_type) 223 | else: 224 | try: 225 | yield gen.maybe_future(handler(self.control_stream, idents, msg)) 226 | except Exception: 227 | self.log.error("Exception in control handler:", exc_info=True) 228 | 229 | sys.stdout.flush() 230 | sys.stderr.flush() 231 | self._publish_status(u"idle") 232 | # flush to ensure reply is sent 233 | self.control_stream.flush(zmq.POLLOUT) 234 | 235 | def should_handle(self, stream, msg, idents): 236 | """Check whether a shell-channel message should be handled 237 | 238 | Allows subclasses to prevent handling of certain messages (e.g. aborted requests). 239 | """ 240 | msg_id = msg["header"]["msg_id"] 241 | if msg_id in self.aborted: 242 | msg_type = msg["header"]["msg_type"] 243 | # is it safe to assume a msg_id will not be resubmitted? 244 | self.aborted.remove(msg_id) 245 | self._send_abort_reply(stream, msg, idents) 246 | return False 247 | return True 248 | 249 | @gen.coroutine 250 | def dispatch_shell(self, stream, msg): 251 | """dispatch shell requests""" 252 | idents, msg = self.session.feed_identities(msg, copy=False) 253 | try: 254 | msg = self.session.deserialize(msg, content=True, copy=False) 255 | except: 256 | self.log.error("Invalid Message", exc_info=True) 257 | return 258 | 259 | # Set the parent message for side effects. 260 | self.set_parent(idents, msg) 261 | self._publish_status(u"busy") 262 | 263 | if self._aborting: 264 | self._send_abort_reply(stream, msg, idents) 265 | self._publish_status(u"idle") 266 | # flush to ensure reply is sent before 267 | # handling the next request 268 | stream.flush(zmq.POLLOUT) 269 | return 270 | 271 | msg_type = msg["header"]["msg_type"] 272 | 273 | # Print some info about this message and leave a '--->' marker, so it's 274 | # easier to trace visually the message chain when debugging. Each 275 | # handler prints its message at the end. 276 | self.log.debug("\n*** MESSAGE TYPE:%s***", msg_type) 277 | self.log.debug(" Content: %s\n --->\n ", msg["content"]) 278 | 279 | if not self.should_handle(stream, msg, idents): 280 | return 281 | 282 | handler = self.shell_handlers.get(msg_type, None) 283 | if handler is None: 284 | self.log.warning("Unknown message type: %r", msg_type) 285 | else: 286 | self.log.debug("%s: %s", msg_type, msg) 287 | try: 288 | self.pre_handler_hook() 289 | except Exception: 290 | self.log.debug("Unable to signal in pre_handler_hook:", exc_info=True) 291 | try: 292 | yield gen.maybe_future(handler(stream, idents, msg)) 293 | except Exception: 294 | self.log.error("Exception in message handler:", exc_info=True) 295 | finally: 296 | try: 297 | self.post_handler_hook() 298 | except Exception: 299 | self.log.debug( 300 | "Unable to signal in post_handler_hook:", exc_info=True 301 | ) 302 | 303 | sys.stdout.flush() 304 | sys.stderr.flush() 305 | self._publish_status(u"idle") 306 | # flush to ensure reply is sent before 307 | # handling the next request 308 | stream.flush(zmq.POLLOUT) 309 | 310 | def pre_handler_hook(self): 311 | """Hook to execute before calling message handler""" 312 | # ensure default_int_handler during handler call 313 | self.saved_sigint_handler = signal(SIGINT, default_int_handler) 314 | 315 | def post_handler_hook(self): 316 | """Hook to execute after calling message handler""" 317 | signal(SIGINT, self.saved_sigint_handler) 318 | 319 | def enter_eventloop(self): 320 | """enter eventloop""" 321 | self.log.info("Entering eventloop %s", self.eventloop) 322 | # record handle, so we can check when this changes 323 | eventloop = self.eventloop 324 | if eventloop is None: 325 | self.log.info("Exiting as there is no eventloop") 326 | return 327 | 328 | def advance_eventloop(): 329 | # check if eventloop changed: 330 | if self.eventloop is not eventloop: 331 | self.log.info("exiting eventloop %s", eventloop) 332 | return 333 | if self.msg_queue.qsize(): 334 | self.log.debug("Delaying eventloop due to waiting messages") 335 | # still messages to process, make the eventloop wait 336 | schedule_next() 337 | return 338 | self.log.debug("Advancing eventloop %s", eventloop) 339 | try: 340 | eventloop(self) 341 | except KeyboardInterrupt: 342 | # Ctrl-C shouldn't crash the kernel 343 | self.log.error("KeyboardInterrupt caught in kernel") 344 | pass 345 | if self.eventloop is eventloop: 346 | # schedule advance again 347 | schedule_next() 348 | 349 | def schedule_next(): 350 | """Schedule the next advance of the eventloop""" 351 | # flush the eventloop every so often, 352 | # giving us a chance to handle messages in the meantime 353 | self.log.debug("Scheduling eventloop advance") 354 | self.io_loop.call_later(1, advance_eventloop) 355 | 356 | # begin polling the eventloop 357 | schedule_next() 358 | 359 | @gen.coroutine 360 | def do_one_iteration(self): 361 | """Process a single shell message 362 | 363 | Any pending control messages will be flushed as well 364 | 365 | .. versionchanged:: 5 366 | This is now a coroutine 367 | """ 368 | # flush messages off of shell streams into the message queue 369 | for stream in self.shell_streams: 370 | stream.flush() 371 | # process all messages higher priority than shell (control), 372 | # and at most one shell message per iteration 373 | priority = 0 374 | while priority is not None and priority < SHELL_PRIORITY: 375 | priority = yield self.process_one(wait=False) 376 | 377 | @gen.coroutine 378 | def process_one(self, wait=True): 379 | """Process one request 380 | 381 | Returns priority of the message handled. 382 | Returns None if no message was handled. 383 | """ 384 | if wait: 385 | priority, t, dispatch, args = yield self.msg_queue.get() 386 | else: 387 | try: 388 | priority, t, dispatch, args = self.msg_queue.get_nowait() 389 | except QueueEmpty: 390 | return None 391 | yield gen.maybe_future(dispatch(*args)) 392 | 393 | @gen.coroutine 394 | def dispatch_queue(self): 395 | """Coroutine to preserve order of message handling 396 | 397 | Ensures that only one message is processing at a time, 398 | even when the handler is async 399 | """ 400 | 401 | while True: 402 | # ensure control stream is flushed before processing shell messages 403 | if self.control_stream: 404 | self.control_stream.flush() 405 | # receive the next message and handle it 406 | try: 407 | yield self.process_one() 408 | except Exception: 409 | self.log.exception("Error in message handler") 410 | 411 | _message_counter = Any( 412 | help="""Monotonic counter of messages 413 | 414 | Ensures messages of the same priority are handled in arrival order. 415 | """, 416 | ) 417 | 418 | @default("_message_counter") 419 | def _message_counter_default(self): 420 | return itertools.count() 421 | 422 | def schedule_dispatch(self, priority, dispatch, *args): 423 | """schedule a message for dispatch""" 424 | idx = next(self._message_counter) 425 | 426 | self.msg_queue.put_nowait((priority, idx, dispatch, args,)) 427 | # ensure the eventloop wakes up 428 | self.io_loop.add_callback(lambda: None) 429 | 430 | def start(self): 431 | """register dispatchers for streams""" 432 | self.io_loop = ioloop.IOLoop.current() 433 | self.msg_queue = PriorityQueue() 434 | self.io_loop.add_callback(self.dispatch_queue) 435 | 436 | if self.control_stream: 437 | self.control_stream.on_recv( 438 | partial( 439 | self.schedule_dispatch, CONTROL_PRIORITY, self.dispatch_control, 440 | ), 441 | copy=False, 442 | ) 443 | 444 | for s in self.shell_streams: 445 | if s is self.control_stream: 446 | continue 447 | s.on_recv( 448 | partial( 449 | self.schedule_dispatch, SHELL_PRIORITY, self.dispatch_shell, s, 450 | ), 451 | copy=False, 452 | ) 453 | 454 | # publish idle status 455 | self._publish_status("starting") 456 | 457 | def record_ports(self, ports): 458 | """Record the ports that this kernel is using. 459 | 460 | The creator of the Kernel instance must call this methods if they 461 | want the :meth:`connect_request` method to return the port numbers. 462 | """ 463 | self._recorded_ports = ports 464 | 465 | # --------------------------------------------------------------------------- 466 | # Kernel request handlers 467 | # --------------------------------------------------------------------------- 468 | 469 | def _publish_execute_input(self, code, parent, execution_count): 470 | """Publish the code request on the iopub stream.""" 471 | 472 | self.session.send( 473 | self.iopub_socket, 474 | u"execute_input", 475 | {u"code": code, u"execution_count": execution_count}, 476 | parent=parent, 477 | ident=self._topic("execute_input"), 478 | ) 479 | 480 | def _publish_status(self, status, parent=None): 481 | """send status (busy/idle) on IOPub""" 482 | self.session.send( 483 | self.iopub_socket, 484 | u"status", 485 | {u"execution_state": status}, 486 | parent=parent or self._parent_header, 487 | ident=self._topic("status"), 488 | ) 489 | 490 | def set_parent(self, ident, parent): 491 | """Set the current parent_header 492 | 493 | Side effects (IOPub messages) and replies are associated with 494 | the request that caused them via the parent_header. 495 | 496 | The parent identity is used to route input_request messages 497 | on the stdin channel. 498 | """ 499 | self._parent_ident = ident 500 | self._parent_header = parent 501 | 502 | def send_response( 503 | self, 504 | stream, 505 | msg_or_type, 506 | content=None, 507 | ident=None, 508 | buffers=None, 509 | track=False, 510 | header=None, 511 | metadata=None, 512 | ): 513 | """Send a response to the message we're currently processing. 514 | 515 | This accepts all the parameters of :meth:`jupyter_client.session.Session.send` 516 | except ``parent``. 517 | 518 | This relies on :meth:`set_parent` having been called for the current 519 | message. 520 | """ 521 | return self.session.send( 522 | stream, 523 | msg_or_type, 524 | content, 525 | self._parent_header, 526 | ident, 527 | buffers, 528 | track, 529 | header, 530 | metadata, 531 | ) 532 | 533 | def init_metadata(self, parent): 534 | """Initialize metadata. 535 | 536 | Run at the beginning of execution requests. 537 | """ 538 | # FIXME: `started` is part of ipyparallel 539 | # Remove for ipykernel 5.0 540 | return { 541 | "started": now(), 542 | } 543 | 544 | def finish_metadata(self, parent, metadata, reply_content): 545 | """Finish populating metadata. 546 | 547 | Run after completing an execution request. 548 | """ 549 | return metadata 550 | 551 | @gen.coroutine 552 | def execute_request(self, stream, ident, parent): 553 | """handle an execute_request""" 554 | 555 | try: 556 | content = parent[u"content"] 557 | code = py3compat.cast_unicode_py2(content[u"code"]) 558 | silent = content[u"silent"] 559 | store_history = content.get(u"store_history", not silent) 560 | user_expressions = content.get("user_expressions", {}) 561 | allow_stdin = content.get("allow_stdin", False) 562 | except: 563 | self.log.error("Got bad msg: ") 564 | self.log.error("%s", parent) 565 | return 566 | 567 | stop_on_error = content.get("stop_on_error", True) 568 | 569 | metadata = self.init_metadata(parent) 570 | 571 | # Re-broadcast our input for the benefit of listening clients, and 572 | # start computing output 573 | if not silent: 574 | self.execution_count += 1 575 | self._publish_execute_input(code, parent, self.execution_count) 576 | 577 | reply_content = yield gen.maybe_future( 578 | self.do_execute(code, silent, store_history, user_expressions, allow_stdin,) 579 | ) 580 | 581 | # Flush output before sending the reply. 582 | sys.stdout.flush() 583 | sys.stderr.flush() 584 | # FIXME: on rare occasions, the flush doesn't seem to make it to the 585 | # clients... This seems to mitigate the problem, but we definitely need 586 | # to better understand what's going on. 587 | if self._execute_sleep: 588 | time.sleep(self._execute_sleep) 589 | 590 | # Send the reply. 591 | reply_content = json_clean(reply_content) 592 | metadata = self.finish_metadata(parent, metadata, reply_content) 593 | 594 | reply_msg = self.session.send( 595 | stream, 596 | u"execute_reply", 597 | reply_content, 598 | parent, 599 | metadata=metadata, 600 | ident=ident, 601 | ) 602 | 603 | self.log.debug("%s", reply_msg) 604 | 605 | if not silent and reply_msg["content"]["status"] == u"error" and stop_on_error: 606 | yield self._abort_queues() 607 | 608 | def do_execute( 609 | self, code, silent, store_history=True, user_expressions=None, allow_stdin=False 610 | ): 611 | """Execute user code. Must be overridden by subclasses. 612 | """ 613 | raise NotImplementedError 614 | 615 | @gen.coroutine 616 | def complete_request(self, stream, ident, parent): 617 | content = parent["content"] 618 | code = content["code"] 619 | cursor_pos = content["cursor_pos"] 620 | 621 | matches = yield gen.maybe_future(self.do_complete(code, cursor_pos)) 622 | matches = json_clean(matches) 623 | completion_msg = self.session.send( 624 | stream, "complete_reply", matches, parent, ident 625 | ) 626 | 627 | def do_complete(self, code, cursor_pos): 628 | """Override in subclasses to find completions. 629 | """ 630 | return { 631 | "matches": [], 632 | "cursor_end": cursor_pos, 633 | "cursor_start": cursor_pos, 634 | "metadata": {}, 635 | "status": "ok", 636 | } 637 | 638 | @gen.coroutine 639 | def inspect_request(self, stream, ident, parent): 640 | content = parent["content"] 641 | 642 | reply_content = yield gen.maybe_future( 643 | self.do_inspect( 644 | content["code"], content["cursor_pos"], content.get("detail_level", 0), 645 | ) 646 | ) 647 | # Before we send this object over, we scrub it for JSON usage 648 | reply_content = json_clean(reply_content) 649 | msg = self.session.send(stream, "inspect_reply", reply_content, parent, ident) 650 | self.log.debug("%s", msg) 651 | 652 | def do_inspect(self, code, cursor_pos, detail_level=0): 653 | """Override in subclasses to allow introspection. 654 | """ 655 | return {"status": "ok", "data": {}, "metadata": {}, "found": False} 656 | 657 | @gen.coroutine 658 | def history_request(self, stream, ident, parent): 659 | content = parent["content"] 660 | 661 | reply_content = yield gen.maybe_future(self.do_history(**content)) 662 | 663 | reply_content = json_clean(reply_content) 664 | msg = self.session.send(stream, "history_reply", reply_content, parent, ident) 665 | self.log.debug("%s", msg) 666 | 667 | def do_history( 668 | self, 669 | hist_access_type, 670 | output, 671 | raw, 672 | session=None, 673 | start=None, 674 | stop=None, 675 | n=None, 676 | pattern=None, 677 | unique=False, 678 | ): 679 | """Override in subclasses to access history. 680 | """ 681 | return {"status": "ok", "history": []} 682 | 683 | def connect_request(self, stream, ident, parent): 684 | if self._recorded_ports is not None: 685 | content = self._recorded_ports.copy() 686 | else: 687 | content = {} 688 | content["status"] = "ok" 689 | msg = self.session.send(stream, "connect_reply", content, parent, ident) 690 | self.log.debug("%s", msg) 691 | 692 | @property 693 | def kernel_info(self): 694 | return { 695 | "protocol_version": kernel_protocol_version, 696 | "implementation": self.implementation, 697 | "implementation_version": self.implementation_version, 698 | "language_info": self.language_info, 699 | "banner": self.banner, 700 | "help_links": self.help_links, 701 | } 702 | 703 | def kernel_info_request(self, stream, ident, parent): 704 | content = {"status": "ok"} 705 | content.update(self.kernel_info) 706 | msg = self.session.send(stream, "kernel_info_reply", content, parent, ident) 707 | self.log.debug("%s", msg) 708 | 709 | def comm_info_request(self, stream, ident, parent): 710 | content = parent["content"] 711 | target_name = content.get("target_name", None) 712 | 713 | # Should this be moved to ipkernel? 714 | if hasattr(self, "comm_manager"): 715 | comms = { 716 | k: dict(target_name=v.target_name) 717 | for (k, v) in self.comm_manager.comms.items() 718 | if v.target_name == target_name or target_name is None 719 | } 720 | else: 721 | comms = {} 722 | reply_content = dict(comms=comms, status="ok") 723 | msg = self.session.send(stream, "comm_info_reply", reply_content, parent, ident) 724 | self.log.debug("%s", msg) 725 | 726 | @gen.coroutine 727 | def shutdown_request(self, stream, ident, parent): 728 | content = yield gen.maybe_future(self.do_shutdown(parent["content"]["restart"])) 729 | self.session.send(stream, u"shutdown_reply", content, parent, ident=ident) 730 | # same content, but different msg_id for broadcasting on IOPub 731 | self._shutdown_message = self.session.msg(u"shutdown_reply", content, parent) 732 | 733 | self._at_shutdown() 734 | # call sys.exit after a short delay 735 | loop = ioloop.IOLoop.current() 736 | loop.add_timeout(time.time() + 0.1, loop.stop) 737 | 738 | def do_shutdown(self, restart): 739 | """Override in subclasses to do things when the frontend shuts down the 740 | kernel. 741 | """ 742 | return {"status": "ok", "restart": restart} 743 | 744 | @gen.coroutine 745 | def is_complete_request(self, stream, ident, parent): 746 | content = parent["content"] 747 | code = content["code"] 748 | 749 | reply_content = yield gen.maybe_future(self.do_is_complete(code)) 750 | reply_content = json_clean(reply_content) 751 | reply_msg = self.session.send( 752 | stream, "is_complete_reply", reply_content, parent, ident 753 | ) 754 | self.log.debug("%s", reply_msg) 755 | 756 | def do_is_complete(self, code): 757 | """Override in subclasses to find completions. 758 | """ 759 | return { 760 | "status": "unknown", 761 | } 762 | 763 | # --------------------------------------------------------------------------- 764 | # Engine methods (DEPRECATED) 765 | # --------------------------------------------------------------------------- 766 | 767 | def apply_request(self, stream, ident, parent): 768 | self.log.warning( 769 | "apply_request is deprecated in kernel_base, moving to ipyparallel." 770 | ) 771 | try: 772 | content = parent[u"content"] 773 | bufs = parent[u"buffers"] 774 | msg_id = parent["header"]["msg_id"] 775 | except: 776 | self.log.error("Got bad msg: %s", parent, exc_info=True) 777 | return 778 | 779 | md = self.init_metadata(parent) 780 | 781 | reply_content, result_buf = self.do_apply(content, bufs, msg_id, md) 782 | 783 | # flush i/o 784 | sys.stdout.flush() 785 | sys.stderr.flush() 786 | 787 | md = self.finish_metadata(parent, md, reply_content) 788 | 789 | self.session.send( 790 | stream, 791 | u"apply_reply", 792 | reply_content, 793 | parent=parent, 794 | ident=ident, 795 | buffers=result_buf, 796 | metadata=md, 797 | ) 798 | 799 | def do_apply(self, content, bufs, msg_id, reply_metadata): 800 | """DEPRECATED""" 801 | raise NotImplementedError 802 | 803 | # --------------------------------------------------------------------------- 804 | # Control messages (DEPRECATED) 805 | # --------------------------------------------------------------------------- 806 | 807 | def abort_request(self, stream, ident, parent): 808 | """abort a specific msg by id""" 809 | self.log.warning( 810 | "abort_request is deprecated in kernel_base. It is only part of IPython parallel" 811 | ) 812 | msg_ids = parent["content"].get("msg_ids", None) 813 | if isinstance(msg_ids, string_types): 814 | msg_ids = [msg_ids] 815 | if not msg_ids: 816 | self._abort_queues() 817 | for mid in msg_ids: 818 | self.aborted.add(str(mid)) 819 | 820 | content = dict(status="ok") 821 | reply_msg = self.session.send( 822 | stream, "abort_reply", content=content, parent=parent, ident=ident 823 | ) 824 | self.log.debug("%s", reply_msg) 825 | 826 | def clear_request(self, stream, idents, parent): 827 | """Clear our namespace.""" 828 | self.log.warning( 829 | "clear_request is deprecated in kernel_base. It is only part of IPython parallel" 830 | ) 831 | content = self.do_clear() 832 | self.session.send( 833 | stream, "clear_reply", ident=idents, parent=parent, content=content 834 | ) 835 | 836 | def do_clear(self): 837 | """DEPRECATED since 4.0.3""" 838 | raise NotImplementedError 839 | 840 | # --------------------------------------------------------------------------- 841 | # Protected interface 842 | # --------------------------------------------------------------------------- 843 | 844 | def _topic(self, topic): 845 | """prefixed topic for IOPub messages""" 846 | base = "kernel.%s" % self.ident 847 | 848 | return py3compat.cast_bytes("%s.%s" % (base, topic)) 849 | 850 | _aborting = Bool(False) 851 | 852 | @gen.coroutine 853 | def _abort_queues(self): 854 | for stream in self.shell_streams: 855 | stream.flush() 856 | self._aborting = True 857 | 858 | self.schedule_dispatch( 859 | ABORT_PRIORITY, self._dispatch_abort, 860 | ) 861 | 862 | @gen.coroutine 863 | def _dispatch_abort(self): 864 | self.log.info("Finishing abort") 865 | yield gen.sleep(self.stop_on_error_timeout) 866 | self._aborting = False 867 | 868 | def _send_abort_reply(self, stream, msg, idents): 869 | """Send a reply to an aborted request""" 870 | self.log.info("Aborting:") 871 | self.log.info("%s", msg) 872 | reply_type = msg["header"]["msg_type"].rsplit("_", 1)[0] + "_reply" 873 | status = {"status": "aborted"} 874 | md = {"engine": self.ident} 875 | md.update(status) 876 | self.session.send( 877 | stream, reply_type, metadata=md, content=status, parent=msg, ident=idents, 878 | ) 879 | 880 | def _no_raw_input(self): 881 | """Raise StdinNotImplentedError if active frontend doesn't support 882 | stdin.""" 883 | raise StdinNotImplementedError( 884 | "raw_input was called, but this " "frontend does not support stdin." 885 | ) 886 | 887 | def getpass(self, prompt="", stream=None): 888 | """Forward getpass to frontends 889 | 890 | Raises 891 | ------ 892 | StdinNotImplentedError if active frontend doesn't support stdin. 893 | """ 894 | if not self._allow_stdin: 895 | raise StdinNotImplementedError( 896 | "getpass was called, but this frontend does not support input requests." 897 | ) 898 | if stream is not None: 899 | import warnings 900 | 901 | warnings.warn( 902 | "The `stream` parameter of `getpass.getpass` will have no effect when using ipykernel", 903 | UserWarning, 904 | stacklevel=2, 905 | ) 906 | return self._input_request( 907 | prompt, self._parent_ident, self._parent_header, password=True, 908 | ) 909 | 910 | def raw_input(self, prompt=""): 911 | """Forward raw_input to frontends 912 | 913 | Raises 914 | ------ 915 | StdinNotImplentedError if active frontend doesn't support stdin. 916 | """ 917 | if not self._allow_stdin: 918 | raise StdinNotImplementedError( 919 | "raw_input was called, but this frontend does not support input requests." 920 | ) 921 | return self._input_request( 922 | str(prompt), self._parent_ident, self._parent_header, password=False, 923 | ) 924 | 925 | def _input_request(self, prompt, ident, parent, password=False): 926 | # Flush output before making the request. 927 | sys.stderr.flush() 928 | sys.stdout.flush() 929 | # flush the stdin socket, to purge stale replies 930 | while True: 931 | try: 932 | self.stdin_socket.recv_multipart(zmq.NOBLOCK) 933 | except zmq.ZMQError as e: 934 | if e.errno == zmq.EAGAIN: 935 | break 936 | else: 937 | raise 938 | 939 | # Send the input request. 940 | content = json_clean(dict(prompt=prompt, password=password)) 941 | self.session.send( 942 | self.stdin_socket, u"input_request", content, parent, ident=ident 943 | ) 944 | 945 | # Await a response. 946 | while True: 947 | try: 948 | ident, reply = self.session.recv(self.stdin_socket, 0) 949 | except Exception: 950 | self.log.warning("Invalid Message:", exc_info=True) 951 | except KeyboardInterrupt: 952 | # re-raise KeyboardInterrupt, to truncate traceback 953 | raise KeyboardInterrupt("Interrupted by user") from None 954 | else: 955 | break 956 | try: 957 | value = py3compat.unicode_to_str(reply["content"]["value"]) 958 | except: 959 | self.log.error("Bad input_reply: %s", parent) 960 | value = "" 961 | if value == "\x04": 962 | # EOF 963 | raise EOFError 964 | return value 965 | 966 | def _at_shutdown(self): 967 | """Actions taken at shutdown by the kernel, called by python's atexit. 968 | """ 969 | if self._shutdown_message is not None: 970 | self.session.send( 971 | self.iopub_socket, self._shutdown_message, ident=self._topic("shutdown") 972 | ) 973 | self.log.debug("%s", self._shutdown_message) 974 | [s.flush(zmq.POLLOUT) for s in self.shell_streams] 975 | -------------------------------------------------------------------------------- /pick_kernel/kernelspec.py: -------------------------------------------------------------------------------- 1 | """The Pick kernel spec for Jupyter""" 2 | 3 | import errno 4 | import json 5 | import os 6 | import shutil 7 | import sys 8 | import tempfile 9 | 10 | from jupyter_client.kernelspec import KernelSpecManager 11 | from ipykernel import kernelspec as ipyks 12 | 13 | pjoin = os.path.join 14 | 15 | KERNEL_NAME = f"picky-python{sys.version_info[0]}" 16 | 17 | # Yoink resources from the IPython kernel's resources 18 | RESOURCES = pjoin(os.path.dirname(ipyks.__file__), "resources") 19 | 20 | 21 | def make_pick_kernel_cmd( 22 | mod="pick_kernel", executable=None, extra_arguments=None, **kwargs 23 | ): 24 | """Build Popen command list for launching a Picky kernel. 25 | 26 | Parameters 27 | ---------- 28 | mod : str, optional (default 'pick_kernel') 29 | A string of a Pick kernel module whose __main__ starts a Pick kernel 30 | 31 | executable : str, optional (default sys.executable) 32 | The Python executable to use for the kernel process. 33 | 34 | extra_arguments : list, optional 35 | A list of extra arguments to pass when executing the launch code. 36 | The most common use for this is to pass --Application.log_level='INFO' in order to 37 | get logs from the Pick kernel. 38 | 39 | Returns 40 | ------- 41 | 42 | A Popen command list 43 | """ 44 | if executable is None: 45 | executable = sys.executable 46 | extra_arguments = extra_arguments or [] 47 | arguments = [executable, "-m", mod, "-f", "{connection_file}"] 48 | arguments.extend(extra_arguments) 49 | 50 | return arguments 51 | 52 | 53 | def get_kernel_dict(extra_arguments=None): 54 | """Construct dict for kernel.json""" 55 | return { 56 | "argv": make_pick_kernel_cmd(extra_arguments=extra_arguments), 57 | "display_name": f"Picky Python {sys.version_info[0]}", 58 | "language": "python", 59 | } 60 | 61 | 62 | def write_kernel_spec(path=None, overrides=None, extra_arguments=None): 63 | """Write a kernel spec directory to `path` 64 | 65 | - If `path` is not specified, a temporary directory is created. 66 | - If `overrides` is given, the kernelspec JSON is updated before writing. 67 | 68 | Always returns the `path` to the `kernelspec`. 69 | """ 70 | if path is None: 71 | path = os.path.join(tempfile.mkdtemp(suffix="_kernels"), KERNEL_NAME) 72 | 73 | # stage resources 74 | shutil.copytree(RESOURCES, path) 75 | # write kernel.json 76 | kernel_dict = get_kernel_dict(extra_arguments) 77 | 78 | if overrides: 79 | kernel_dict.update(overrides) 80 | with open(pjoin(path, "kernel.json"), "w") as f: 81 | json.dump(kernel_dict, f, indent=1) 82 | 83 | return path 84 | 85 | 86 | def install( 87 | kernel_spec_manager=None, 88 | user=False, 89 | kernel_name=KERNEL_NAME, 90 | display_name=None, 91 | prefix=None, 92 | ): 93 | """Install the Picky kernelspec for Jupyter 94 | 95 | Parameters 96 | ---------- 97 | 98 | kernel_spec_manager: KernelSpecManager [optional] 99 | A KernelSpecManager to use for installation. 100 | If none provided, a default instance will be created. 101 | user: bool [default: False] 102 | Whether to do a user-only install, or system-wide. 103 | kernel_name: str, optional 104 | Specify a name for the kernelspec. 105 | Names are needed to have multiple Picky kernels for different environments. 106 | display_name: str, optional 107 | Specify the display name for the kernelspec 108 | prefix: str, optional 109 | Specify an install prefix for the kernelspec. 110 | This is needed to install into a non-default location, 111 | such as a conda or a virtual environment. 112 | 113 | Returns 114 | ------- 115 | 116 | The path where the kernelspec was installed. 117 | """ 118 | if kernel_spec_manager is None: 119 | kernel_spec_manager = KernelSpecManager() 120 | 121 | if (kernel_name != KERNEL_NAME) and (display_name is None): 122 | # kernel_name is specified and display_name is not 123 | # default display_name to kernel_name 124 | display_name = kernel_name 125 | overrides = {} 126 | if display_name: 127 | overrides["display_name"] = display_name 128 | else: 129 | extra_arguments = None 130 | path = write_kernel_spec(overrides=overrides, extra_arguments=extra_arguments) 131 | dest = kernel_spec_manager.install_kernel_spec( 132 | path, kernel_name=kernel_name, user=user, prefix=prefix 133 | ) 134 | # cleanup the temporary path afterward 135 | shutil.rmtree(path) 136 | return dest 137 | 138 | 139 | # Entrypoint 140 | 141 | from traitlets.config import Application 142 | 143 | 144 | class InstallPickyKernelSpecApp(Application): 145 | """Dummy app wrapping argparse""" 146 | 147 | name = "picky-kernel-install" 148 | 149 | def initialize(self, argv=None): 150 | if argv is None: 151 | argv = sys.argv[1:] 152 | self.argv = argv 153 | 154 | def start(self): 155 | import argparse 156 | 157 | parser = argparse.ArgumentParser( 158 | prog=self.name, description="Install the Picky kernel spec." 159 | ) 160 | parser.add_argument( 161 | "--user", 162 | action="store_true", 163 | help="Install for the current user instead of system-wide", 164 | ) 165 | parser.add_argument( 166 | "--name", 167 | type=str, 168 | default=KERNEL_NAME, 169 | help="Specify a name for the kernelspec." 170 | " This is needed to have multiple Picky kernels at the same time.", 171 | ) 172 | parser.add_argument( 173 | "--display-name", 174 | type=str, 175 | help="Specify the display name for the kernelspec." 176 | " This is helpful when you have multiple Picky kernels.", 177 | ) 178 | parser.add_argument( 179 | "--prefix", 180 | type=str, 181 | help="Specify an install prefix for the kernelspec." 182 | " This is needed to install into a non-default location, such as a conda/virtual-env.", 183 | ) 184 | parser.add_argument( 185 | "--sys-prefix", 186 | action="store_const", 187 | const=sys.prefix, 188 | dest="prefix", 189 | help=f"Install to Python's sys.prefix." 190 | f" Shorthand for --prefix='{sys.prefix}'. For use in conda/virtual-envs." 191 | ) 192 | opts = parser.parse_args(self.argv) 193 | try: 194 | dest = install( 195 | user=opts.user, 196 | kernel_name=opts.name, 197 | prefix=opts.prefix, 198 | display_name=opts.display_name, 199 | ) 200 | except OSError as e: 201 | if e.errno == errno.EACCES: 202 | print(e, file=sys.stderr) 203 | if opts.user: 204 | print("Perhaps you want `sudo` or `--user`?", file=sys.stderr) 205 | self.exit(1) 206 | raise 207 | print(f"Installed kernelspec {opts.name} in {dest}") 208 | 209 | 210 | main = InstallPickyKernelSpecApp.launch_instance 211 | 212 | if __name__ == "__main__": 213 | InstallPickyKernelSpecApp.launch_instance() 214 | -------------------------------------------------------------------------------- /pick_kernel/subkernels.py: -------------------------------------------------------------------------------- 1 | from .exceptions import PickRegistrationException 2 | 3 | from jupyter_client import AsyncKernelManager 4 | 5 | import entrypoints 6 | 7 | 8 | class Subkernels(object): 9 | def __init__(self): 10 | self._subkernels = {} 11 | 12 | def register(self, name, subkernel): 13 | self._subkernels[name] = subkernel 14 | 15 | def register_entry_points(self): 16 | """Register entrypoints for a subkernel 17 | Load handlers provided by other packages 18 | """ 19 | for entrypoint in entrypoints.get_group_all("pick_kernel.subkernel"): 20 | self.register(entrypoint.name, entrypoint.load()) 21 | 22 | def get_subkernel(self, name=None): 23 | """Retrieves an engine by name.""" 24 | subkernel = self._subkernels.get(name) 25 | if not subkernel: 26 | raise PickRegistrationException(f"No subkernel named {name} found") 27 | return subkernel 28 | 29 | def list_subkernels(self): 30 | """Returns a list of available subkernels""" 31 | return [ 32 | f"%%kernel.{name}" 33 | for name in filter(lambda x: x is not None, self._subkernels.keys()) 34 | if not getattr(self._subkernels[name], "hidden", False) 35 | ] 36 | 37 | def launch_subkernel(self, name=None): 38 | return self.get_subkernel(name).launch() 39 | 40 | 41 | class Subkernel(object): 42 | """ 43 | Base class for subkernels. 44 | 45 | Other specific subkernels should inherit and implement the `launch` method. 46 | """ 47 | 48 | @staticmethod 49 | async def launch(config, session, context, connection_file): 50 | """This must return an AsyncKernelManager() that has 51 | already had start_kernel called on it""" 52 | raise NotImplementedError("launch must be implemented") 53 | 54 | 55 | class DefaultKernel(Subkernel): 56 | hidden = True 57 | 58 | @staticmethod 59 | async def launch(config, session, context, connection_file): 60 | args = [] 61 | if config: 62 | # configuration passed to the default kernel passes options to ipykernel 63 | args = list(filter(lambda x: x != "", config.split("\n"))) 64 | 65 | km = AsyncKernelManager( 66 | kernel_name="python3", 67 | # Pass our IPython session as the session for the KernelManager 68 | session=session, 69 | # Use the same ZeroMQ context that allows for awaiting on recv 70 | context=context, 71 | connection_file=connection_file, 72 | ) 73 | 74 | # Tack on additional arguments to the underlying kernel 75 | await km.start_kernel(extra_arguments=args) 76 | 77 | return km 78 | 79 | 80 | # Instantiate a SubKernels instance, register Handlers and entrypoints 81 | _subkernels = Subkernels() 82 | _subkernels.register(None, DefaultKernel) 83 | _subkernels.register("ipykernel", DefaultKernel) 84 | _subkernels.register_entry_points() 85 | 86 | # Expose registration at a top level 87 | register = _subkernels.register 88 | list_subkernels = _subkernels.list_subkernels 89 | get_subkernel = _subkernels.get_subkernel 90 | -------------------------------------------------------------------------------- /pick_kernel/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nteract/pick/657059a1ba79a09a5cee07a75a3f02fd055456e3/pick_kernel/tests/__init__.py -------------------------------------------------------------------------------- /pick_kernel/tests/test_subkernel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | 6 | from mock import Mock, patch 7 | 8 | from .. import subkernels 9 | from .. import exceptions 10 | 11 | 12 | class TestSubkernelRegistration(unittest.TestCase): 13 | def setUp(self): 14 | self.subkernels = subkernels.Subkernels() 15 | 16 | def test_registration(self): 17 | mock_subkernel = Mock() 18 | self.subkernels.register("mock_subkernel", mock_subkernel) 19 | self.assertIn("mock_subkernel", self.subkernels._subkernels) 20 | self.assertIs(mock_subkernel, self.subkernels._subkernels["mock_subkernel"]) 21 | 22 | def test_getting(self): 23 | mock_subkernel = Mock() 24 | self.subkernels.register("mock_subkernel", mock_subkernel) 25 | retrieved_subkernel = self.subkernels.get_subkernel("mock_subkernel") 26 | self.assertIs(mock_subkernel, retrieved_subkernel) 27 | 28 | self.assertRaises( 29 | exceptions.PickRegistrationException, 30 | self.subkernels.get_subkernel, 31 | "non-existent", 32 | ) 33 | 34 | def test_registering_entry_points(self): 35 | fake_entrypoint = Mock(load=Mock()) 36 | fake_entrypoint.name = "fake-subkernel" 37 | 38 | with patch( 39 | "entrypoints.get_group_all", return_value=[fake_entrypoint] 40 | ) as mock_get_group_all: 41 | 42 | self.subkernels.register_entry_points() 43 | mock_get_group_all.assert_called_once_with("pick_kernel.subkernel") 44 | self.assertEqual( 45 | self.subkernels.get_subkernel("fake-subkernel"), 46 | fake_entrypoint.load.return_value, 47 | ) 48 | 49 | def test_listing(self): 50 | mock_subkernel = Mock(spec=["hidden"]) 51 | 52 | mock_subkernel.hidden = False 53 | self.subkernels.register("mock_subkernel", mock_subkernel) 54 | retrieved_subkernel = self.subkernels.get_subkernel("mock_subkernel") 55 | self.assertIs(mock_subkernel, retrieved_subkernel) 56 | 57 | hidden_mock_subkernel = Mock(spec=["hidden"]) 58 | hidden_mock_subkernel.hidden = True 59 | self.subkernels.register("hidden_mock_subkernel", hidden_mock_subkernel) 60 | retrieved_subkernel = self.subkernels.get_subkernel("hidden_mock_subkernel") 61 | self.assertIs(hidden_mock_subkernel, retrieved_subkernel) 62 | 63 | self.assertEqual(self.subkernels.list_subkernels(), ["%%kernel.mock_subkernel"]) 64 | -------------------------------------------------------------------------------- /pick_kernel/version.py: -------------------------------------------------------------------------------- 1 | version = "0.0.4" 2 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | bump2version 2 | papermill 3 | pytest 4 | pytest-asyncio 5 | tox 6 | mock 7 | click 8 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ipykernel>=5.2.1 2 | jupyter_client>=6.1.3 3 | tornado>=4.2 4 | pyzmq>=19.0.0 5 | entrypoints 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | import setuptools 3 | 4 | local_path = os.path.dirname(__file__) 5 | # Fix for tox which manipulates execution pathing 6 | if not local_path: 7 | local_path = "." 8 | here = os.path.abspath(local_path) 9 | 10 | 11 | def version(): 12 | with open(here + "/pick_kernel/version.py", "r") as ver: 13 | for line in ver.readlines(): 14 | if line.startswith("version ="): 15 | return line.split(" = ")[-1].strip()[1:-1] 16 | raise ValueError("No version found in pick_kernel/version.py") 17 | 18 | 19 | # Get the long description from the README file 20 | with open(os.path.join(here, "README.md"), encoding="utf-8") as f: 21 | long_description = f.read() 22 | 23 | 24 | def read(fname): 25 | with open(fname, "r") as fhandle: 26 | return fhandle.read() 27 | 28 | 29 | def read_reqs(fname): 30 | req_path = os.path.join(here, fname) 31 | return [req.strip() for req in read(req_path).splitlines() if req.strip()] 32 | 33 | 34 | # Borrowing from the pattern used by papermill for requirements 35 | all_reqs = [] 36 | dev_reqs = read_reqs("requirements-dev.txt") + all_reqs 37 | extras_require = { 38 | "test": dev_reqs, 39 | "dev": dev_reqs, 40 | "all": all_reqs, 41 | } 42 | 43 | 44 | setuptools.setup( 45 | name="pick_kernel", 46 | version=version(), 47 | author="Kyle Kelley", 48 | author_email="rgbkrk@gmail.com", 49 | description="The Jupyter Kernel for Choosy Users", 50 | long_description=long_description, 51 | long_description_content_type="text/markdown", 52 | url="https://github.com/rgbkrk/pick", 53 | packages=setuptools.find_packages(), 54 | entry_points={ 55 | "console_scripts": [ 56 | "pick=pick_kernel:main", 57 | "pick-install=pick_kernel.kernelspec:main", 58 | ] 59 | }, 60 | python_requires=">=3.6", 61 | install_requires=read_reqs("requirements.txt"), 62 | extras_require=extras_require, 63 | classifiers=[ 64 | "Intended Audience :: Developers", 65 | "Intended Audience :: System Administrators", 66 | "Intended Audience :: Science/Research", 67 | "License :: OSI Approved :: BSD License", 68 | "Programming Language :: Python", 69 | "Programming Language :: Python :: 3", 70 | "Programming Language :: Python :: 3.6", 71 | "Programming Language :: Python :: 3.7", 72 | "Programming Language :: Python :: 3.8", 73 | ], 74 | ) 75 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | skipsdist = true 3 | envlist = py{37,38} 4 | 5 | [testenv] 6 | # disable Python's hash randomization for tests that stringify dicts, etc 7 | setenv = 8 | PYTHONHASHSEED = 0 9 | deps = .[dev] 10 | commands = 11 | pytest 12 | --------------------------------------------------------------------------------