Convert jupyter-kernel into a method

* jupyter-kernel-process.el
* jupyter-server-kernel.el (jupyter-kernel): New method.

* jupyter-kernel.el (jupyter-kernel): Convert.  Load files that handle
  keywords and re-dispatch when ARGS does not have any connection
  info.  Document behavior.

* test/test-helper.el (jupyter-test-with-kernel-client)
* jupyter-server.el
(jupyter-connect-server-repl)
(jupyter-server-start-new-kernel): Use `jupyter-kernel`.
This commit is contained in:
Nathaniel Nicandro 2020-04-27 20:49:43 -05:00
parent a33231b4d3
commit f2c9d5fb25
5 changed files with 83 additions and 74 deletions

View file

@ -48,36 +48,35 @@
(cl-call-next-method))))
(defun jupyter-kernel-process (&rest args)
"Return a representation of a kernel process.
Return a `jupyter-kernel-process' with ARGS being the slot values
used for initialization."
(let ((kernel (apply #'make-jupyter-kernel-process args))
(session (pcase (plist-get args :session)
((and (pred stringp) (pred file-exists-p) `,conn-file)
(let ((conn-info (jupyter-read-connection conn-file)))
(jupyter-session
:conn-info conn-info
:key (plist-get conn-info :key))))
((and (pred jupyter-session-p) `,session)
session)
(_
(prog1 (jupyter-session-with-random-ports)
;; This is here for stability when running
;; the tests. Sometimes the kernel ports are
;; not set up fast enough due to the hack
;; done in
;; `jupyter-session-with-random-ports'. The
;; effect seems to be messages that are sent
;; but never received by the kernel.
(sit-for 0.2)))))
(spec (pcase (plist-get args :spec)
((and (pred stringp) `,name)
(or (jupyter-guess-kernelspec name)
(error "No kernelspec matching name (%s)" name)))
(`,spec spec))))
(setf (jupyter-kernel-session kernel) session)
(setf (jupyter-kernel-spec kernel) spec)
kernel))
"Return a `jupyter-kernel-process' initialized with ARGS."
(apply #'make-jupyter-kernel-process args))
(cl-defmethod jupyter-kernel :extra "process" (&rest args)
"Return a representation of a kernel based on an Emacs process.
If ARGS contains a :spec key, return a `jupyter-kernel-process'
initialized using ARGS. If the value is the name of a
kernelspec, the returned kernel's spec slot will be set to the
corresponding `jupyter-kernelspec'. The session of the returned
kernel will be initialized with the return value of
`jupyter-session-with-random-ports'.
Call the next method if ARGS does not contain :spec."
(let ((spec (plist-get args :spec)))
(if (not spec) (cl-call-next-method)
(when (stringp spec)
(plist-put args :spec
(or (jupyter-guess-kernelspec spec)
(error "No kernelspec matching name (%s)" spec))))
(apply #'jupyter-kernel-process
:session (prog1 (jupyter-session-with-random-ports)
;; This is here for stability when running the
;; tests. Sometimes the kernel ports are not
;; set up fast enough due to the hack done in
;; `jupyter-session-with-random-ports'. The
;; effect seems to be messages that are sent but
;; never received by the kernel.
(sit-for 0.2))
args))))
;;; Client connection

View file

