├── .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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------