2012-05-07 14:41:15 +02:00
;;; ein-kernel.el --- Communicate with IPython notebook server
;; Copyright (C) 2012- Takafumi Arakaki
2012-07-01 20:18:05 +02:00
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
2012-05-07 14:41:15 +02:00
;; This file is NOT part of GNU Emacs.
;; ein-kernel.el is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; ein-kernel.el is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with ein-kernel.el. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
2018-11-03 16:02:04 -04:00
;; `ein:kernel' is the proxy class of notebook server state.
;; It agglomerates both the "kernel" and "session" objects of server described here
;; https://github.com/jupyter/jupyter/wiki/Jupyter-Notebook-Server-API
;; It may have been better to keep them separate to allow parallel reasoning with
;; the notebook server, but that time is past.
2012-05-07 14:41:15 +02:00
;;; Code:
2012-07-08 13:45:30 +02:00
( require 'ansi-color )
2012-05-07 14:41:15 +02:00
2012-08-28 15:26:32 +02:00
( require 'ein-core )
2017-07-12 14:38:04 -05:00
( require 'ein-classes )
2012-05-07 14:41:15 +02:00
( require 'ein-log )
( require 'ein-websocket )
2012-05-22 13:34:38 +02:00
( require 'ein-events )
2012-05-26 21:08:48 +02:00
( require 'ein-query )
2015-05-15 23:23:56 -05:00
( require 'ein-ipdb )
2020-02-29 22:29:57 -05:00
( declare-function ein:notebook-get-opened-notebook " ein-notebook " )
( declare-function ein:notebooklist-get-buffer " ein-notebooklist " )
( declare-function ein:notebooklist-reload " ein-notebooklist " )
2012-09-01 20:51:55 +02:00
2014-10-22 21:35:20 -05:00
( defun ein:$kernel-session-url ( kernel )
( concat " /api/sessions/ " ( ein:$kernel-session-id kernel ) ) )
2012-09-01 20:51:55 +02:00
;;;###autoload
2012-08-28 14:53:41 +02:00
( defalias 'ein:kernel-url-or-port 'ein:$kernel-url-or-port )
2012-09-01 20:51:55 +02:00
;;;###autoload
2012-08-15 20:54:22 +02:00
( defalias 'ein:kernel-id 'ein:$kernel-kernel-id )
2012-05-31 15:43:49 +02:00
2018-05-12 17:32:32 -05:00
( defcustom ein:pre-kernel-execute-functions nil
" List of functions to call before sending a message to the kernel for execution. Each function is called with the message (see `ein:kernel--get-msg' ) about to be sent. "
:type 'list
:group 'ein )
( defcustom ein:on-shell-reply-functions nil
" List of functions to call when the kernel responds on the shell channel.
Each function should have the call signature: msg-id header content metadata "
:type 'list
:group 'ein )
2020-03-01 16:30:35 -07:00
( defcustom ein:on-kernel-connect-functions nil
" Abnormal hook that is run after a websocket connection is made
to a jupyter kernel. Functions defined here must accept a single
argument, which is the kernel that was just connected. "
:type 'list
:group 'ein )
2018-05-12 17:32:32 -05:00
2012-05-22 13:34:38 +02:00
;;; Initialization and connection.
2018-11-05 10:16:40 -05:00
( defun ein:kernel-new ( url-or-port path kernelspec base-url events &optional api-version )
2012-05-07 14:41:15 +02:00
( make-ein:$kernel
2012-05-13 15:59:40 +02:00
:url-or-port url-or-port
2018-11-05 10:16:40 -05:00
:path path
:kernelspec kernelspec
2012-05-22 13:34:38 +02:00
:events events
2018-10-30 14:17:29 -04:00
:api-version ( or api-version 5 )
2014-11-16 08:12:04 -06:00
:session-id ( ein:utils-uuid )
2012-05-07 14:41:15 +02:00
:kernel-id nil
2018-10-30 14:17:29 -04:00
:websocket nil
2012-05-22 13:34:38 +02:00
:base-url base-url
2015-03-31 16:55:14 -05:00
:stdin-activep nil
2018-11-22 09:07:02 -07:00
:oinfo-cache ( make-hash-table :test #' equal )
2012-05-07 14:41:15 +02:00
:username " username "
2012-08-26 15:14:59 +02:00
:msg-callbacks ( make-hash-table :test 'equal ) ) )
2012-05-07 14:41:15 +02:00
2012-05-19 17:58:02 +02:00
( defun ein:kernel-del ( kernel )
" Destructor for `ein:$kernel' . "
2015-07-08 13:10:45 -05:00
( ein:kernel-disconnect kernel ) )
2012-05-19 17:58:02 +02:00
2012-05-22 13:34:38 +02:00
( defun ein:kernel--get-msg ( kernel msg-type content )
2012-05-07 14:41:15 +02:00
( list
:header ( list
:msg_id ( ein:utils-uuid )
:username ( ein:$kernel-username kernel )
:session ( ein:$kernel-session-id kernel )
2018-08-07 08:36:26 -05:00
:version " 5.0 "
2016-08-26 20:52:54 -05:00
:date ( format-time-string " %Y-%m-%dT%T " ( current-time ) ) ; ISO 8601 timestamp
2012-05-07 14:41:15 +02:00
:msg_type msg-type )
2012-08-07 00:54:36 +02:00
:metadata ( make-hash-table )
2012-05-07 14:41:15 +02:00
:content content
:parent_header ( make-hash-table ) ) )
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-session-p ( kernel callback &optional iteration )
2018-11-05 10:16:40 -05:00
" Don't make any changes on the server side. CALLBACK with arity 2, kernel and a boolean whether session exists on server. "
2018-11-03 16:02:04 -04:00
( unless iteration
( setq iteration 0 ) )
( let ( ( session-id ( ein:$kernel-session-id kernel ) ) )
( ein:query-singleton-ajax
( ein:url ( ein:$kernel-url-or-port kernel ) " api/sessions " session-id )
:type " GET "
:parser #' ein:json-read
:complete ( apply-partially #' ein:kernel-session-p--complete session-id )
2018-11-05 10:16:40 -05:00
:success ( apply-partially #' ein:kernel-session-p--success kernel session-id callback )
:error ( apply-partially #' ein:kernel-session-p--error kernel callback iteration ) ) ) )
2018-11-03 16:02:04 -04:00
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-session-p--complete ( session-id
&key data response
&allow-other-keys
&aux ( resp-string ( format " STATUS: %s DATA: %s " ( request-response-status-code response ) data ) ) )
2018-11-03 16:02:04 -04:00
( ein:log 'debug " ein:kernel-session-p--complete %s " resp-string ) )
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-session-p--error ( kernel callback iteration
&key error-thrown symbol-status data
&allow-other-keys )
2019-11-13 13:54:29 -05:00
( if ( ein:aand ( plist-get data :message ) ( cl-search " not found " it ) )
2018-11-05 10:16:40 -05:00
( when callback ( funcall callback kernel nil ) )
2018-11-03 16:02:04 -04:00
( let* ( ( max-tries 3 )
( tries-left ( 1- ( - max-tries iteration ) ) ) )
( ein:log 'verbose " ein:kernel-session-p--error [%s], %s tries left "
( car error-thrown ) tries-left )
( if ( > tries-left 0 )
2018-11-05 10:16:40 -05:00
( ein:kernel-session-p kernel callback ( 1+ iteration ) ) ) ) ) )
2018-11-03 16:02:04 -04:00
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-session-p--success ( kernel session-id callback
&key data &allow-other-keys )
2018-11-03 16:02:04 -04:00
( let ( ( session-p ( equal ( plist-get data :id ) session-id ) ) )
2018-11-09 18:27:23 -06:00
( ein:log 'verbose " ein:kernel-session-p--success: session-id=%s session-p=%s "
2018-11-03 16:02:04 -04:00
session-id session-p )
2018-11-05 10:16:40 -05:00
( when callback ( funcall callback kernel session-p ) ) ) )
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-restart-session ( kernel )
2018-11-05 10:16:40 -05:00
" Server side delete of KERNEL session and subsequent restart with all new state "
2018-11-09 18:27:23 -06:00
( ein:kernel-delete-session
2018-11-05 10:16:40 -05:00
( lambda ( kernel )
( ein:events-trigger ( ein:$kernel-events kernel ) 'status_restarting.Kernel )
( ein:kernel-retrieve-session kernel 0
( lambda ( kernel )
( ein:events-trigger ( ein:$kernel-events kernel )
2020-01-12 23:01:26 -05:00
'status_restarted.Kernel ) ) ) )
:kernel kernel ) )
2018-11-05 10:16:40 -05:00
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-retrieve-session ( kernel &optional iteration callback )
2018-11-03 16:02:04 -04:00
" Formerly ein:kernel-start, but that was misnomer because 1. the server really starts a session (and an accompanying kernel), and 2. it may not even start a session if one exists for the same path.
2018-12-05 17:15:02 -05:00
If 'picking up from where we last left off ', that is, we restart emacs and reconnect to same server, jupyter will hand us back the original, still running session.
2018-11-03 16:02:04 -04:00
The server logic is here ( could not find other documentation )
https://github.com/jupyter/notebook/blob/04a686dbaf9dfe553324a03cb9e6f778cf1e3da1/notebook/services/sessions/handlers.py#L56-L81
2018-11-05 10:16:40 -05:00
CALLBACK of arity 1 , the kernel.
2018-11-03 16:02:04 -04:00
"
2018-10-30 14:17:29 -04:00
( unless iteration
( setq iteration 0 ) )
2018-11-01 20:08:10 -04:00
( if ( <= ( ein:$kernel-api-version kernel ) 2 )
2018-11-03 16:02:04 -04:00
( error " Api %s unsupported " ( ein:$kernel-api-version kernel ) )
2018-11-05 10:16:40 -05:00
( let ( ( kernel-id ( ein:$kernel-kernel-id kernel ) )
( kernelspec ( ein:$kernel-kernelspec kernel ) )
( path ( ein:$kernel-path kernel ) ) )
2018-11-01 20:08:10 -04:00
( ein:query-singleton-ajax
( ein:url ( ein:$kernel-url-or-port kernel ) " api/sessions " )
:type " POST "
:data ( json-encode
( cond ( ( <= ( ein:$kernel-api-version kernel ) 4 )
` ( ( " notebook " .
2018-11-05 10:16:40 -05:00
( ( " path " . , path ) ) )
2018-11-01 20:08:10 -04:00
,@ ( if kernelspec
` ( ( " kernel " .
( ( " name " . , ( ein:$kernelspec-name kernelspec ) ) ) ) ) ) ) )
2018-11-05 10:16:40 -05:00
( t ` ( ( " path " . , path )
2018-11-01 20:08:10 -04:00
( " type " . " notebook " )
2018-11-04 19:45:22 -05:00
,@ ( if kernelspec
` ( ( " kernel " .
( ( " name " . , ( ein:$kernelspec-name kernelspec ) )
,@ ( if kernel-id
` ( ( " id " . , kernel-id ) ) ) ) ) ) ) ) ) ) )
2018-11-01 20:08:10 -04:00
:parser #' ein:json-read
2018-11-03 16:02:04 -04:00
:complete ( apply-partially #' ein:kernel-retrieve-session--complete kernel callback )
:success ( apply-partially #' ein:kernel-retrieve-session--success kernel callback )
2018-11-05 10:16:40 -05:00
:error ( apply-partially #' ein:kernel-retrieve-session--error kernel iteration callback ) ) ) ) )
2012-05-07 14:41:15 +02:00
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-retrieve-session--complete
( kernel callback
&key data response
&allow-other-keys
&aux ( resp-string ( format " STATUS: %s DATA: %s " ( request-response-status-code response ) data ) ) )
2018-11-03 16:02:04 -04:00
( ein:log 'debug " ein:kernel-retrieve-session--complete %s " resp-string ) )
2018-11-01 20:08:10 -04:00
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-retrieve-session--error
( kernel iteration callback
&key error-thrown symbol-status &allow-other-keys )
2018-11-04 19:45:22 -05:00
( let* ( ( max-tries 3 )
2018-10-30 14:17:29 -04:00
( tries-left ( 1- ( - max-tries iteration ) ) ) )
2018-11-03 16:02:04 -04:00
( ein:log 'verbose " ein:kernel-retrieve-session--error [%s], %s tries left "
2018-10-30 14:17:29 -04:00
( car error-thrown ) tries-left )
2018-11-04 18:59:30 -05:00
( sleep-for 0 ( * ( 1+ iteration ) 500 ) )
2018-10-30 14:17:29 -04:00
( if ( > tries-left 0 )
2018-11-05 10:16:40 -05:00
( ein:kernel-retrieve-session kernel ( 1+ iteration ) callback ) ) ) )
2018-10-30 14:17:29 -04:00
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-retrieve-session--success ( kernel callback &key data &allow-other-keys )
2014-10-17 16:44:04 -05:00
( let ( ( session-id ( plist-get data :id ) ) )
( if ( plist-get data :kernel )
( setq data ( plist-get data :kernel ) ) )
2020-01-11 16:03:54 -05:00
( cl-destructuring-bind ( &key id &allow-other-keys ) data
2018-11-09 18:27:23 -06:00
( ein:log 'verbose " ein:kernel-retrieve-session--success: kernel-id=%s session-id=%s "
2018-11-01 20:08:10 -04:00
id session-id )
2014-10-17 16:44:04 -05:00
( setf ( ein:$kernel-kernel-id kernel ) id )
( setf ( ein:$kernel-session-id kernel ) session-id )
2015-07-08 13:37:09 -05:00
( setf ( ein:$kernel-ws-url kernel ) ( ein:kernel--ws-url ( ein:$kernel-url-or-port kernel ) ) )
2014-10-17 16:44:04 -05:00
( setf ( ein:$kernel-kernel-url kernel )
2018-10-30 14:17:29 -04:00
( concat ( file-name-as-directory ( ein:$kernel-base-url kernel ) ) id ) ) )
2018-11-05 10:16:40 -05:00
( ein:kernel-start-websocket kernel callback ) ) )
( defun ein:kernel-reconnect-session ( kernel &optional callback )
" Check if session still exists. If it does, retrieve it. If it doesn't, ask the user to create a new session (ein:kernel-retrieve-session both retrieves and creates).
2019-02-26 17:58:42 -05:00
CALLBACK takes one argument kernel ( e.g., execute cell now that we 're reconnected ) "
2018-11-05 10:16:40 -05:00
( ein:kernel-disconnect kernel )
( ein:kernel-session-p
kernel
2018-11-09 18:27:23 -06:00
( apply-partially
( lambda ( callback* kernel session-p )
2018-12-05 17:15:02 -05:00
( when ( or session-p
( and ( not noninteractive ) ( y-or-n-p " Session not found. Restart? " ) ) )
( ein:events-trigger ( ein:$kernel-events kernel ) 'status_reconnecting.Kernel )
( ein:kernel-retrieve-session
kernel 0
( apply-partially
( lambda ( callback** kernel )
( ein:events-trigger ( ein:$kernel-events kernel )
'status_reconnected.Kernel )
( when callback** ( funcall callback** kernel ) ) )
callback* ) ) ) )
2018-11-05 10:16:40 -05:00
callback ) ) )
2012-05-07 14:41:15 +02:00
2018-12-01 18:54:58 -05:00
( defun ein:kernel--ws-url ( url-or-port )
" Assuming URL-OR-PORT already normalized by `ein:url'
See https://github.com/ipython/ipython/pull/3307 "
( let* ( ( parsed-url ( url-generic-parse-url url-or-port ) )
( protocol ( if ( string= ( url-type parsed-url ) " https " ) " wss " " ws " ) ) )
( format " %s://%s:%s%s "
protocol
( url-host parsed-url )
( url-port parsed-url )
( url-filename parsed-url ) ) ) )
2013-06-10 19:56:46 +02:00
2012-12-17 17:14:26 +01:00
( defun ein:kernel-send-cookie ( channel host )
;; cookie can be an empty string for IPython server with no password,
;; but something must be sent to start channel.
( let ( ( cookie ( ein:query-get-cookie host " / " ) ) )
( ein:websocket-send channel cookie ) ) )
2012-05-07 14:41:15 +02:00
2018-10-30 14:17:29 -04:00
( defun ein:kernel--handle-websocket-reply ( kernel ws frame )
( ein:and-let* ( ( packet ( websocket-frame-payload frame ) )
( channel ( plist-get ( ein:json-read-from-string packet ) :channel ) ) )
( cond ( ( string-equal channel " iopub " )
( ein:kernel--handle-iopub-reply kernel packet ) )
( ( string-equal channel " shell " )
( ein:kernel--handle-shell-reply kernel packet ) )
( ( string-equal channel " stdin " )
( ein:kernel--handle-stdin-reply kernel packet ) )
( t ( ein:log 'warn " Received reply from unforeseen channel %s " channel ) ) ) ) )
2018-11-05 10:16:40 -05:00
( defun ein:start-single-websocket ( kernel open-callback )
" OPEN-CALLBACK (kernel) (e.g., execute cell) "
2018-10-30 14:17:29 -04:00
( let ( ( ws-url ( concat ( ein:$kernel-ws-url kernel )
( ein:$kernel-kernel-url kernel )
" /channels?session_id= "
( ein:$kernel-session-id kernel ) ) ) )
( ein:log 'verbose " WS start: %s " ws-url )
2018-11-01 20:26:42 -05:00
( setf ( ein:$kernel-websocket kernel )
2018-10-30 14:17:29 -04:00
( ein:websocket ws-url kernel
( apply-partially #' ein:kernel--handle-websocket-reply kernel )
( lambda ( ws )
( let* ( ( websocket ( websocket-client-data ws ) )
( kernel ( ein:$websocket-kernel websocket ) ) )
( unless ( ein:$websocket-closed-by-client websocket )
2018-11-05 10:16:40 -05:00
( ein:log 'verbose " WS closed unexpectedly: %s " ( websocket-url ws ) )
2018-10-30 14:17:29 -04:00
( ein:kernel-disconnect kernel ) ) ) )
2018-11-05 10:16:40 -05:00
( apply-partially
( lambda ( cb ws )
( let* ( ( websocket ( websocket-client-data ws ) )
( kernel ( ein:$websocket-kernel websocket ) ) )
( when ( ein:kernel-live-p kernel )
2020-03-14 00:31:37 -04:00
( ein:log 'debug " ein:start-single-websocket: Running on-connect abnormal hooks. " )
2020-03-01 16:30:35 -07:00
( run-hook-with-args 'ein:on-kernel-connect-functions kernel )
2018-11-05 10:16:40 -05:00
( when cb
( funcall cb kernel ) ) )
( ein:log 'verbose " WS opened: %s " ( websocket-url ws ) ) ) )
open-callback ) ) ) ) )
( defun ein:kernel-start-websocket ( kernel callback )
2018-10-30 14:17:29 -04:00
( cond ( ( <= ( ein:$kernel-api-version kernel ) 2 )
( error " Api version %s unsupported " ( ein:$kernel-api-version kernel ) ) )
2018-11-05 10:16:40 -05:00
( t ( ein:start-single-websocket kernel callback ) ) ) )
2012-05-07 14:41:15 +02:00
2014-11-16 11:49:02 -06:00
( defun ein:kernel-on-connect ( kernel content -metadata-not-used- )
( ein:log 'info " Kernel connect_request_reply received. " ) )
2012-05-07 14:41:15 +02:00
2018-10-30 14:17:29 -04:00
( defun ein:kernel-disconnect ( kernel )
2018-11-03 16:02:04 -04:00
" Close websocket connection to running kernel, but do not
delete the kernel on the server side "
2018-11-02 11:29:51 -04:00
( ein:events-trigger ( ein:$kernel-events kernel ) 'status_disconnected.Kernel )
2019-11-24 00:17:52 -05:00
( aif ( ein:$kernel-websocket kernel )
2018-10-30 14:17:29 -04:00
( progn ( ein:websocket-close it )
( setf ( ein:$kernel-websocket kernel ) nil ) ) ) )
2012-05-07 14:41:15 +02:00
2012-07-19 00:16:00 +02:00
( defun ein:kernel-live-p ( kernel )
2018-10-30 14:17:29 -04:00
( and ( ein:$kernel-p kernel )
( ein:aand ( ein:$kernel-websocket kernel ) ( ein:websocket-open-p it ) ) ) )
2012-05-13 16:54:22 +02:00
2018-11-05 10:16:40 -05:00
( defun ein:kernel-when-ready ( kernel callback )
2019-02-26 17:58:42 -05:00
" Execute CALLBACK of arity 1 (the kernel) when KERNEL is ready. Warn user otherwise. "
2018-11-05 10:16:40 -05:00
( if ( ein:kernel-live-p kernel )
( funcall callback kernel )
( ein:log 'verbose " Kernel %s unavailable " ( ein:$kernel-kernel-id kernel ) )
( ein:kernel-reconnect-session kernel callback ) ) )
2012-05-13 16:54:22 +02:00
2017-04-21 17:57:16 -05:00
( defun ein:kernel-object-info-request ( kernel objname callbacks &optional cursor-pos detail-level )
2012-05-22 13:34:38 +02:00
" Send object info request of OBJNAME to KERNEL.
When calling this method pass a CALLBACKS structure of the form:
( :object_info_reply ( FUNCTION . ARGUMENT ) )
2012-08-08 23:27:07 +02:00
Call signature::
( ` funcall ' FUNCTION ARGUMENT CONTENT METADATA )
2015-03-17 20:41:31 -05:00
CONTENT and METADATA are given by ` object_info_reply ' message.
2012-05-13 16:54:22 +02:00
2015-03-17 20:41:31 -05:00
` object_info_reply ' message is documented here:
2012-05-22 13:34:38 +02:00
http://ipython.org/ipython-doc/dev/development/messaging.html#object-information
"
2014-11-16 11:49:02 -06:00
( assert ( ein:kernel-live-p kernel ) nil " object_info_reply: Kernel is not active. " )
2012-05-07 14:41:15 +02:00
( when objname
2018-10-30 14:17:29 -04:00
( if ( <= ( ein:$kernel-api-version kernel ) 2 )
( error " Api version %s unsupported " ( ein:$kernel-api-version kernel ) ) )
2018-09-09 09:44:59 -05:00
( let* ( ( content ( if ( < ( ein:$kernel-api-version kernel ) 5 )
( list
;; :text ""
:oname ( format " %s " objname )
:cursor_pos ( or cursor-pos 0 )
:detail_level ( or detail-level 0 ) )
( list
:code ( format " %s " objname )
:cursor_pos ( or cursor-pos 0 )
:detail_level ( or detail-level 0 ) ) ) )
2017-04-21 17:57:16 -05:00
( msg ( ein:kernel--get-msg kernel " inspect_request "
( append content ( list :detail_level 1 ) ) ) )
( msg-id ( plist-get ( plist-get msg :header ) :msg_id ) ) )
2015-01-14 17:07:28 -06:00
( ein:websocket-send-shell-channel kernel msg )
2015-03-17 20:41:31 -05:00
( ein:kernel-set-callbacks-for-msg kernel msg-id callbacks ) ) ) )
2012-05-22 13:34:38 +02:00
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-execute ( kernel code &optional callbacks
&key
( silent t )
( store-history t )
( user-expressions ( make-hash-table ) )
( allow-stdin t )
( stop-on-error nil ) )
2012-05-22 13:34:38 +02:00
" Execute CODE on KERNEL.
2020-01-11 16:03:54 -05:00
The CALLBACKS plist looks like:
2012-05-22 13:34:38 +02:00
2012-06-04 16:23:53 +02:00
( :execute_reply EXECUTE-REPLY-CALLBACK
:output OUTPUT-CALLBACK
:clear_output CLEAR-OUTPUT-CALLBACK
2012-06-04 16:18:17 +02:00
:set_next_input SET-NEXT-INPUT )
2012-05-22 13:34:38 +02:00
2020-01-11 16:03:54 -05:00
Right hand sides ending -CALLBACK above are of the form ( FUNCTION ARG1 . . . ARGN ) .
( Hindsight: this was all much better implemented using ` apply-partially ' )
2012-08-08 22:15:22 +02:00
2020-01-11 16:03:54 -05:00
Return randomly generated MSG-ID tag uniquely identifying expectation of a kernel response.
"
2014-11-16 11:49:02 -06:00
( assert ( ein:kernel-live-p kernel ) nil " execute_reply: Kernel is not active. " )
2018-10-27 17:52:17 -04:00
( let* ( ( content ( list
:code code
:silent ( or silent json-false )
:store_history ( or store-history json-false )
:user_expressions user-expressions
:allow_stdin allow-stdin
:stop_on_error ( or stop-on-error json-false ) ) )
( msg ( ein:kernel--get-msg kernel " execute_request " content ) )
( msg-id ( plist-get ( plist-get msg :header ) :msg_id ) ) )
2020-01-11 16:03:54 -05:00
( ein:log 'debug " ein:kernel-execute: code=%s msg_id=%s " code msg-id )
2018-10-27 17:52:17 -04:00
( run-hook-with-args 'ein:pre-kernel-execute-functions msg )
( ein:websocket-send-shell-channel kernel msg )
( ein:kernel-set-callbacks-for-msg kernel msg-id callbacks )
( unless silent
( mapc #' ein:funcall-packed
( ein:$kernel-after-execute-hook kernel ) ) )
msg-id ) )
2012-05-22 13:34:38 +02:00
2018-12-25 19:12:26 -05:00
( defun ein:kernel-complete ( kernel line cursor-pos callbacks errback )
2012-05-22 13:34:38 +02:00
" Complete code at CURSOR-POS in a string LINE on KERNEL.
CURSOR-POS is the position in the string LINE, not in the buffer.
2018-12-25 19:12:26 -05:00
ERRBACK takes a string ( error message ) .
2012-05-22 13:34:38 +02:00
When calling this method pass a CALLBACKS structure of the form:
( :complete_reply ( FUNCTION . ARGUMENT ) )
2012-05-07 14:41:15 +02:00
2012-08-08 23:27:07 +02:00
Call signature::
2018-12-25 19:12:26 -05:00
( funcall FUNCTION ARGUMENT CONTENT METADATA )
2012-08-08 23:27:07 +02:00
CONTENT and METADATA are given by ` complete_reply ' message.
2012-05-07 14:41:15 +02:00
2012-05-22 13:34:38 +02:00
` complete_reply ' message is documented here:
http://ipython.org/ipython-doc/dev/development/messaging.html#complete
"
2018-12-25 19:12:26 -05:00
( condition-case err
2019-05-29 18:34:09 +02:00
( let* ( ( content ( if ( < ( ein:$kernel-api-version kernel ) 4 )
2018-12-25 19:12:26 -05:00
( list
;; :text ""
:line line
:cursor_pos cursor-pos )
( list
:code line
:cursor_pos cursor-pos ) ) )
( msg ( ein:kernel--get-msg kernel " complete_request " content ) )
( msg-id ( plist-get ( plist-get msg :header ) :msg_id ) ) )
( assert ( ein:kernel-live-p kernel ) nil " kernel not live " )
( ein:websocket-send-shell-channel kernel msg )
( ein:kernel-set-callbacks-for-msg kernel msg-id callbacks )
msg-id )
( error ( if errback ( funcall errback ( error-message-string err ) )
( ein:display-warning ( error-message-string err ) :error ) ) ) ) )
2012-05-07 14:41:15 +02:00
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-history-request ( kernel callbacks
&key
( output nil )
( raw t )
( hist-access-type " tail " )
session
start
stop
( n 10 )
pattern
unique )
2012-08-28 12:18:12 +02:00
" Request execution history to KERNEL.
When calling this method pass a CALLBACKS structure of the form:
( :history_reply ( FUNCTION . ARGUMENT ) )
Call signature::
( ` funcall ' FUNCTION ARGUMENT CONTENT METADATA )
CONTENT and METADATA are given by ` history_reply ' message.
` history_reply ' message is documented here:
http://ipython.org/ipython-doc/dev/development/messaging.html#history
2012-09-08 00:04:08 +02:00
Relevant Python code:
* :py:method: ` IPython.zmq.ipkernel.Kernel.history_request `
* :py:class: ` IPython.core.history.HistoryAccessor `
2012-08-28 12:18:12 +02:00
"
2014-11-16 11:49:02 -06:00
( assert ( ein:kernel-live-p kernel ) nil " history_reply: Kernel is not active. " )
2012-08-28 12:18:12 +02:00
( let* ( ( content ( list
:output ( ein:json-any-to-bool output )
:raw ( ein:json-any-to-bool raw )
:hist_access_type hist-access-type
:session session
:start start
:stop stop
:n n
2013-01-18 14:28:21 +01:00
:pattern pattern
:unique unique ) )
2012-08-28 12:18:12 +02:00
( msg ( ein:kernel--get-msg kernel " history_request " content ) )
( msg-id ( plist-get ( plist-get msg :header ) :msg_id ) ) )
2015-01-14 17:07:28 -06:00
( ein:websocket-send-shell-channel kernel msg )
2012-08-28 12:18:12 +02:00
( ein:kernel-set-callbacks-for-msg kernel msg-id callbacks )
msg-id ) )
2014-10-22 21:35:20 -05:00
( defun ein:kernel-connect-request ( kernel callbacks )
" Request basic information for a KERNEL.
When calling this method pass a CALLBACKS structure of the form::
( :connect_reply ( FUNCTION . ARGUMENT ) )
Call signature::
( ` funcall ' FUNCTION ARGUMENT CONTENT METADATA )
CONTENT and METADATA are given by ` kernel_info_reply ' message.
` connect_request ' message is documented here:
http://ipython.org/ipython-doc/dev/development/messaging.html#connect
Example::
( ein:kernel-connect-request
( ein:get-kernel )
' ( :kernel_connect_reply ( message . \"CONTENT: %S\\nMETADATA: %S\" ) ) )
"
2015-01-14 17:07:28 -06:00
;(assert (ein:kernel-live-p kernel) nil "connect_reply: Kernel is not active.")
2014-10-22 21:35:20 -05:00
( let* ( ( msg ( ein:kernel--get-msg kernel " connect_request " ( make-hash-table ) ) )
( msg-id ( plist-get ( plist-get msg :header ) :msg_id ) ) )
2015-01-14 17:07:28 -06:00
( ein:websocket-send-shell-channel kernel msg )
2014-10-22 21:35:20 -05:00
( ein:kernel-set-callbacks-for-msg kernel msg-id callbacks )
msg-id ) )
2012-08-28 12:18:12 +02:00
2012-05-07 14:41:15 +02:00
( defun ein:kernel-interrupt ( kernel )
2018-10-30 14:17:29 -04:00
( when ( ein:kernel-live-p kernel )
2012-05-07 14:41:15 +02:00
( ein:log 'info " Interrupting kernel " )
2012-06-20 21:01:58 +02:00
( ein:query-singleton-ajax
2012-05-26 21:08:48 +02:00
( ein:url ( ein:$kernel-url-or-port kernel )
( ein:$kernel-kernel-url kernel )
" interrupt " )
:type " POST "
2012-12-29 17:17:39 +01:00
:success ( lambda ( &rest ignore )
( ein:log 'info " Sent interruption command. " ) ) ) ) )
2012-05-07 14:41:15 +02:00
2020-01-12 23:01:26 -05:00
( cl-defun ein:kernel-delete-session ( &optional callback
&key url-or-port path kernel
&aux ( session-id ) )
2018-11-09 13:02:43 -05:00
" Regardless of success or error, we clear all state variables of kernel and funcall CALLBACK (kernel) "
2020-01-12 23:01:26 -05:00
( cond ( kernel
( setq url-or-port ( ein:$kernel-url-or-port kernel ) )
( setq path ( ein:$kernel-path kernel ) )
( setq session-id ( ein:$kernel-session-id kernel ) ) )
( ( and url-or-port path )
( aif ( ein:notebook-get-opened-notebook url-or-port path )
( progn
( setq kernel ( ein:$notebook-kernel it ) )
( setq session-id ( ein:$kernel-session-id kernel ) ) )
( let ( ( ein:force-sync t ) )
( ein:content-query-sessions
url-or-port
( lambda ( session-hash )
( setq session-id ( car ( gethash path session-hash ) ) ) )
nil ) ) ) )
( t ( error " ein:kernel-delete-session: need kernel, or url-or-port and path " ) ) )
( if session-id
( ein:query-singleton-ajax
( ein:url url-or-port " api/sessions " session-id )
:type " DELETE "
:complete ( apply-partially #' ein:kernel-delete-session--complete kernel session-id callback )
2020-02-01 20:16:29 -05:00
:error ( apply-partially #' ein:kernel-delete-session--error session-id nil )
2020-01-19 09:03:51 -05:00
:success ( apply-partially #' ein:kernel-delete-session--success session-id
2020-01-19 09:15:05 -05:00
( aif ( ein:notebooklist-get-buffer url-or-port )
( buffer-local-value 'ein:%notebooklist% it ) )
2020-02-01 20:16:29 -05:00
nil ) )
2020-01-12 23:01:26 -05:00
( ein:log 'verbose " ein:kernel-delete-session: no sessions found for %s " path )
( when callback
( funcall callback kernel ) ) ) )
2018-10-30 14:17:29 -04:00
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-delete-session--error ( session-id callback
&key response error-thrown
&allow-other-keys )
2018-11-09 18:27:23 -06:00
( ein:log 'error " ein:kernel-delete-session--error %s: ERROR %s DATA %s "
2018-10-30 14:17:29 -04:00
session-id ( car error-thrown ) ( cdr error-thrown ) ) )
2020-01-19 09:03:51 -05:00
( cl-defun ein:kernel-delete-session--success ( session-id nblist callback
2019-11-13 17:46:50 -05:00
&key data symbol-status response
&allow-other-keys )
2020-01-19 09:03:51 -05:00
( ein:log 'verbose " ein:kernel-delete-session--success: %s deleted " session-id )
2020-01-19 09:15:05 -05:00
( when nblist
( ein:notebooklist-reload nblist ) ) )
2018-10-30 14:17:29 -04:00
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-delete-session--complete ( kernel session-id callback
&key data response
&allow-other-keys
&aux ( resp-string ( format " STATUS: %s DATA: %s " ( request-response-status-code response ) data ) ) )
2020-02-01 20:16:29 -05:00
( ein:log 'verbose " ein:kernel-delete-session--complete %s " resp-string )
2020-01-12 23:01:26 -05:00
( when kernel
( ein:kernel-disconnect kernel ) )
2018-11-05 10:16:40 -05:00
( when callback ( funcall callback kernel ) ) )
2012-05-22 13:34:38 +02:00
2018-10-30 14:17:29 -04:00
;; Reply handlers.
2012-05-22 13:34:38 +02:00
( defun ein:kernel-get-callbacks-for-msg ( kernel msg-id )
2012-05-23 01:13:10 +02:00
( gethash msg-id ( ein:$kernel-msg-callbacks kernel ) ) )
2012-05-22 13:34:38 +02:00
( defun ein:kernel-set-callbacks-for-msg ( kernel msg-id callbacks )
2020-01-11 16:03:54 -05:00
" Set up promise for MSG-ID. "
2012-05-22 13:34:38 +02:00
( puthash msg-id callbacks ( ein:$kernel-msg-callbacks kernel ) ) )
2015-03-31 16:55:14 -05:00
( defun ein:kernel--handle-stdin-reply ( kernel packet )
( setf ( ein:$kernel-stdin-activep kernel ) t )
2020-01-11 16:03:54 -05:00
( cl-destructuring-bind
2015-03-31 16:55:14 -05:00
( &key header parent_header metadata content &allow-other-keys )
( ein:json-read-from-string packet )
( let ( ( msg-type ( plist-get header :msg_type ) )
( msg-id ( plist-get header :msg_id ) )
( password ( plist-get content :password ) ) )
2020-01-11 16:03:54 -05:00
( ein:log 'debug " ein:kernel--handle-stdin-reply: msg_type=%s msg_id=%s "
2018-10-27 17:52:17 -04:00
msg-type msg-id )
2015-03-31 16:55:14 -05:00
( cond ( ( string-equal msg-type " input_request " )
2015-03-31 17:26:00 -05:00
( if ( not ( eql password :json-false ) )
2015-03-31 16:55:14 -05:00
( let* ( ( passwd ( read-passwd ( plist-get content :prompt ) ) )
( content ( list :value passwd ) )
( msg ( ein:kernel--get-msg kernel " input_reply " content ) ) )
( ein:websocket-send-stdin-channel kernel msg )
2015-03-31 17:26:00 -05:00
( setf ( ein:$kernel-stdin-activep kernel ) nil ) )
2016-11-29 22:06:03 -06:00
( cond ( ( string-match " ipdb> " ( plist-get content :prompt ) ) ( ein:run-ipdb-session kernel " ipdb> " ) )
2018-03-05 15:56:16 -06:00
( ( string-match " (Pdb) " ( plist-get content :prompt ) ) ( ein:run-ipdb-session kernel " (Pdb) " ) )
( t ( let* ( ( in ( read-string ( plist-get content :prompt ) ) )
( content ( list :value in ) )
( msg ( ein:kernel--get-msg kernel " input_reply " content ) ) )
( ein:websocket-send-stdin-channel kernel msg )
( setf ( ein:$kernel-stdin-activep kernel ) nil ) ) ) ) ) ) ) ) ) )
2015-03-31 16:55:14 -05:00
2012-06-04 16:18:17 +02:00
( defun ein:kernel--handle-payload ( kernel callbacks payload )
2020-01-02 20:09:42 -05:00
( cl-loop with events = ( ein:$kernel-events kernel )
2012-05-22 13:34:38 +02:00
for p in payload
2020-01-11 16:03:54 -05:00
for text = ( or ( plist-get p :text ) ( plist-get ( plist-get p :data ) :text/plain ) )
2012-05-22 13:34:38 +02:00
for source = ( plist-get p :source )
2013-02-10 05:00:40 +01:00
if ( member source ' ( " IPython.kernel.zmq.page.page "
2014-05-23 15:21:16 -04:00
" IPython.zmq.page.page "
" page " ) )
2012-05-23 23:32:51 +02:00
do ( when ( not ( equal ( ein:trim text ) " " ) )
( ein:events-trigger
2012-06-06 21:03:54 +02:00
events 'open_with_text.Pager ( list :text text ) ) )
2012-05-22 13:34:38 +02:00
else if
2013-02-10 05:00:40 +01:00
( member
source
' ( " IPython.kernel.zmq.zmqshell.ZMQInteractiveShell.set_next_input "
2016-12-18 23:09:07 -06:00
" IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input "
" set_next_input " ) )
2012-06-04 21:12:06 +02:00
do ( let ( ( cb ( plist-get callbacks :set_next_input ) ) )
( when cb ( ein:funcall-packed cb text ) ) ) ) )
2012-05-22 13:34:38 +02:00
2020-01-11 16:03:54 -05:00
( defun ein:kernel--handle-shell-reply ( kernel packet )
( cl-destructuring-bind
( &key header content metadata parent_header &allow-other-keys )
( ein:json-read-from-string packet )
( let* ( ( msg-type ( plist-get header :msg_type ) )
( msg-id ( plist-get parent_header :msg_id ) )
( callbacks ( ein:kernel-get-callbacks-for-msg kernel msg-id ) ) )
( ein:log 'debug " ein:kernel--handle-shell-reply: msg_type=%s msg_id=%s "
msg-type msg-id )
( run-hook-with-args 'ein:on-shell-reply-functions msg-type header content metadata )
( aif ( plist-get callbacks ( intern-soft ( format " :%s " msg-type ) ) )
( ein:funcall-packed it content metadata )
( ein:log 'info " ein:kernel--handle-shell-reply: No :%s callback for msg_id=%s "
msg-type msg-id ) )
( aif ( plist-get content :payload )
( ein:kernel--handle-payload kernel callbacks it ) )
( let ( ( events ( ein:$kernel-events kernel ) ) )
( ein:case-equal msg-type
( ( " execute_reply " )
( aif ( plist-get content :execution_count )
( ein:events-trigger events 'execution_count.Kernel it ) ) ) ) ) ) ) )
2012-05-22 13:34:38 +02:00
( defun ein:kernel--handle-iopub-reply ( kernel packet )
2015-04-10 13:59:36 -05:00
( if ( ein:$kernel-stdin-activep kernel )
( ein:ipdb--handle-iopub-reply kernel packet )
2020-01-11 16:03:54 -05:00
( cl-destructuring-bind
2015-04-10 13:59:36 -05:00
( &key content metadata parent_header header &allow-other-keys )
( ein:json-read-from-string packet )
( let* ( ( msg-type ( plist-get header :msg_type ) )
2020-01-23 10:33:05 -05:00
( msg-id ( plist-get header :msg_id ) )
( parent-id ( plist-get parent_header :msg_id ) )
( callbacks ( ein:kernel-get-callbacks-for-msg kernel parent-id ) )
2015-04-10 13:59:36 -05:00
( events ( ein:$kernel-events kernel ) ) )
2020-01-23 10:33:05 -05:00
( ein:log 'debug
" ein:kernel--handle-iopub-reply: msg_type=%s msg_id=%s parent_id=%s "
msg-type msg-id parent-id )
2020-01-11 16:03:54 -05:00
( ein:case-equal msg-type
( ( " stream " " display_data " " pyout " " pyerr " " error " " execute_result " )
2020-01-12 23:01:26 -05:00
( aif ( plist-get callbacks :output ) ;; ein:cell--handle-output
2020-01-11 16:03:54 -05:00
( ein:funcall-packed it msg-type content metadata )
( ein:log 'warn ( concat " ein:kernel--handle-iopub-reply: "
2020-01-23 10:33:05 -05:00
" No :output callback for parent_id=%s " )
parent-id ) ) )
2020-01-11 16:03:54 -05:00
( ( " status " )
( ein:case-equal ( plist-get content :execution_state )
( ( " busy " )
( ein:events-trigger events 'status_busy.Kernel ) )
( ( " idle " )
( ein:events-trigger events 'status_idle.Kernel ) )
( ( " dead " )
( ein:kernel-disconnect kernel ) ) ) )
( ( " data_pub " )
( ein:log 'verbose " ein:kernel--handle-iopub-reply: data_pub %S " packet ) )
( ( " clear_output " )
( aif ( plist-get callbacks :clear_output )
( ein:funcall-packed it content metadata )
( ein:log 'info ( concat " ein:kernel--handle-iopub-reply: "
2020-01-23 10:33:05 -05:00
" No :clear_output callback for parent_id=%s " )
parent-id ) ) ) ) ) ) ) )
2012-05-27 05:19:17 +02:00
2012-08-04 00:26:30 +02:00
( defun ein:kernel-filename-to-python ( kernel filename )
" See: `ein:filename-to-python' . "
( ein:filename-to-python ( ein:$kernel-url-or-port kernel ) filename ) )
( defun ein:kernel-filename-from-python ( kernel filename )
" See: `ein:filename-from-python' . "
( ein:filename-from-python ( ein:$kernel-url-or-port kernel ) filename ) )
2012-07-08 13:45:30 +02:00
( defun ein:kernel-construct-defstring ( content )
" Construct call signature from CONTENT of ` ` :object_info_reply ` ` .
2012-08-19 21:36:35 +02:00
Used in ` ein:pytools-finish-tooltip ', etc. "
2012-07-08 13:45:30 +02:00
( or ( plist-get content :call_def )
( plist-get content :init_definition )
( plist-get content :definition ) ) )
( defun ein:kernel-construct-help-string ( content )
" Construct help string from CONTENT of ` ` :object_info_reply ` ` .
2012-08-19 21:36:35 +02:00
Used in ` ein:pytools-finish-tooltip ', etc. "
2012-07-25 18:51:02 +02:00
( let* ( ( defstring ( ein:aand
( ein:kernel-construct-defstring content )
( ansi-color-apply it )
( ein:string-fill-paragraph it ) ) )
2012-07-25 18:55:55 +02:00
( docstring ( ein:aand
( or ( plist-get content :call_docstring )
( plist-get content :init_docstring )
( plist-get content :docstring )
;; "<empty docstring>"
)
( ansi-color-apply it ) ) )
2019-01-16 18:23:39 -05:00
( help ( ein:aand
( delete nil ( list defstring docstring ) )
( ein:join-str " \n " it ) ) ) )
2012-07-08 13:45:30 +02:00
help ) )
2012-05-27 05:49:41 +02:00
( defun ein:kernel-request-stream ( kernel code func &optional args )
2012-08-04 00:04:16 +02:00
" Run lisp callback FUNC with the output stream returned by Python CODE.
The first argument to the lisp function FUNC is the stream output
as a string and the rest of the argument is the optional ARGS. "
2012-05-27 05:49:41 +02:00
( ein:kernel-execute
kernel
code
2012-08-08 21:30:14 +02:00
( list :output ( cons ( lambda ( packed msg-type content -metadata-not-used- )
2012-05-27 05:49:41 +02:00
( let ( ( func ( car packed ) )
( args ( cdr packed ) ) )
( when ( equal msg-type " stream " )
2019-11-24 00:17:52 -05:00
( aif ( plist-get content :text )
2012-05-27 05:49:41 +02:00
( apply func it args ) ) ) ) )
( cons func args ) ) ) ) )
2019-11-13 17:46:50 -05:00
( cl-defun ein:kernel-history-request-synchronously
2012-09-05 22:46:34 +02:00
( kernel &rest args &key ( timeout 0.5 ) ( tick-time 0.05 ) &allow-other-keys )
" Send the history request and wait TIMEOUT seconds.
Return a list ( CONTENT METADATA ) .
This function checks the request reply every TICK-TIME seconds.
See ` ein:kernel-history-request ' for other usable options. "
;; As `result' and `finished' are set in callback, make sure they
;; won't be trapped in other let-bindings.
( lexical-let ( result finished )
( apply
#' ein:kernel-history-request
kernel
( list :history_reply
( cons ( lambda ( -ignore- content metadata )
( setq result ( list content metadata ) )
( setq finished t ) )
nil ) )
args )
2020-01-02 20:09:42 -05:00
( cl-loop repeat ( floor ( / timeout tick-time ) )
2012-09-05 22:46:34 +02:00
do ( sit-for tick-time )
when finished
return t
finally ( error " Timeout " ) )
result ) )
( defun ein:kernel-history-search-synchronously ( kernel pattern &rest args )
" Search execution history in KERNEL using PATTERN.
Return matched history as a list of strings.
See ` ein:kernel-history-request-synchronously ' and
` ein:kernel-history-request ' for usable options. "
( let ( ( reply
( apply #' ein:kernel-history-request-synchronously
kernel
:hist-access-type " search "
:pattern pattern
args ) ) )
( mapcar #' caddr ( plist-get ( car reply ) :history ) ) ) )
2012-05-07 14:41:15 +02:00
( provide 'ein-kernel )
;;; ein-kernel.el ends here