@ -59,20 +59,34 @@
"Return non-nil if KERNEL has been launched."
(and (jupyter-kernel-session kernel) t))
(defun jupyter-kernel (&rest args)
(cl-defgeneric jupyter-kernel (&rest args)
"Return a kernel constructed from ARGS.
ARGS are keyword arguments."
(if (plist-get args :conn-info)
(make-jupyter-kernel
:session (let ((conn-info
(if (stringp (plist-get args :conn-info))
(jupyter-read-connection
(plist-get args :conn-file))
(plist-get args :conn-info))))
(jupyter-session
:conn-info conn-info
:key (plist-get conn-info :key))))
(error "Implement")))
ARGS are keyword arguments used to initialize the returned
kernel.
The default implementation will return a `jupyter-kernel' with a
session initialized from the value of :conn-info in ARGS, either
the name of a connection file to read or itself a connection
property list (see `jupyter-read-connection'). A client can
connect to the returned kernel using `jupyter-client'.
This method can be extended with extra primary methods for the
purposes of handling different forms of ARGS that do not just
need the default behavior."
(let ((conn-info (plist-get args :conn-info)))
(cond
(conn-info
(when (stringp conn-info)
(setq conn-info (jupyter-read-connection conn-info)))
(apply #'make-jupyter-kernel
:session (jupyter-session
:conn-info conn-info
:key (plist-get conn-info :key))
(cl-loop
for (k v) on args by #'cddr
unless (eq k :conn-info) collect k and collect v)))
(t
(cl-call-next-method)))))
;;; Kernel management

View file

@ -156,25 +156,29 @@ The kernelspecs are returned in the same form as returned by
(cl-call-next-method))))
(defun jupyter-server-kernel (&rest args)
"Return a representation of a kernel on a Jupyter server.
ARGS is a property list used to initialize the returned
`jupyter-server-kernel'. The following keys of ARGS are handled
specially:
- If :spec is present and it is the name of a kernelspec, then
the SPEC of the returned kernel will be the one associated
with that name on the server."
(cl-assert (jupyter-server-p (plist-get args :server)))
(when (stringp (plist-get args :spec))
(let ((server (plist-get args :server))
(name (plist-get args :spec)))
(plist-put args :spec
(or (jupyter-guess-kernelspec
name (jupyter-server-kernelspecs server))
(error "No kernelspec matching %s @ %s" name
(oref server url))))))
"Return a `jupyter-server-kernel' initialized with ARGS."
(apply #'make-jupyter-server-kernel args))
(cl-defmethod jupyter-kernel :extra "server" (&rest args)
"Return a representation of a kernel on a Jupyter server.
If ARGS contains a :server key, return a `jupyter-server-kernel'
initialized using ARGS. If ARGS contains a :spec key whose value
is the name of a kernelspec, the returned kernel's spec slot will
be set to the corresponding `jupyter-kernelspec'.
Call the next method if ARGS does not contain :server."
(let ((server (plist-get args :server)))
(if (not server) (cl-call-next-method)
(cl-assert (object-of-class-p server 'jupyter-server))
(let ((spec (plist-get args :spec)))
(when (stringp spec)
(plist-put args :spec
(or (jupyter-guess-kernelspec
spec (jupyter-server-kernelspecs server))
(error "No kernelspec matching %s @ %s" spec
(oref server url))))))
(apply #'jupyter-server-kernel args))))
;;;; Kernel management
(cl-defmethod jupyter-launch ((kernel jupyter-server-kernel))
@ -252,6 +256,8 @@ using its SPEC."
(jupyter-server--event-handler-fn handler)
event))))))))))
;;; Client connection
(defun jupyter-server--connect-channels (server id)
(jupyter-send (oref server ioloop) 'connect-channels id)
(unless (jupyter-server-kernel-connected-p server id)

View file

@ -329,10 +329,7 @@ the kernel whose class is CLIENT-CLASS. Note that the clients
see jupyter-make-client."
(require 'jupyter-server-kernel)
(or client-class (setq client-class 'jupyter-kernel-client))
(let* ((specs (jupyter-server-kernelspecs server))
(kernel (jupyter-server-kernel
:server server
:spec (jupyter-guess-kernelspec kernel-name specs)))
(let* ((kernel (jupyter-kernel :server server :spec kernel-name))
(manager (jupyter-server-kernel-manager :kernel kernel)))
(let ((client (jupyter-make-client manager client-class)))
(list manager client))))
@ -386,17 +383,10 @@ the same meaning as in `jupyter-connect-repl'."
(y-or-n-p "Name REPL? "))
(read-string "REPL Name: "))
t nil t)))
(require 'jupyter-server-kernel)
(or client-class (setq client-class 'jupyter-repl-client))
(jupyter-error-if-not-client-class-p client-class 'jupyter-repl-client)
(let* ((manager
;; TODO: Move to making `jupyter-server-kernel' and the
;; other one return singleton kernels?
(or (jupyter-server-find-kernel server kernel-id)
(jupyter-server-kernel-manager
:kernel (jupyter-server-kernel
:id kernel-id
:server server))))
(let* ((manager (jupyter-server-kernel-manager
:kernel (jupyter-kernel :server server :id kernel-id)))
(client (jupyter-make-client manager client-class)))
(jupyter-bootstrap-repl client repl-name associate-buffer display)))

View file

@ -218,7 +218,7 @@ This only starts a single global client unless the variable
`(jupyter-test-with-client-cache
(lambda (name) (jupyter-make-client
(jupyter-kernel-manager
:kernel (jupyter-kernel-process :spec name))
:kernel (jupyter-kernel :spec name))
'jupyter-kernel-client))
jupyter-test-global-clients ,kernel ,client
(unwind-protect