emacs-jupyter/README.org

1316 lines
57 KiB
Org Mode
Raw Normal View History

2018-05-13 11:41:28 -05:00
An interface to communicate with Jupyter kernels in Emacs.
2018-01-06 18:04:06 -06:00
2019-02-09 14:35:00 -06:00
[[https://melpa.org/#/jupyter][https://melpa.org/packages/jupyter-badge.svg]] [[https://travis-ci.com/dzop/emacs-jupyter][https://travis-ci.com/dzop/emacs-jupyter.svg?branch=master]]
2018-11-17 12:21:57 -06:00
2019-03-28 14:28:03 -05:00
* Table of Contents :TOC:
- [[#what-does-this-package-do][What does this package do?]]
- [[#api-for-creating-jupyter-frontends-in-emacs][API for creating Jupyter frontends in Emacs]]
- [[#support-differences-between-kernel-languages][Support differences between kernel languages]]
- [[#how-do-i-install-this-package][How do I install this package?]]
- [[#using-melpa][Using MELPA]]
- [[#building-a-package-archive-using-cask][Building a package archive using cask]]
- [[#manual-installation][Manual installation]]
- [[#building-the-widget-support-experimental][Building the widget support (EXPERIMENTAL)]]
- [[#how-does-this-package-compare-to-other-similar-packages][How does this package compare to other similar packages?]]
- [[#how-do-i-use-the-built-in-frontends][How do I use the built-in frontends?]]
- [[#repl][REPL]]
- [[#org-mode-source-blocks][=org-mode= source blocks]]
- [[#api][API]]
- [[#naming-conventions][Naming conventions]]
- [[#overview][Overview]]
- [[#jupyter-kernel-client][=jupyter-kernel-client=]]
- [[#jupyter-kernel-manager][=jupyter-kernel-manager=]]
- [[#jupyter-widget-client][=jupyter-widget-client=]]
- [[#jupyter-repl-client][=jupyter-repl-client=]]
- [[#jupyter-ioloop][=jupyter-ioloop=]]
- [[#jupyter-channel-ioloop][=jupyter-channel-ioloop=]]
- [[#callbacks-and-hooks][Callbacks and hooks]]
- [[#waiting-for-messages][Waiting for messages]]
- [[#message-property-lists][Message property lists]]
- [[#modify-behavior-depending-on-kernel-language][Modify behavior depending on kernel language]]
- [[#org-mode][=org-mode=]]
2018-11-23 21:19:45 -06:00
* What does this package do?
2019-03-28 14:28:03 -05:00
** API for creating Jupyter frontends in Emacs
2018-11-23 21:19:45 -06:00
This package provides an API for communicating with a Jupyter kernel via =zmq=
sockets (http://github.com/dzop/emacs-zmq). It utilizes Emacs' object
implementation, =eieio=, to define Jupyter client and kernel manager classes
2019-01-16 16:59:19 -06:00
that can be sub-classed to provide support for any kind of Jupyter frontend in
Emacs. Currently, there is a built-in REPL frontend and =org-mode= source block
frontend.
2018-11-23 21:19:45 -06:00
2019-01-16 16:59:19 -06:00
The base Jupyter client provides default implementations for handling common
message replies from a kernel that integrate well with Emacs' built-in
features. Below is a partial list of how kernel messages are integrated with
Emacs:
- Sending an inspect request will display the inspect reply in the =*Help*=
buffer and previous inspect requests can be revisited by calling
=help-go-back= or =help-go-forward= while in the =*Help*= buffer.
- Making completion requests to a kernel is done through the
=completion-at-point= interface.
- If the kernel asks for input from the user, a prompt is displayed in the
minibuffer.
- Search through REPL history using =isearch=.
2019-01-12 20:56:48 -06:00
** Support differences between kernel languages
2018-11-23 21:19:45 -06:00
2019-02-09 14:35:00 -06:00
There are many methods that can be extended to take into account differences
between kernel languages. This is achieved by providing a method specializer,
=jupyter-lang=, that can be added to the =&context= section of a method
definition. See =cl-generic-generalizers=.
2018-11-23 21:19:45 -06:00
For example, the Python kernel sends =text/plain= data in its inspect replies,
but most of the time the documentation requested is written using
reStructuredText (rST) markup, the officially supported markup language for
documentation strings in Python. In =emacs-jupyter= all insertion of messages
into a buffer is handled by the =jupyter-insert= method. This method is
extended to properly highlight rST when an inspect reply message is being
inserted and the message is from a Python kernel.
See the files =jupyter-python.el= and =jupyter-julia.el= for how these
languages are integrated into the =emacs-jupyter= framework. Note, you don't
need to require these support files directly since they will be loaded
automatically when needed.
2018-11-23 21:19:45 -06:00
* How do I install this package?
2019-03-28 14:28:03 -05:00
** Using MELPA
*NOTE:* Your Emacs needs to have been built with module support for this
package to work since it relies on the =emacs-zmq= package. See the README of
that package for more information.
2019-02-09 14:35:00 -06:00
The recommended way to install this package is through the built-in package
manager in Emacs.
Ensure MELPA is in your =package-archives=
#+BEGIN_SRC elisp
(add-to-list 'package-archives '("melpa" . "https://melpa.org/packages/"))
#+END_SRC
Ensure the latest versions of MELPA packages are available
=M-x package-refresh-contents RET=
Install Jupyter
=M-x package-install RET jupyter RET=
** Building a package archive using cask
One way to install this package is to build a package archive using =cask=
2019-01-16 16:59:19 -06:00
(https://github.com/cask/cask) to build a local Emacs package file. To do this,
clone the repository, enter its directory, and run the following at the command
line:
2018-05-13 11:41:28 -05:00
2018-11-23 21:19:45 -06:00
#+BEGIN_SRC shell
cask package
2018-05-13 11:41:28 -05:00
#+END_SRC
2018-01-06 18:04:06 -06:00
2018-11-23 21:19:45 -06:00
This creates a file =dist/jupyter-0.6.0.tar= containing the package archive. To
install it
2018-11-15 17:22:20 -06:00
2018-11-23 21:19:45 -06:00
1. Start your Emacs normally
2. Ensure MELPA is in your =package-archives=
3. =M-x package-initialize=
4. =M-x package-refresh-contents=
5. =M-x package-install-file ~/path/to/jupyter/dist/jupyter-0.6.0.tar=
** Manual installation
For a manual installation you can add the repository directory to your
=load-path= and ensure the following dependencies are installed:
2018-01-18 23:10:51 -06:00
2018-05-13 11:41:28 -05:00
- markdown-mode (optional) :: https://jblevins.org/projects/markdown-mode/
- company-mode (optional) :: http://company-mode.github.io/
2018-11-15 17:22:20 -06:00
- emacs-websocket :: https://github.com/ahyatt/emacs-websocket
- simple-httpd :: https://github.com/skeeto/emacs-web-server
- zmq :: http://github.com/dzop/emacs-zmq
2018-11-23 21:19:45 -06:00
#+BEGIN_SRC elisp
(add-to-list 'load-path "~/path/to/jupyter")
(require 'jupyter)
#+END_SRC
** Building the widget support (EXPERIMENTAL)
2018-10-27 22:14:59 -05:00
:PROPERTIES:
:ID: 59559FA3-59AD-453F-93E7-113B43F85493
:END:
There is also support for interacting with Jupyter widgets through an external
browser. If a widget is to be displayed, an external browser is opened first to
display the widget. In this case, Emacs acts as a relay for passing messages
between the kernel and the external browser.
If you would like to try out this limited support, you will need to have =node=
installed on your system to build the necessary javascript. Then you will have
to run the following commands from the root project directory:
#+BEGIN_SRC shell
2018-11-23 21:19:45 -06:00
make widgets
2018-11-15 17:22:20 -06:00
#+END_SRC
2018-11-23 21:19:45 -06:00
* How does this package compare to other similar packages?
2018-11-15 17:22:20 -06:00
2018-11-29 02:37:23 -06:00
There are two popular packages that implement similar functionality to this one
2018-11-15 17:22:20 -06:00
2018-11-23 21:19:45 -06:00
- ob-ipython :: https://github.com/gregsexton/ob-ipython
- Interacts with a Jupyter kernel via =org-mode= source blocks.
2019-01-16 16:59:19 -06:00
- ob-ipython (scimax) :: https://github.com/jkitchin/scimax
- Scimax is an Emacs starterkit for scientists and engineers that extends
=ob-ipython=.
2018-11-23 21:19:45 -06:00
- emacs-ipython-notebook (ein) :: https://github.com/millejoh/emacs-ipython-notebook
- A Jupyter notebook interface in Emacs.
=emacs-jupyter= extends the features of =ob-ipython= by integrating more with
=org-mode= and providing a better REPL interface to the kernel. For example,
=ob-ipython= currently does not provide a function for
=org-babel-load-in-session=. =ob-ipython= also starts a new process for every
2018-11-29 02:37:23 -06:00
request since it relies on calling a Python script to send and receive messages
whereas =emacs-jupyter= directly uses =zmq= sockets via =emacs-zmq= for
communication and only starts a process on every new client connection. This
difference in how messages are passed between Emacs and a kernel is notable
when making completion requests. =ob-ipython= will incur the overhead of
starting up a new process /and/ new sockets on every completion request which
can potentially be every keystroke if you type slow enough.
2018-11-23 21:19:45 -06:00
2019-01-16 16:59:19 -06:00
The =scimax= version of =ob-ipython= offers some useful extensions to the
default =ob-ipython= experience such as custom keybindings when inside an
=org-mode= source block, selective display of mimetypes, and jumping to source
block error locations. Many of these features have also been implemented in
=emacs-jupyter=.
2018-11-23 21:19:45 -06:00
=ein= is more of a full featured solution for a Jupyter notebook interface in
2018-11-29 02:37:23 -06:00
Emacs. The goals of =emacs-jupyter= and =ein= are different. =ein= aims to be a
frontend to the Jupyter notebook server API
2018-11-23 21:19:45 -06:00
(https://github.com/jupyter/jupyter/wiki/Jupyter-Notebook-Server-API) which is
an extra layer between the user and a kernel
(https://jupyter.readthedocs.io/en/latest/architecture/how_jupyter_ipython_work.html#notebooks).
2018-11-29 02:37:23 -06:00
In addition to being notebook client, =ein= offers many more powerful features
for Python kernels. =emacs-jupyter=, on the other hand, offers an API that
implements the Jupyter messaging protocol for communication with a kernel via
=zmq= sockets. The API tries to integrate the interaction between the user and
a kernel with built-in Emacs features. The REPL support and =org-mode=
integration are examples of how the API can be used. In the future, it would be
nice to add some kind of notebook interface in =emacs-jupyter= or at least an
efficient conversion process between notebook files and =org-mode=.
2019-03-28 14:28:03 -05:00
* How do I use the built-in frontends?
** REPL
2018-05-13 11:41:28 -05:00
2019-03-28 14:28:03 -05:00
To start a new kernel on the =localhost= and connect a REPL client to it
=M-x jupyter-run-repl=. Alternatively you can connect to an existing
kernel by supplying the kernel's connection file using
=M-x jupyter-connect-repl=.
2018-05-13 11:41:28 -05:00
The REPL supports most of the rich output that a kernel may send to a client.
2018-05-30 12:30:18 -05:00
If the kernel requests a widget to be displayed, a browser is opened that
displays the widget. If the kernel sends image data, the image will be
displayed in the REPL buffer. If LaTeX is sent, it will be compiled (using
2019-01-16 16:59:19 -06:00
=org-mode=) and displayed.
2018-05-30 12:30:18 -05:00
2019-03-28 14:28:03 -05:00
*** Rich kernel output
2018-01-18 23:10:51 -06:00
2018-05-30 12:30:18 -05:00
A Jupyter kernel provides many representations of results that may be used by
the frontend, in this case Emacs. Luckily, Emacs provides
good support for most of the available representations.
The supported mimetypes along with their dependencies are shown below in order
of priority if multiple representations are returned. Note, if a dependency is
not available in your Emacs, a mimetype with a lower priority will be used to
display output.
| Mimetype | Dependency |
|--------------------------------------------+---------------------------|
| =application/vnd.jupyter.widget-view+json= | [[https://github.com/ahyatt/emacs-websocket][websocket]], [[https://github.com/skeeto/emacs-web-server][simple-httpd]] |
| =text/html= | Emacs built with libxml2 |
| =text/markdown= | [[https://jblevins.org/projects/markdown-mode/][markdown-mode]] |
| =text/latex= | [[https://orgmode.org/][org-mode]] |
| =image/svg+xml= | Emacs built with librsvg2 |
| =image/png= | none |
| =text/plain= | none |
2019-03-28 14:28:03 -05:00
*** Inspection
2018-01-18 23:10:51 -06:00
2019-01-12 20:56:48 -06:00
To send an inspect request to the kernel, press =M-i= when the cursor is at the
location of the code you would like to inspect.
2019-03-28 14:28:03 -05:00
*** Completion
2018-05-13 11:41:28 -05:00
2018-05-30 12:30:18 -05:00
Completion is implemented through the =completion-at-point= interface. In
addition to completing symbols in the REPL buffer, completion also works in
buffers [[id:DA597E05-E9A9-4DCE-BBD7-6D25238638C5][associated]] with a REPL. For =org-mode= users, there is even completion
in the =org-mode= buffer when editing the contents of a Jupyter source code
block.
2019-03-28 14:28:03 -05:00
*** REPL history
2018-01-18 23:10:51 -06:00
2018-05-30 12:30:18 -05:00
You can navigate through the REPL history using =C-n= and =C-p= or =M-n= and
=M-p=.
You can also search through the history using =isearch=. To search through
history, use the standard =isearch= keybindings: =C-s= to search forward
through history and =C-s C-r= to search backward.
2019-03-28 14:28:03 -05:00
*** Associating other buffers with a REPL
2018-05-30 12:30:18 -05:00
:PROPERTIES:
:ID: DA597E05-E9A9-4DCE-BBD7-6D25238638C5
:END:
2018-01-18 23:10:51 -06:00
2018-05-13 11:41:28 -05:00
After starting a REPL, it is possible to associate the REPL with other buffers
if they pass certain criteria. Currently, the buffer must have the =major-mode=
that corresponds to the REPL's kernel language. To associate a buffer with a
REPL you can run the command =jupyter-repl-associate-buffer=.
2018-01-18 23:10:51 -06:00
2018-05-13 11:41:28 -05:00
=jupyter-repl-associate-buffer= will ask you for the REPL you would like to
2018-01-18 23:10:51 -06:00
associate with the =current-buffer= and enable the minor mode
=jupyter-repl-interaction-mode=. This minor mode populates the following
keybindings for interacting with the REPL:
2018-05-13 11:41:28 -05:00
| Key binding | Command |
|-------------+------------------------------------|
| =C-M-x= | =jupyter-eval-defun= |
2018-11-15 17:22:20 -06:00
| =M-i= | =jupyter-inspect-at-point= |
| =C-c C-b= | =jupyter-eval-buffer= |
| =C-c C-c= | =jupyter-eval-line-or-region= |
2018-05-13 11:41:28 -05:00
| =C-c C-i= | =jupyter-repl-interrupt-kernel= |
| =C-c C-r= | =jupyter-repl-restart-kernel= |
2018-11-15 17:22:20 -06:00
| =C-c C-s= | =jupyter-repl-scratch-buffer= |
| =C-c M-:= | =jupyter-eval-string= |
2019-03-28 14:28:03 -05:00
*** =jupyter-repl-persistent-mode=
2018-12-09 23:16:05 -06:00
2019-01-16 16:59:19 -06:00
A global minor mode that will persist a kernel connection to a buffer about to
be displayed if the current buffer is in =jupyter-repl-interaction-mode= and
the buffer being switched to has the same =major-mode=. This mode is
automatically enabled whenever =jupyter-run-repl= or =jupyter-connect-repl= is
called.
2019-03-28 14:28:03 -05:00
*** =jupyter-repl-maximum-size=
2019-01-16 16:59:19 -06:00
Set the maximum number of lines before the REPL buffer is truncated.
2019-05-04 03:25:08 -05:00
*** =jupyter-repl-allow-RET-when-busy=
If non-nil, allow inserting a newline in a REPL cell whenever the kernel is
busy. Normally this isn't allowed since the REPL relies on the kernel
responding to messages when =RET= is pressed, but a kernel does not respond to
messages when it is busy.
2019-03-28 14:28:03 -05:00
*** Widget support
2018-05-30 12:30:18 -05:00
There is also support for Jupyter widgets integrated into the REPL. If any of
the results returned by a kernel have a widget representation, a browser is
opened and the widget is displayed in the browser. There is only one browser
per client.
This feature is currently considered experimental and has only been tested for
2018-10-27 22:14:59 -05:00
simple uses of widgets. See [[id:B15FF43B-114C-4D73-B69C-2095F108EBBB][=jupyter-widget-client=]].
2019-03-28 14:28:03 -05:00
** =org-mode= source blocks
2018-05-13 11:41:28 -05:00
For users of =org-mode=, integration with =org-babel= is provided through the
2018-11-23 21:19:45 -06:00
=ob-jupyter= library. To enable Jupyter support for source code blocks, add
2018-05-30 12:30:18 -05:00
=jupyter= to =org-babel-load-languages=.
#+BEGIN_SRC elisp
(org-babel-do-load-languages
'org-babel-load-languages
'((emacs-lisp . t)
(julia . t)
(python . t)
(jupyter . t)))
2018-05-30 12:30:18 -05:00
#+END_SRC
Note, =jupyter= should be added as the last element when loading languages
since it depends on the values of variables such as =org-src-lang-modes= and
=org-babel-tangle-lang-exts=. After =ob-jupyter= has been loaded, new source
code blocks with names of the form =jupyter-LANG= will be available. =LANG= can be
any one of the kernel languages found on your system. See
=jupyter-available-kernelspecs=.
2018-05-16 22:20:26 -05:00
Every Jupyter source code block requires that the =:session= parameter be
2018-05-30 12:30:18 -05:00
specified since all interaction with a kernel is through a REPL. For example,
to interact with a =python= kernel you would create a new source block like so
2018-05-13 11:41:28 -05:00
2018-05-16 22:01:30 -05:00
#+BEGIN_SRC org
,#+BEGIN_SRC jupyter-python :session py
2018-05-16 22:02:31 -05:00
x = 'foo'
y = 'bar'
x + ' ' + y
2018-05-16 22:01:30 -05:00
,#+END_SRC
2018-05-13 11:41:28 -05:00
#+END_SRC
2018-01-18 23:10:51 -06:00
2018-05-16 22:20:26 -05:00
By default, source blocks are executed synchronously. To execute a source block
2018-05-30 12:30:18 -05:00
asynchronously set the =:async= parameter to =yes=:
2018-01-06 18:04:06 -06:00
2018-05-16 22:01:30 -05:00
#+BEGIN_SRC org
,#+BEGIN_SRC jupyter-python :session py :async yes
2018-05-16 22:02:31 -05:00
x = 'foo'
y = 'bar'
x + ' ' + y
2018-05-16 22:01:30 -05:00
,#+END_SRC
2018-05-13 11:41:28 -05:00
#+END_SRC
2018-01-06 18:04:06 -06:00
2018-05-13 11:41:28 -05:00
Since a particular language may have multiple kernels available, the default
2018-11-29 02:37:23 -06:00
kernel used will be the first one found by =jupyter-available-kernelspecs= for
the language. To change the kernel, set the =:kernel= parameter:
2018-01-06 18:04:06 -06:00
2018-05-16 22:01:30 -05:00
#+BEGIN_SRC org
,#+BEGIN_SRC jupyter-python :session py :async yes :kernel python2
2018-05-16 22:02:31 -05:00
x = 'foo'
y = 'bar'
x + ' ' + y
2018-05-16 22:01:30 -05:00
,#+END_SRC
2018-05-13 11:41:28 -05:00
#+END_SRC
2018-01-06 18:04:06 -06:00
2018-05-30 12:30:18 -05:00
Note, the same session name can be used for different values of =:kernel= since
2018-11-29 02:37:23 -06:00
the underlying REPL buffer's name is based on both =:session= and =:kernel=.
2018-05-30 12:30:18 -05:00
2018-11-29 02:37:23 -06:00
Any of the defaults for a language can be changed by setting
=org-babel-default-header-args:jupyter-LANG= to an appropriate value. For example
2018-11-29 02:37:23 -06:00
to change the defaults for the =julia= kernel, you can set
=org-babel-default-header-args:jupyter-julia= to something like
2018-01-06 18:04:06 -06:00
#+BEGIN_SRC elisp
(setq org-babel-default-header-args:jupyter-julia '((:async . "yes")
(:session . "jl")
(:kernel . "julia-1.0")))
2018-05-13 11:41:28 -05:00
#+END_SRC
2019-03-28 14:28:03 -05:00
*** Integration with =ob-async=
2019-03-13 18:24:37 -05:00
If you use the =ob-async= package, make sure you add the Jupyter source block
languages to [[https://github.com/astahlman/ob-async#ob-async-no-async-languages-alist][ob-async-no-async-languages-alist]] so that =ob-async= doesn't
override =emacs-jupyter= when the =:async= header argument is specified. For
example you can put the following in your configuration:
#+BEGIN_SRC elisp
(setq ob-async-no-async-languages-alist '("jupyter-python" "jupyter-julia"))
#+END_SRC
2019-03-28 14:28:03 -05:00
*** Overriding built-in src-block languages
You may find having to specify the names of Jupyter source blocks using
=jupyter-LANG= a bit verbose and want to have the built-in support for =LANG=
source blocks overridden to use the machinery of =jupyter-LANG= source blocks.
This can be done by calling the function
=org-babel-jupyter-override-src-block=.
For example, to override the behavior of =python= source blocks so that they
act like =jupyter-python= source blocks, you can add the following in your
initialization (after calling =org-babel-do-load-languages=):
#+BEGIN_SRC elisp
(org-babel-jupyter-override-src-block "python")
#+END_SRC
After calling the above function, all =python= source blocks are effectively
aliases of =jupyter-python= source blocks and the variable
=org-babel-default-header-args:python= will be set to the value of
=org-babel-default-header-args:jupyter-python=. Note,
=org-babel-default-header-args:python= will *not* be an alias of
=org-babel-default-header-args:jupyter-python=, the value of the former is
merely set to the value of the latter after calling
=org-babel-jupyter-override-src-block=.
If you decide you want to go back to the original behavior or =python= source
blocks, you can restore the overridden functions by calling
=org-babel-jupyter-restore-src-block=.
#+BEGIN_SRC elisp
(org-babel-jupyter-restore-src-block "python")
#+END_SRC
2019-03-28 14:28:03 -05:00
*** Rich kernel output
2018-05-13 11:41:28 -05:00
2018-05-30 12:30:18 -05:00
In =org-mode= a code block returns scalar data (plain text, numbers, lists,
tables, \dots), an image file name, or code from another language. All of this
information must be specified in the code block's header arguments, but all of
this information is already provided in the messages passed between a Jupyter
kernel and its frontends.
When a kernel provides representations of results other than plain text, those
2019-01-13 17:25:29 -06:00
richer representations have priority. For example if the kernel returns LaTeX
code, the results are wrapped in a LaTeX source block. Similarly for HTML and
markdown. If an image is returned, the image is automatically saved to file and
a link to the file will be the result of the code block.
2018-05-30 12:30:18 -05:00
Below are the supported mimetypes ordered by priority
- text/org
2018-11-29 02:37:23 -06:00
- image/svg+xml, image/jpeg, image/png
2018-05-30 12:30:18 -05:00
- text/html
- text/markdown
- text/latex
- text/plain
2019-01-13 17:25:29 -06:00
Since it is possible to determine how a result should be represented in
=org-mode= via its MIME type, only a few header arguments are supported.
2019-03-28 14:28:03 -05:00
**** A note on using the =:results= header argument
Results are inserted in the =org-mode= buffer in such a way that most header
arguments that control how results should be inserted don't need to specified.
There are some cases where this behavior is not wanted and which can be
controlled by setting the =:results= header argument.
- Insert unwrapped LaTeX :: Normally LaTeX results are wrapped in a
=BEGIN_EXPORT= block, in order to insert LaTeX unwrapped, specify
=:results raw=.
- Suppress table creation :: Whenever a result can be converted into an
=org-mode= table, e.g. when it look like =[1, 2 , 3]=, it is automatically
converted into a table. To suppress this behavior you can specify
=:results scalar=.
2019-03-28 14:28:03 -05:00
**** Fixing the file name of images with the =:file= argument
Whenever an image result is returned, a random image file name is generated and
the image is written into =org-babel-jupyter-resourse-directory=. In order to
specify your own file name for the image, you can give an appropriate value to
the =:file= header argument.
2019-01-13 17:25:29 -06:00
2019-03-28 14:28:03 -05:00
**** Changing the mime-type priority with the =:display= argument
The priority of mimetypes used to display results can be overwritten using the
=:display= option. If instead of displaying HTML results we'd wish to display
plain text, the argument =:display text/plain text/html= would prioritize plain
text results over html ones. The following example displays plain text instead
of HTML:
#+BEGIN_SRC org
,#+BEGIN_SRC jupyter-python :session py :display plain
import pandas as pd
data = [[1, 2], [3, 4]]
pd.DataFrame(data, columns=["Foo", "Bar"])
,#+END_SRC
#+END_SRC
2019-03-28 14:28:03 -05:00
**** Image output without the =:file= header argument
2018-05-30 12:30:18 -05:00
For images sent by the kernel, if no =:file= parameter is provided to the code
block, a file name is automatically generated based on the image data and the
image is written to file in =org-babel-jupyter-resource-directory=. This is
2019-01-12 20:56:48 -06:00
great for quickly generating throw-away plots while you are working on your
2018-05-30 12:30:18 -05:00
code. Once you are happy with your results you can specify the =:file=
parameter to fix the file name.
2019-03-28 14:28:03 -05:00
**** =org-babel-jupyter-resource-directory=
2019-01-16 16:59:19 -06:00
This variable is similar to =org-preview-latex-image-directory= but solely for
any files created when Jupyter code blocks are run, e.g. automatically
generated image file names.
2019-03-28 14:28:03 -05:00
***** Deletion of generated image files
2019-01-16 16:59:19 -06:00
Whenever you run a code block multiple times and replace its results, before
the results are replaced, any generated files will be deleted to reduce the
clutter in =org-babel-jupyter-resource-directory=.
**** Convert rich kernel output with the =:pandoc= header argument
By default html, markdown, and latex results are wrapped in a =BEGIN_EXPORT=
block. If the header argument =:pandoc t= is set, they are instead
converted to org-mode format with [[https://pandoc.org/][pandoc]]. You can control which outputs get
converted with the custom variable =jupyter-org-pandoc-convertable=.
2019-03-28 14:28:03 -05:00
*** Editing the contents of a code block
2018-05-13 11:41:28 -05:00
2018-05-30 12:30:18 -05:00
When editing a Jupyter code block's contents, i.e. by pressing =C-c '= when at
a code block, =jupyter-repl-interaction-mode= is automatically enabled in the
edit buffer and the buffer will be associated with the REPL session of the code
block (see =jupyter-repl-associate-buffer=).
2018-05-13 11:41:28 -05:00
You may also bind the command =org-babel-jupyter-scratch-buffer= to an
appropriate key in =org-mode= to display a scratch buffer in the code block's
=major-mode= and connected to the code block's session.
2019-03-28 14:28:03 -05:00
*** Connecting to an existing kernel
2018-05-13 11:41:28 -05:00
2018-05-30 12:30:18 -05:00
To connect to an existing kernel, pass the kernel's connection file as the
value of the =:session= parameter. The name of the file must have a =.json=
suffix for this to work.
2019-03-28 14:28:03 -05:00
**** Remote kernels
2018-05-13 11:41:28 -05:00
If the connection file is a [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Remote-Files.html][remote file name]], i.e. has a prefix like
=/method:host:=, the kernel's ports are assumed to live on =host=. Before
attempting to connect to the kernel, =ssh= tunnels for the connection are
created. So if you had a remote kernel on a host named =ec2= whose connection
file is =/run/user/1000/jupyter/kernel-julia-0.6.json= on that host, you could
specify the =:session= like
2018-05-13 11:41:28 -05:00
2018-05-16 22:01:30 -05:00
#+BEGIN_SRC org
,#+BEGIN_SRC jupyter-julia :session /ssh:ec2:/run/user/1000/jupyter/kernel-julia-0.6.json
2018-05-16 22:02:31 -05:00
...
2018-05-16 22:01:30 -05:00
,#+END_SRC
2018-05-13 11:41:28 -05:00
#+END_SRC
Note, the kernel on the remote host needs to have the ZMQ socket ports exposed.
This means that starting a kernel using
#+BEGIN_SRC shell
jupyter notebook --no-browser
#+END_SRC
currently doesn't work since the notebook server does not allow communication
with a kernel using ZMQ sockets. You will have to use the connection file
created from using something like
#+BEGIN_SRC shell
jupyter kernel --kernel=python
#+END_SRC
2019-03-28 14:28:03 -05:00
***** Password handling for remote connections
2018-05-13 11:41:28 -05:00
Currently there is no password handling, so if your =ssh= connection requires a
password I suggest you instead use [[https://www.ssh.com/ssh/keygen/][key-based authentication]]. Or if you are
connecting to a server using a =pem= file add something like
#+BEGIN_SRC conf
Host ec2
User <user>
HostName <host>
IdentityFile <identity>.pem
2018-01-06 18:04:06 -06:00
#+END_SRC
2018-05-13 11:41:28 -05:00
to your =~/.ssh/config= file.
2019-03-28 14:28:03 -05:00
*** TODO Standard output, displayed data, and code block results
2018-11-29 02:37:23 -06:00
2018-12-09 23:16:05 -06:00
One significant difference between Jupyter code blocks and regular =org-mode=
code blocks is that the underlying Jupyter kernel can request that the client
display extra data in addition to output or the result of a code block. See
[[https://jupyter-client.readthedocs.io/en/stable/messaging.html#display-data][display_data messages]].
2019-01-13 17:25:29 -06:00
To account for this, Jupyter code blocks do not go through the normal
=org-mode= result insertion mechanism (see =org-babel-insert-result=). The
downside of this is that, compared to normal code blocks, only a small subset
of the header arguments common to all code blocks are supported. The upside is
that all forms of results produced by a kernel can be inserted into the buffer
similar to a Jupyter notebook.
2018-11-29 02:37:23 -06:00
The implementation of =org-mode= code blocks is really meant to handle either
capturing the standard output /or/ the result of a code block. When using
2019-01-12 20:56:48 -06:00
Jupyter code blocks, if the kernel produces output or asks to display extra
information, the results are appended to a =:RESULTS:= drawer.
2019-03-28 14:28:03 -05:00
*** =jupyter-org-interaction-mode=
2019-01-13 17:25:29 -06:00
A minor mode that enables completion and custom keybindings when =point= is
inside a Jupyter code block. This mode is enabled by default in =org-mode=
buffers, but only has an effect when =point= is inside a Jupyter code block.
2019-03-28 14:28:03 -05:00
**** Custom keybindings inside Jupyter code blocks
2019-01-13 17:25:29 -06:00
You can define new keybindings that are enabled when =point= is inside a
Jupyter code block by using the function =jupyter-org-define-key=. These
bindings are added to =jupyter-org-interaction-mode-map= and are only active
when =jupyter-org-interaction-mode= is enabled.
By default the following keybindings from =jupyter-repl-interaction-mode= are
available when =jupyter-org-interaction-mode= is enabled
| Key binding | Command |
|-------------+---------------------------------|
| =C-M-x= | =jupyter-eval-defun= |
| =M-i= | =jupyter-inspect-at-point= |
| =C-x C-e= | =jupyter-eval-line-or-region= |
| =C-c C-i= | =jupyter-repl-interrupt-kernel= |
| =C-c C-r= | =jupyter-repl-restart-kernel= |
2018-05-13 11:41:28 -05:00
* API
2018-05-30 12:30:18 -05:00
** Naming conventions
2019-01-16 16:59:19 -06:00
Methods that send messages to a kernel are named =jupyter-send-<msg-type>=
where =<msg-type>= is any message type. The message types are identical to
2018-05-30 12:30:18 -05:00
those defined in the [[http://jupyter-client.readthedocs.io/en/stable/messaging.html][Jupyter spec]] with ~_~ characters replaced by ~-~
characters. So to send an =execute-request= you would call
2019-01-16 16:59:19 -06:00
=jupyter-send-execute-request=.
Similarly, methods that are responsible for handling messages received from a
kernel are named =jupyter-handle-<msg-type>=.
2018-05-30 12:30:18 -05:00
Methods that require a message type as an argument such as
=jupyter-add-callback= should do so by passing a message type keyword such as
=:execute-request=.
2018-11-15 17:22:20 -06:00
** Overview
*** Classes
2018-05-30 12:30:18 -05:00
- =jupyter-kernel-client= :: The base class for Jupyter frontends. Handles all
message sending and receiving to/from a Jupyter kernel.
- =jupyter-kernel-manager= :: The base class for starting local kernel
processes.
- =jupyter-widget-client= :: (EXPERIMENTAL) A subclass of
=jupyter-kernel-client= that adds support for displaying Jupyter widgets in
an external browser.
- =jupyter-repl-client= :: A subclass of =jupyter-kernel-client= that implements
a REPL. Note, a =jupyter-repl-client= also has a =jupyter-widget-client= as
a parent class.
- =jupyter-org-client= :: A subclass of =jupyter-repl-client= that adds support
for evaluating =org-mode= source code blocks and inserting the results in
the =org-mode= buffer.
2018-11-15 17:22:20 -06:00
**** Lower level classes
- =jupyter-ioloop= :: A general class for asynchronous communication with a
2018-11-29 02:37:23 -06:00
subprocess. The subprocess polls its standard input for "events" from the
parent process. To add a new event to be handled by the subprocess you use
=jupyter-ioloop-add-event=. The resulting subprocess event handler created
using =jupyter-ioloop-add-event= can potentially send an event back to the
parent process. In the parent, events are handled by extending the
=jupyter-ioloop-handler= method.
2018-11-15 17:22:20 -06:00
- =jupyter-channel-ioloop= :: A subclass of =jupyter-ioloop= configured to
2018-11-29 02:37:23 -06:00
start a subprocess that handles messages being passed on Jupyter channels
between a kernel and the parent Emacs process. This is what
=jupyter-kernel-client= uses to communicate with a kernel.
*** Communicating with a kernel
**** Initializing a connection
2018-05-30 12:30:18 -05:00
For a =jupyter-kernel-client= to start communicating with a kernel, the
following steps are taken:
1. Initialize the connection using =jupyter-initialize-connection=
2. Start listening on the client's channels with =jupyter-start-channels=
2019-02-10 02:41:42 -06:00
When starting a local kernel process, both steps are taken care of in
=jupyter-start-new-kernel=.
For remote kernels, you will have to manually supply the connection JSON file
to =jupyter-initialize-connection= and start the kernel channels.
2018-11-15 17:22:20 -06:00
**** Sending messages
Once a connection is initialized, messages can be sent to the kernel using the
=jupyter-send-<msg-type>= family of methods, where =<msg-type>= is any valid
2019-02-10 02:41:42 -06:00
request message type (see =jupyter-message-types=). These methods
asynchronously send a message to the kernel using a subprocess associated with
each client, see help:jupyter-channel-ioloop, and they each return a
=jupyter-request= object which encapsulates the information necessary for
handling reply messages associated with the request in the future.
2018-05-30 12:30:18 -05:00
**** Receiving messages
2018-11-15 17:22:20 -06:00
There are two ways to handle the reply messages sent by the kernel: (1)
2018-05-30 12:30:18 -05:00
subclass the =jupyter-kernel-client= and override the
=jupyter-handle-<msg-type>= family of methods or (2) attach callbacks to the
=jupyter-request= objects returned by the =jupyter-send-<msg-type>= methods.
Both ways can occur in parallel.
When a message is received, =jupyter-handle-message= is called on the client to
kick off the message handling process. Any callbacks associated with the
2018-11-15 17:22:20 -06:00
=jupyter-request= of the message are evaluated and the appropriate
=jupyter-handle-<msg-type>= method called.
2018-05-30 12:30:18 -05:00
Note, the default handler methods of =jupyter-kernel-client= are no-ops with
the exception of =jupyter-handle-input-request= which requests input from the
user and sends it to the kernel.
2018-05-13 11:41:28 -05:00
** =jupyter-kernel-client=
Represents a client connected to a Jupyter kernel.
*** Initializing a connection
=jupyter-initialize-connection= takes a client and a connection file as
arguments and configures the client to communicate with the kernel whose
2018-05-30 12:30:18 -05:00
connection information is contained in the [[http://jupyter-client.readthedocs.io/en/stable/kernels.html#connection-files][connection file]].
After initializing a connection, to begin communicating with a kernel call
2018-05-13 11:41:28 -05:00
=jupyter-start-channels=.
2018-01-06 18:04:06 -06:00
2018-01-18 23:10:51 -06:00
#+BEGIN_SRC elisp
2018-05-13 11:41:28 -05:00
(let ((client (jupyter-kernel-client)))
(jupyter-initialize-connection client "kernel1234.json")
(jupyter-start-channels client))
2018-01-18 23:10:51 -06:00
#+END_SRC
2018-05-13 11:41:28 -05:00
=jupyter-initialize-connection= is mainly useful when initializing a remote
2018-05-30 12:30:18 -05:00
connection or connecting to an existing kernel. In order to start a new kernel
on the =localhost= use =jupyter-start-new-kernel=
2018-01-06 18:04:06 -06:00
#+BEGIN_SRC elisp
2018-05-30 12:30:18 -05:00
(cl-destructuring-bind (manager client)
2018-05-13 11:41:28 -05:00
(jupyter-start-new-kernel "python")
BODY)
2018-01-18 23:10:51 -06:00
#+END_SRC
2018-05-30 12:30:18 -05:00
The above code starts a new =python= kernel and returns the
=jupyter-kernel-manager= object used to manage the lifetime of the local kernel
process and the =jupyter-kernel-client= connected to the manager's kernel.
=jupyter-start-channels= will already have been called on the returned client
when =jupyter-start-new-kernel= returns.
2018-05-13 11:41:28 -05:00
2018-05-30 12:30:18 -05:00
To create multiple client's connected to the kernel of a
=jupyter-kernel-manager= use =jupyter-make-client=.
2018-05-13 11:41:28 -05:00
*** Starting/stopping channels
2018-05-30 12:30:18 -05:00
To start a client's channels, use =jupyter-start-channels=. To stop a client's
channels, =jupyter-stop-channels=. To determine if at least one channel is
2018-05-13 11:41:28 -05:00
alive, =jupyter-channels-running-p=.
You can also start individual channels with
2018-05-13 11:41:28 -05:00
#+BEGIN_SRC elisp
(jupyter-start-channel client :shell)
2018-05-13 11:41:28 -05:00
#+END_SRC
and stop a channel with
#+BEGIN_SRC elisp
(jupyter-stop-channel client :shell)
#+END_SRC
2018-05-13 11:41:28 -05:00
*** Making requests to a kernel
:PROPERTIES:
:ID: 9D893914-E769-4AEF-8928-826B67038C2A
:END:
2018-01-18 23:10:51 -06:00
2018-05-30 12:30:18 -05:00
To free up Emacs from having to process messages sent to and received from a
kernel, an Emacs subprocess is created for every client. This subprocess is
responsible for polling the client's channels for messages and taking care of
message signing, encoding, and decoding. The parent Emacs process is only
responsible for supplying the message property lists (the representation used
for Jupyter messages in Emacs) when sending a message and will receive the
decoded message property list when receiving a message. The exception to this is
the heartbeat channel which is implemented using timers in the parent Emacs
process.
Note, the message property lists should not be accessed directly. There are
helper functions which should be used to access the message fields. See [[id:D09FDD89-43A9-41DA-A6E8-6D6C73336981][Message property lists]].
**** The lifetime of a request
Sending a request to a kernel is done through one of the
=jupyter-send-<msg-type>= methods of a =jupyter-kernel-client=. The arguments
of the Jupyter message that each method represents are passed as keyword
arguments, the keywords all have names according to the Jupyter messaging spec
but with ~_~ replaced by ~-~. These methods construct the message property
lists based on their arguments and pass the constructed message to the
=jupyter-send= method of a client. The =jupyter-send= method then returns a new
=jupyter-request= representing the sent message.
2018-01-18 23:10:51 -06:00
#+BEGIN_SRC elisp
2018-05-13 11:41:28 -05:00
(jupyter-send-execute-request client :code "1 + 2") ; Returns a `jupyter-request'
2018-01-06 18:04:06 -06:00
#+END_SRC
2018-05-30 12:30:18 -05:00
When a request is sent, the message ID of the request is added to the client's
request table which maps message IDs to their corresponding =jupyter-request=
objects.
When a message is received from the kernel the request that generated it is
found in the request table by using the =jupyter-message-parent-id= of the
message. The slots of the =jupyter-request= are updated, any callbacks
associated with the =jupyter-request= are run for the message, and the message
is dispatched to the appropriate channel handler method of the client (one of
the =jupyter-handle-<msg-type>= methods).
A request is considered complete and is dropped from the request table once a
=status: idle= message has been received for the request and it is not the most
recently made request.
2018-11-15 17:22:20 -06:00
**** =jupyter-generate-request=
2018-05-30 12:30:18 -05:00
When one of the send methods are called, a =jupyter-request= object is
instantiated by a call to =jupyter-generate-request= and the instantiated
request is returned by the send method so that the caller can attach their
callbacks as described above.
Most likely, subclasses would want to attach extra information to a request.
For example, an =org-mode= client that sends an =:execute-request= based on the
contents of a source code block might want to keep track of the code block's
buffer position so that it can insert the results at the right location when
they are ready.
This is the purpose of the =jupyter-generate-request= method. If a
=jupyter-request= object is not general enough for some purpose, a subclass of
=jupyter-kernel-client= can define a new request object, ensuring that the slots
of a =jupyter-request= are included, and return the new type of request when
=jupyter-generate-request= is called for a message.
For example, below is the definition of the =jupyter-org-request= type for
handling requests made in an =org-mode= buffer
#+BEGIN_SRC elisp
(cl-defstruct (jupyter-org-request
(:include jupyter-request))
result-type
block-params
results
silent
id-cleared-p
marker
async)
#+END_SRC
And the context specializers used are
#+BEGIN_SRC elisp
(cl-defmethod jupyter-generate-request ((client jupyter-org-client) msg
&context (major-mode org-mode))
...) ; Return a `jupyter-org-request'
#+END_SRC
Notice that the =major-mode= context allows for =jupyter-org-request= objects
to be used by =jupyter-generate-request= when the request is generated in
=org-mode= buffers and to use the less specialized =jupyter-request= in other
contexts.
2018-11-15 17:22:20 -06:00
**** =jupyter-drop-request=
2018-05-30 12:30:18 -05:00
When a request is completed, i.e. when the kernel sends an idle message for a
request, you may want to do some final cleanup of the request. This is the
purpose of the =jupyter-drop-request= method, it gets called when an idle
message has been received for a kernel but only when the request is not the
most recently sent request.
2018-05-13 11:41:28 -05:00
*** Handling received messages
2018-01-06 18:04:06 -06:00
2018-05-30 12:30:18 -05:00
The handler methods of a =jupyter-kernel-client= are called whenever the
corresponding message is received from the kernel. They are intended to be
overwritten by subclasses and most of the default implementations do nothing
with the exception of the =:input-reply=, =:comm-open=, and =:comm-close=
messages. The =:input-reply= handler asks for input from the user through the
minibuffer and sends it to the kernel whereas the =:comm-open= / =:comm-close=
default message handlers store the state of open =comms= in the client's =comms=
slot.
The handler methods have the following signature
2018-01-18 23:10:51 -06:00
#+BEGIN_SRC elisp
(cl-defmethod jupyter-handle-<msg-type> ((client jupyter-kernel-client) req arg1 arg2 ...)
2018-05-13 11:41:28 -05:00
BODY)
2018-01-18 23:10:51 -06:00
#+END_SRC
2018-01-06 18:04:06 -06:00
2018-05-30 12:30:18 -05:00
=req= will be the =jupyter-request= object that generated the message. =arg1=,
=arg2=, ... will be the unwrapped message contents passed to the handler, their
number of arguments and their order are dependent on the message type.
Alternatively you may work with the full message property list by accessing the
=jupyter-request-last-message= slot of the =juptyer-request= object.
See [[id:0E7CA280-8D14-4994-A3C7-C3B7204AC9D2][message callbacks]] for another way of handling received messages.
**** A note on boolean arguments
2018-11-15 17:22:20 -06:00
For message types that have boolean message fields, the symbol in the variable
=jupyter--false= represents a false value so when checking the contents of
these arguments it is best to explicitly check for =t=.
2018-05-30 12:30:18 -05:00
#+BEGIN_SRC elisp
(if (eq arg1 t) ...)
#+END_SRC
2018-05-13 11:41:28 -05:00
2018-11-15 17:22:20 -06:00
This is because there are some ambiguities between translating JSON values to
their Emacs Lisp equivalents, since =nil= in Emacs is used both as signifying
=false= or nothing whereas JSON has =null= for nothing.
2018-05-13 11:41:28 -05:00
*** Client local variables
2018-01-06 18:04:06 -06:00
2018-05-13 11:41:28 -05:00
Some variables which are used internally by =jupyter-kernel-client= have client
local values. For example the variable =jupyter-include-other-output= tells a
2018-05-30 12:30:18 -05:00
=jupyter-kernel-client= to pass IOPub messages originating from a different
client to their corresponding handlers and defaults to =nil=, i.e. do not
handle IOPub messages from other clients. To modify a client local variable you
would use =jupyter-set=
2018-01-06 18:04:06 -06:00
2018-05-13 11:41:28 -05:00
#+BEGIN_SRC elisp
(jupyter-set client 'jupyter-include-other-output t)
#+END_SRC
2018-01-06 18:04:06 -06:00
2018-05-30 12:30:18 -05:00
and to retrieve the client local value, use =jupyter-get=
2018-01-06 18:04:06 -06:00
2018-01-18 23:10:51 -06:00
#+BEGIN_SRC elisp
2018-05-13 11:41:28 -05:00
(jupyter-get client 'jupyter-include-other-output)
2018-01-18 23:10:51 -06:00
#+END_SRC
2018-01-06 18:04:06 -06:00
These functions just set/get the value of a buffer local variable in a private
buffer of the client. You may work with these buffer local variables directly
by using the =jupyter-with-client-buffer= macro, just be sure to use
2018-05-30 12:30:18 -05:00
=setq-local= if you are setting a new client local variable otherwise you may
change the global value of the variable. Alternatively you can define a
variable as automatically buffer local when set with =defvar-local=.
2018-05-13 11:41:28 -05:00
#+BEGIN_SRC elisp
(jupyter-with-client-buffer client
2018-05-13 11:41:28 -05:00
(message "jupyter-include-other-output: %s" jupyter-include-other-output)
(setq-local jupyter-include-other-output (not jupyter-include-other-output)))
#+END_SRC
2018-05-30 12:30:18 -05:00
**** Channel hooks
The channel hook variables =jupyter-iopub-message-hook=,
=jupyter-shell-message-hook=, and =jupyter-stdin-message-hook= are all client
local variables and functions can be added to or removed from them using
=jupyter-add-hook= and =jupyter-remove-hook=. See [[id:B29776AA-2ACF-4A4F-A4EA-3F194262465D][Channel hooks]].
2018-05-13 11:41:28 -05:00
** =jupyter-kernel-manager=
Manage the lifetime of a kernel on the =localhost=.
*** Kernelspecs
2019-01-12 20:56:48 -06:00
To get a list of kernelspecs on your system, as represented in Emacs, use
=jupyter-available-kernelspecs= which processes the output of the shell command
2018-05-13 11:41:28 -05:00
#+BEGIN_SRC sh
jupyter kernelspec list
#+END_SRC
2019-01-12 20:56:48 -06:00
to construct the list of kernelspecs. =jupyter-available-kernelspecs= also
supports remote hosts. If the =default-directory= points to a remote system,
the returned kernelspecs are those on the remote system.
2018-05-30 12:30:18 -05:00
2019-01-12 20:56:48 -06:00
To find all kernelspecs whose kernels match some regular expression use
=jupyter-find-kernelspecs=. In case you would like to get the kernelspec for a
specific kernel, use =jupyter-get-kernelspec=.
2018-05-13 11:41:28 -05:00
2018-05-30 12:30:18 -05:00
You may also use =jupyter-completing-read-kernelspec= in an
=interactive= spec to ask the user to select a kernel from
the list of available kernelspecs.
*** Managing the lifetime of a kernel
2018-05-13 11:41:28 -05:00
**** Starting a kernel
As was mentioned previously, to start a new kernel on the =localhost= and
create a connected client, use =jupyter-start-new-kernel= which takes a kernel
name and returns a =jupyter-kernel-manager= which manages the lifetime of the
kernel, and a connected =jupyter-kernel-client=.
2018-05-13 11:41:28 -05:00
#+BEGIN_SRC elisp
(cl-destructuring-bind (manager client)
2018-05-13 11:41:28 -05:00
(jupyter-start-new-kernel "python")
BODY)
#+END_SRC
Instead of supplying an exact kernel name, you may also supply the prefix of
one. Then the first available kernel that has the same prefix will be started.
See =jupyter-find-kernelspecs=.
**** Stopping a kernel
To shutdown a kernel, use =jupyter-shutdown-kernel=. To check if a kernel is
alive, =jupyter-kernel-alive-p=.
**** Interrupting a kernel
To interrupt a kernel, use =jupyter-interrupt-kernel=.
2018-05-30 12:30:18 -05:00
*** Making clients connected to a kernel
2018-05-13 11:41:28 -05:00
Once you have a kernel manager you can make new =jupyter-kernel-client= (or a
subclass of one) instances using =jupyter-make-client=.
2018-05-30 12:30:18 -05:00
** =jupyter-widget-client=
2018-10-27 22:14:59 -05:00
:PROPERTIES:
:ID: F8C2EB90-1DF3-4880-B684-31FE4784FAD1
:END:
This class adds support for interacting with Jupyter widgets using an external
browser for the widget display. In order for this to work properly you will
need to have =simple-httpd= and the =websocket= packages installed, in
addition, you will have to build the required javascript files as described in
[[id:59559FA3-59AD-453F-93E7-113B43F85493][Widget support]].
The default implementation of =jupyter-widget-client= overrides the following
methods of a =jupyter-kernel-client=
#+BEGIN_SRC elisp
(jupyter-handle-comm-close)
(jupyter-handle-comm-open)
(jupyter-handle-comm-msg)
#+END_SRC
2018-05-30 12:30:18 -05:00
2018-10-27 22:14:59 -05:00
Comm messages in Jupyter are a way to allow for custom messages between the
kernel and a client. In the case of Jupyter widgets they are used to sync
widget state between the kernel and client.
It would be amazing to add custom Jupyter widgets to Emacs using the built
=widget= library which would work for widgets such as text boxes, buttons, and
other simple widgets, but there doesn't seem to be a way to support more
complex widgets in Emacs that require embedded javascript.
The default implementation of =jupyter-kernel-client= only keeps track of open
comms through a client's =comms= slot. The =jupyter-widget-client= subclass
adds the functionality to display and interact with widgets through an external
browser. This works by relaying the comm messages between the browser and the
kernel through a websocket. For this to work, you will also need to have the
=simple-httpd= and =websocket= Emacs packages available.
2018-05-30 12:30:18 -05:00
This feature is currently experimental, but seems to work well. I was able to
interact with an [[https://github.com/jupyter-widgets/ipyleaflet][ipyleaflet]] map without any noticeable delay.
2018-11-15 17:22:20 -06:00
** TODO =jupyter-repl-client=
** TODO =jupyter-ioloop=
** TODO =jupyter-channel-ioloop=
2019-05-04 03:25:08 -05:00
** TODO =jupyter-comm-layer=
2018-05-30 12:30:18 -05:00
** Callbacks and hooks
2018-05-13 11:41:28 -05:00
:PROPERTIES:
:ID: 0E7CA280-8D14-4994-A3C7-C3B7204AC9D2
:END:
2019-02-10 02:41:42 -06:00
There are mainly two ways of evaluating code when receiving a message from the
kernel. Either sub-classing =jupyter-kernel-client= and overriding the handler
methods or adding message callbacks to the =jupyter-request= objects returned
by the send methods. If both methods are used in parallel, the message
callbacks will run before the handler methods.
When working with a subclass of =jupyter-kernel-client=, to prevent a subset of
handler methods from firing when a message is received for a request, see
=jupyter-inhibit-handlers= below.
2018-05-13 11:41:28 -05:00
2019-02-10 02:41:42 -06:00
Also provided are message hook variables which are local to each client object
and look like =jupyter-<channel>-message-hook=, where =<channel>= can be one of
=iopub=, =shell=, or =stdin=. These hooks also provide an alternative method of
suppressing client handlers from running based on the received message.
2018-05-13 11:41:28 -05:00
*** =jupyter-request= callbacks
2018-05-30 12:30:18 -05:00
:PROPERTIES:
:ID: BFCFCD3B-138A-4471-BEED-0EA3258493E5
:END:
2018-05-13 11:41:28 -05:00
2019-02-10 02:41:42 -06:00
To add callbacks to a request, use =jupyter-add-callback= which accepts a
=jupyter-request= as its first argument and alternating (message type,
callback) pairs as the remaining arguments. The callbacks are registered with
the request object to run whenever a message of the appropriate type is
received. For example, to do something when a client receives a
=:kernel-info-reply= you would do the following:
2018-01-06 18:04:06 -06:00
#+BEGIN_SRC elisp
2018-05-13 11:41:28 -05:00
(jupyter-add-callback (jupyter-send-kernel-info-request client)
2018-01-18 23:10:51 -06:00
:kernel-info-reply (lambda (msg)
(let ((info (jupyter-message-content msg)))
BODY)))
2018-01-06 18:04:06 -06:00
#+END_SRC
To print out the results of an execute request:
#+BEGIN_SRC elisp
2018-05-13 11:41:28 -05:00
(jupyter-add-callback (jupyter-send-execute-request client :code "1 + 2")
2018-01-18 23:10:51 -06:00
:execute-result (lambda (msg)
(message (jupyter-message-data msg :text/plain))))
2018-01-06 18:04:06 -06:00
#+END_SRC
2018-01-18 23:10:51 -06:00
To add multiple callbacks to a request:
2018-01-06 18:04:06 -06:00
#+BEGIN_SRC elisp
2018-05-13 11:41:28 -05:00
(jupyter-add-callback (jupyter-send-execute-request client :code "1 + 2")
2018-01-18 23:10:51 -06:00
:execute-result (lambda (msg)
(message (jupyter-message-data msg :text/plain)))
:status (lambda (msg)
(when (jupyter-message-status-idle-p msg)
(message "DONE!"))))
#+END_SRC
There is also the possibility of running the same handler for different message
types:
#+BEGIN_SRC elisp
2018-05-13 11:41:28 -05:00
(jupyter-add-callback (jupyter-send-execute-request client :code "1 + 2")
2018-01-18 23:10:51 -06:00
'(:status :execute-result :execute-reply)
2018-01-06 18:04:06 -06:00
(lambda (msg)
(pcase (jupyter-message-type msg)
(:status ...)
(:execute-reply ...)
(:execute-result ...))))
2018-01-06 18:04:06 -06:00
#+END_SRC
2018-01-18 23:10:51 -06:00
*** Channel hooks
2018-05-30 12:30:18 -05:00
:PROPERTIES:
:ID: B29776AA-2ACF-4A4F-A4EA-3F194262465D
:END:
2018-01-18 23:10:51 -06:00
2018-05-13 11:41:28 -05:00
Hook variables are available for each channel: =jupyter-iopub-message-hook=,
=jupyter-stdin-message-hook=, and =jupyter-shell-message-hook=. Unless you want
to run a channel hook for every client, use =jupyter-add-hook= to add a
function to one of the channel hooks. =jupyter-add-hook= only adds to the
client local value of the hook variables.
2018-01-18 23:10:51 -06:00
#+BEGIN_SRC elisp
(jupyter-add-hook
client 'jupyter-iopub-message-hook
(lambda (msg)
(when (jupyter-message-status-idle-p msg)
(message "Kernel idle."))))
#+END_SRC
2018-05-30 12:30:18 -05:00
To remove a client local hook, use =jupyter-remove-hook=.
Channel hooks also provide a way of suppressing the handler methods. If any of
the channel hooks return a non-nil value, the handler method for that message
will be suppressed.
*** =jupyter-inhibit-handlers=
2018-01-18 23:10:51 -06:00
2018-05-30 12:30:18 -05:00
In addition to suppressing handler methods using channel hooks, to prevent a
client from running its handler methods for a particular request you can =let=
bind =jupyter-inhibit-handlers= to an appropriate value before the request is
2018-05-13 11:41:28 -05:00
made. For example, to prevent a client from running its stream handler for a
request you would do the following:
2018-01-06 18:04:06 -06:00
2018-05-13 11:41:28 -05:00
#+BEGIN_SRC elisp
(let ((jupyter-inhibit-handlers '(:stream)))
(jupyter-send-execute-request client :code "print(\"foo\")\n1 + 2"))
#+END_SRC
2018-01-06 18:04:06 -06:00
2018-05-30 12:30:18 -05:00
=jupyter-inhibit-handlers= can be either a list of message types or =t=, the
latter meaning inhibit handlers for all message types. Alternatively you can
set the =jupyter-request-inhibited-handlers= slot of a =jupyter-request=
object. This slot can take the same values as =jupyter-inhibit-handlers=.
2018-05-13 11:41:28 -05:00
** Waiting for messages
2018-05-30 12:30:18 -05:00
All message passing between the kernel and Emacs happens asynchronously. So if
a code path in Emacs Lisp is dependent on some message already having been
received, e.g. an idle message, there needs to be primitives that will block so
2019-02-10 02:41:42 -06:00
that there is a guarantee that a particular message has been received before
proceeding.
2018-01-06 18:04:06 -06:00
2018-05-30 12:30:18 -05:00
The following functions all wait for different conditions to be met on the
received messages of a request and return the message that caused the function
to stop waiting or =nil= if no message was received within a timeout period.
The default timeout is =jupyter-default-timeout= seconds.
For example, to wait until an idle message has been received for a request:
2018-01-06 18:04:06 -06:00
#+BEGIN_SRC elisp
(let ((timeout 4))
(jupyter-wait-until-idle
2018-05-13 11:41:28 -05:00
(jupyter-send-execute-request
2018-01-06 18:04:06 -06:00
client :code "import time\ntime.sleep(3)")
timeout))
#+END_SRC
To wait until a message of a specific type is received for a request:
#+BEGIN_SRC elisp
2018-01-18 23:10:51 -06:00
(jupyter-wait-until-received :execute-reply
2018-05-13 11:41:28 -05:00
(jupyter-send-execute-request client :code "[i*10 for i in range(100000)]"))
2018-01-06 18:04:06 -06:00
#+END_SRC
The most general form of the blocking functions is =jupyter-wait-until= which
2018-05-30 12:30:18 -05:00
takes a message type and a predicate function of a single argument. Whenever a
message is received that matches the message type, the message is passed to the
function to determine if =jupyter-wait-until= should return from waiting.
2018-01-06 18:04:06 -06:00
#+BEGIN_SRC elisp
(defun stream-prints-50-p (msg)
(let ((text (jupyter-message-get msg :text)))
(cl-loop for line in (split-string text "\n")
thereis (equal line "50"))))
(let ((timeout 2))
2018-01-18 23:10:51 -06:00
(jupyter-wait-until
2018-05-13 11:41:28 -05:00
(jupyter-send-execute-request client :code "[print(i) for i in range(100)]")
2018-01-18 23:10:51 -06:00
:stream #'stream-prints-50-p
timeout))
2018-01-06 18:04:06 -06:00
#+END_SRC
The above code runs =stream-prints-50-p= for every =stream= message received
from a kernel (here assumed to be a python kernel) for an execute request that
prints the numbers 0 to 99 and waits until the kernel has printed the number 50
before returning from the =jupyter-wait-until= call. If the number 50 is not
printed before the two second timeout, =jupyter-wait-until= returns =nil=.
2018-05-13 11:41:28 -05:00
Otherwise it returns the stream message whose content contains the number 50.
** Message property lists
2018-05-30 12:30:18 -05:00
:PROPERTIES:
:ID: D09FDD89-43A9-41DA-A6E8-6D6C73336981
:END:
2018-01-06 18:04:06 -06:00
2018-05-30 12:30:18 -05:00
There is really no need to construct or access message property lists directly.
The =jupyter-send-<msg-type>= client methods already handle creating them by
calling the =jupyter-message-<msg-type>= family of functions. Similarly, when a
message is received from a kernel the message properties are unwrapped and
passed as arguments to the =jupyter-handle-<msg-type>= client methods. If
required, the message property list is available in the
=jupyter-request-last-message= slot of the =jupyter-request= passed to the
=jupyter-handle-<msg-type>= client methods.
On the other hand, message callbacks pass the message property list directly to
the callback. In this case, the following functions can be used to access the
fields of the property list:
2018-01-06 18:04:06 -06:00
#+BEGIN_SRC elisp
2018-05-13 11:41:28 -05:00
;; Get the `:content' propery of MSG
(jupyter-message-content msg)
;; Get the message type (one of the keys in `jupyter-message-types')
(jupyter-message-type msg)
;; Get the value of KEY in the MSG contents
(jupyter-message-get msg key)
;; Get the value of the MIMETYPE in MSG's :data property
;; MIMETYPE should be one of `:image/png', `:text/plain', ...
(jupyter-message-data msg mimetype)
2018-01-06 18:04:06 -06:00
#+END_SRC
2018-05-30 12:30:18 -05:00
Note that access of the message property lists should only occur through the
=jupyter-message-*= functions since the main parts of a message such as the
content and header are lazily decoded.
*** Convenience macros
=jupyter-with-message-content= gives a way to extract and
bind the keys of a =jupyter-message-content= easily
#+BEGIN_SRC elisp
(jupyter-with-message-content msg (status ename)
...) ; status and ename keys of (jupyter-message-content msg) are bound
#+END_SRC
There is also =jupyter-with-message-data= which extracts
and binds the mimetypes of =jupyter-message-data=
#+BEGIN_SRC elisp
(jupyter-with-message-data msg ((res text/plain))
...) ; res is bound to (jupyter-message-data msg :text/plain)
#+END_SRC
** Modify behavior depending on kernel language
Since Jupyter supports many different programming language kernels, each with
varying degrees of support in Emacs there needs to be a general way of
modifying the behavior of the client to take this into account.
This is achieved using the =&context= specializer of =cl-defmethod=. There are
currently two specializers in use, =jupyter-lang= and =jupyter-repl-mode=.
=jupyter-lang= is a context specializer that matches when the kernel language
of the =jupyter-current-client= is equal to the specializer's argument. For
example, below is the function that gets called in the REPL buffer when the
kernel language is =julia= for indenting the current line:
#+BEGIN_SRC elisp
(cl-defmethod jupyter-indent-line (&context (jupyter-lang julia))
(call-interactively #'julia-latexsub-or-indent))
#+END_SRC
There are many other entry points where methods may be overridden in such a
way. Below is the full list of methods that can be overridden in this way
2019-01-13 17:25:29 -06:00
| Method | Purpose |
|--------------------------------------+---------------------------------------------------------------|
| =jupyter-insert= | Insert Jupyter results into the buffer |
| =jupyter-code-context= | Return code and position for inspect and complete requests |
| =jupyter-indent-line= | Indent the current cell in the REPL buffer |
| =jupyter-completion-prefix= | Return the completion prefix for the current context |
| =jupyter-completion-post-completion= | Evaluate code when a completion candidate has been selected |
| =jupyter-repl-after-init= | Evaluate code after a REPL buffer has been initialized |
| =jupyter-repl-after-change= | Evaluate code when the input cell code changes |
| =jupyter-markdown-follow-link= | Follow a markdown link at point |
| =jupyter-handle-payload= | Handle a payload sent by the kernel |
| =jupyter-org-result= | Transform result of execution into an =org= representation |
| =org-babel-jupyter-transform-code= | Transform code of a src-block before sending it to the kernel |
2018-05-30 12:30:18 -05:00
In addition to the =jupyter-lang= context, there is also the
=jupyter-repl-mode= context which is identical to the =derived-mode= context
but does its check against =jupyter-repl-lang-mode= if the
=jupyter-current-client= is a =jupyter-repl-client=. This is useful to modify
behavior depending on the =major-mode= that is used for a particular language.
For example for =javascript= kernels, it used to setup code highlighting when
=js2-mode= is used as the REPL languages =major-mode= since =js2-mode= does not
use =font-lock=.
** =org-mode=
2018-11-15 17:22:20 -06:00
*** =jupyter-org-client=
2018-05-30 12:30:18 -05:00
A =jupyter-org-client= is a subclass of =jupyter-kernel-client= meant to
display the results of a Jupyter code block in an =org-mode= buffer.
2018-11-15 17:22:20 -06:00
**** =jupyter-org-result=
The main entry point for extending how results are inserted into the =org-mode=
buffer is the method help:jupyter-org-result, which dispatches on the MIME type
2018-11-29 02:37:23 -06:00
of a result returned from a kernel. The MIME type priority is given in
=jupyter-org-mime-types=. =jupyter-org-result= can return either an
=org-element= object or a string. In the former case, the =org-element= is
transformed into its string representation before insertion into the buffer. In
the later case, the string is inserted into the =org-mode= buffer as is,
without any further processing.
There are helper functions for generating =org-element= objects which have
names like =jupyter-org-scalar=, =jupyter-org-export-block=,
=jupyter-org-file-link=, etc.
2018-11-15 17:22:20 -06:00
***** Extending =jupyter-org-result=
For a kernel language to extend the behavior of how results are inserted, the
=jupyter-lang= method specializer can be used. For example, below is how
=:text/plain= results are modified for Python code blocks
#+BEGIN_SRC elisp
(cl-defmethod jupyter-org-result ((_mime (eql :text/plain))
&context (jupyter-lang python)
&rest _)
(let ((result (cl-call-next-method)))
(cond
2018-11-29 02:37:23 -06:00
((stringp result)
(org-babel-python-table-or-string result))
2018-11-15 17:22:20 -06:00
(t result))))
#+END_SRC
=cl-call-next-method= calls down to a less specialized method of
=jupyter-org-result= and if the returned result is still expected to be plain
text, calls =org-babel-python-table-org-string= to convert any results that
2018-11-29 02:37:23 -06:00
look like Python arrays into =org-mode= tables before returning its result.
2019-01-13 17:25:29 -06:00
*** =jupyter-org-define-key=
Bind a key that is only available when =point= is inside a Jupyter code block.
When the command bound to the key is evaluated, =jupyter-current-client= will
be bound to the client of the current code block, also the syntax table will be
the same as the underlying kernel language's (see
=jupyter-org-with-src-block-client=).
These keys only have an effect when =jupyter-org-interaction-mode= is enabled.