mirror of
https://github.com/vale981/emacs-jupyter
synced 2025-03-06 16:01:37 -05:00
Split ZMQ related code from jupyter-server.el
In the following, accept means moved from `jupyter-server.el` and move means moved to `jupyter-server-ioloop-comm.el`. A label like [class] means the references that appear before the label are actually methods of class. * jupyter-server.el: Move all `jupyter-event-handler` methods. (jupyter-server-kernel-comm): Add `ws` slot. The class has been repurposed for communication using a websocket in the current Emacs instance. The old behavior has been assigned to the `jupyter-server-ioloop-kernel-comm` class. (jupyter-comm-id, jupyter-server-name-client-kernel) (jupyter-channel-alive-p, jupyter-channels-running-p) (jupyter-server-kernel-manager, jupyter-current-server): Rename `jupyter-server-kernel-comm` to `jupyter-server-abstract-kcomm`. (jupyter-server-kernel-connected-p): Redefine as a generic function. Move the method body. (jupyter-comm-start, jupyter-connect-client) (jupyter-disconnect-client) [jupyter-server]: Move. (jupyter-comm-start, jupyter-comm-stop) (jupyter-send, jupyter-comm-alive-p) [jupyter-server-kernel-comm]: Move. * jupyter-server-ioloop-comm.el: Accept moved `jupyter-event-handler` methods. Rename `jupyter-server` to `jupyter-server-ioloop-comm`. Rename `jupyter-server-kernel-comm` to `jupyter-server-ioloop-kernel-comm`. (jupyter-server-kernel-connected-p) (jupyter-comm-start, jupyter-connect-client) (jupyter-disconnect-client) [jupyter-server]: Accept. (jupyter-comm-start, jupyter-comm-stop) (jupyter-comm-alive-p, jupyter-send) [jupyter-server-kernel-comm]: Accept.
This commit is contained in:
parent
3e7bd859c8
commit
ce5cd70838
2 changed files with 119 additions and 114 deletions
|
@ -40,6 +40,114 @@
|
||||||
(defclass jupyter-server-ioloop-kernel-comm (jupyter-server-abstract-kcomm)
|
(defclass jupyter-server-ioloop-kernel-comm (jupyter-server-abstract-kcomm)
|
||||||
())
|
())
|
||||||
|
|
||||||
|
;;; `jupyter-ioloop-comm' event handlers
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-event-handler ((comm jupyter-server-ioloop-comm)
|
||||||
|
(event (head disconnect-channels)))
|
||||||
|
(let ((kernel-id (cadr event)))
|
||||||
|
(with-slots (ioloop) comm
|
||||||
|
(cl-callf2 remove kernel-id
|
||||||
|
(process-get (oref ioloop process) :kernel-ids)))))
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-event-handler ((comm jupyter-server-ioloop-comm)
|
||||||
|
(event (head connect-channels)))
|
||||||
|
(let ((kernel-id (cadr event)))
|
||||||
|
(with-slots (ioloop) comm
|
||||||
|
(cl-callf append (process-get (oref ioloop process) :kernel-ids)
|
||||||
|
(list kernel-id)))))
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-event-handler ((comm jupyter-server-ioloop-comm) event)
|
||||||
|
"Send EVENT to all clients connected to COMM.
|
||||||
|
Each client must have a KERNEL slot which, in turn, must have an
|
||||||
|
ID slot. The second element of EVENT is expected to be a kernel
|
||||||
|
ID. Send EVENT, with the kernel ID excluded, to a client whose
|
||||||
|
kernel has a matching ID."
|
||||||
|
(let ((kernel-id (cadr event)))
|
||||||
|
(setq event (cons (car event) (cddr event)))
|
||||||
|
(jupyter-comm-handler-loop comm client
|
||||||
|
(when (equal kernel-id (oref (oref client kernel) id))
|
||||||
|
;; TODO: Since the event handlers of CLIENT will eventually call the
|
||||||
|
;; `jupyter-handle-message' of a `jupyter-kernel-client' we really
|
||||||
|
;; don't need to do any filtering based off of a `jupyter-session-id',
|
||||||
|
;; but maybe should? The `jupyter-handle-message' method will only
|
||||||
|
;; handle messages that have a parent ID of a previous request so there
|
||||||
|
;; already is filtering at the kernel client level.
|
||||||
|
(jupyter-event-handler client event)))))
|
||||||
|
|
||||||
|
;;; `jupyter-ioloop-comm' methods
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-comm-start ((comm jupyter-server-ioloop-comm))
|
||||||
|
(unless (and (slot-boundp comm 'ioloop)
|
||||||
|
(jupyter-ioloop-alive-p (oref comm ioloop)))
|
||||||
|
;; TODO: Is a write to the cookie file and then a read of the cookie file
|
||||||
|
;; whenever connecting a websocket in a subprocess good enough? If, e.g.
|
||||||
|
;; the notebook is restarted and it clears the login information, there are
|
||||||
|
;; sometimes error due to `jupyter-api-request' trying to ask for login
|
||||||
|
;; information which look like "wrong type argument listp, [http://...]".
|
||||||
|
;; They don't seem to happens with the changes mentioned, but is it enough?
|
||||||
|
(url-cookie-write-file)
|
||||||
|
(oset comm ioloop (jupyter-server-ioloop
|
||||||
|
:url (oref comm url)
|
||||||
|
:ws-url (oref comm ws-url)
|
||||||
|
:ws-headers (jupyter-api-auth-headers comm)))
|
||||||
|
(cl-call-next-method)))
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-comm-add-handler ((comm jupyter-server-ioloop-comm)
|
||||||
|
(kcomm jupyter-server-ioloop-kernel-comm))
|
||||||
|
(cl-call-next-method)
|
||||||
|
(with-slots (id) (oref kcomm kernel)
|
||||||
|
(unless (jupyter-server-kernel-connected-p comm id)
|
||||||
|
(jupyter-server--connect-channels comm id))))
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-comm-remove-handler ((comm jupyter-server-ioloop-comm)
|
||||||
|
(kcomm jupyter-server-ioloop-kernel-comm))
|
||||||
|
(with-slots (id) (oref kcomm kernel)
|
||||||
|
(when (jupyter-server-kernel-connected-p comm id)
|
||||||
|
(jupyter-send comm 'disconnect-channels id)
|
||||||
|
(unless (jupyter-ioloop-wait-until (oref comm ioloop)
|
||||||
|
'disconnect-channels #'identity)
|
||||||
|
(error "Timeout when disconnecting websocket for kernel id %s" id))))
|
||||||
|
(cl-call-next-method))
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-server-kernel-connected-p ((comm jupyter-server-ioloop-comm) id)
|
||||||
|
"Return non-nil if COMM has a WebSocket connection to a kernel with ID."
|
||||||
|
(and (jupyter-comm-alive-p comm)
|
||||||
|
(member id (process-get (oref (oref comm ioloop) process) :kernel-ids))))
|
||||||
|
|
||||||
|
;; `jupyter-server-ioloop-kcomm'
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-comm-start ((comm jupyter-server-ioloop-kernel-comm) &rest _ignore)
|
||||||
|
"Register COMM to receive server events.
|
||||||
|
If SERVER receives events that have the same kernel ID as the
|
||||||
|
kernel associated with COMM, then COMM's `jupyter-event-handler'
|
||||||
|
will receive those events."
|
||||||
|
(with-slots (server) (oref comm kernel)
|
||||||
|
(jupyter-comm-add-handler server comm)))
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-comm-stop ((comm jupyter-server-ioloop-kernel-comm) &rest _ignore)
|
||||||
|
"Disconnect COMM from receiving server events."
|
||||||
|
(jupyter-comm-remove-handler (oref (oref comm kernel) server) comm))
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-comm-alive-p ((comm jupyter-server-ioloop-kernel-comm))
|
||||||
|
"Return non-nil if COMM can receive server events for its associated kernel."
|
||||||
|
(with-slots (kernel) comm
|
||||||
|
(and (jupyter-server-kernel-connected-p
|
||||||
|
(oref kernel server)
|
||||||
|
(oref kernel id))
|
||||||
|
(catch 'member
|
||||||
|
(jupyter-comm-handler-loop (oref kernel server) client
|
||||||
|
(when (eq client comm)
|
||||||
|
(throw 'member t)))))))
|
||||||
|
|
||||||
|
(cl-defmethod jupyter-send ((comm jupyter-server-ioloop-kernel-comm) event-type &rest event)
|
||||||
|
"Use COMM to send an EVENT to the server with type, EVENT-TYPE.
|
||||||
|
SERVER will direct EVENT to the right kernel based on the kernel
|
||||||
|
ID of the kernel associated with COMM."
|
||||||
|
(with-slots (kernel) comm
|
||||||
|
(unless (jupyter-comm-alive-p comm)
|
||||||
|
(jupyter-comm-start comm))
|
||||||
|
(apply #'jupyter-send (oref kernel server) event-type (oref kernel id) event)))
|
||||||
|
|
||||||
(provide 'jupyter-server-ioloop-comm)
|
(provide 'jupyter-server-ioloop-comm)
|
||||||
|
|
||||||
;;; jupyter-server-ioloop-comm.el ends here
|
;;; jupyter-server-ioloop-comm.el ends here
|
||||||
|
|
|
@ -83,6 +83,8 @@
|
||||||
(declare-function jupyter-tramp-file-name-p "jupyter-tramp" (filename))
|
(declare-function jupyter-tramp-file-name-p "jupyter-tramp" (filename))
|
||||||
(declare-function jupyter-tramp-server-from-file-name "jupyter-tramp" (filename))
|
(declare-function jupyter-tramp-server-from-file-name "jupyter-tramp" (filename))
|
||||||
(declare-function jupyter-tramp-file-name-from-url "jupyter-tramp" (url))
|
(declare-function jupyter-tramp-file-name-from-url "jupyter-tramp" (url))
|
||||||
|
(declare-function jupyter-server-ioloop-kernel-comm "jupyter-server-ioloop-comm")
|
||||||
|
(declare-function jupyter-server-ioloop-comm "jupyter-server-ioloop-comm")
|
||||||
|
|
||||||
(defgroup jupyter-server nil
|
(defgroup jupyter-server nil
|
||||||
"Support for the Jupyter kernel gateway"
|
"Support for the Jupyter kernel gateway"
|
||||||
|
@ -169,9 +171,9 @@ Access should be done through `jupyter-available-kernelspecs'.")))
|
||||||
:abstract t)
|
:abstract t)
|
||||||
|
|
||||||
(defclass jupyter-server-kernel-comm (jupyter-server-abstract-kcomm)
|
(defclass jupyter-server-kernel-comm (jupyter-server-abstract-kcomm)
|
||||||
())
|
((ws :type websocket)))
|
||||||
|
|
||||||
(cl-defmethod jupyter-comm-id ((comm jupyter-server-kernel-comm))
|
(cl-defmethod jupyter-comm-id ((comm jupyter-server-abstract-kcomm))
|
||||||
(let* ((kernel (oref comm kernel))
|
(let* ((kernel (oref comm kernel))
|
||||||
(id (oref kernel id)))
|
(id (oref kernel id)))
|
||||||
(or (jupyter-server-kernel-name (oref kernel server) id)
|
(or (jupyter-server-kernel-name (oref kernel server) id)
|
||||||
|
@ -250,7 +252,7 @@ CLIENT must be communicating with a `jupyter-server-kernel', the
|
||||||
ID of the kernel will be associated with NAME, see
|
ID of the kernel will be associated with NAME, see
|
||||||
`jupyter-server-kernel-names'."
|
`jupyter-server-kernel-names'."
|
||||||
(cl-check-type client jupyter-kernel-client)
|
(cl-check-type client jupyter-kernel-client)
|
||||||
(cl-check-type (oref client kcomm) jupyter-server-kernel-comm)
|
(cl-check-type (oref client kcomm) jupyter-server-abstract-kcomm)
|
||||||
(let* ((kernel (thread-first client
|
(let* ((kernel (thread-first client
|
||||||
(oref kcomm)
|
(oref kcomm)
|
||||||
(oref kernel)))
|
(oref kernel)))
|
||||||
|
@ -259,42 +261,10 @@ ID of the kernel will be associated with NAME, see
|
||||||
|
|
||||||
;;; Plumbing
|
;;; Plumbing
|
||||||
|
|
||||||
;;;; `jupyter-server' events
|
|
||||||
|
|
||||||
(cl-defmethod jupyter-event-handler ((comm jupyter-server)
|
|
||||||
(event (head disconnect-channels)))
|
|
||||||
(let ((kernel-id (cadr event)))
|
|
||||||
(with-slots (ioloop) comm
|
|
||||||
(cl-callf2 remove kernel-id
|
|
||||||
(process-get (oref ioloop process) :kernel-ids)))))
|
|
||||||
|
|
||||||
(cl-defmethod jupyter-event-handler ((comm jupyter-server)
|
|
||||||
(event (head connect-channels)))
|
|
||||||
(let ((kernel-id (cadr event)))
|
|
||||||
(with-slots (ioloop) comm
|
|
||||||
(cl-callf append (process-get (oref ioloop process) :kernel-ids)
|
|
||||||
(list kernel-id)))))
|
|
||||||
|
|
||||||
(cl-defmethod jupyter-event-handler ((comm jupyter-server) event)
|
|
||||||
"Send EVENT to all clients connected to COMM.
|
|
||||||
Each client must have a KERNEL slot which, in turn, must have an
|
|
||||||
ID slot. The second element of EVENT is expected to be a kernel
|
|
||||||
ID. Send EVENT, with the kernel ID excluded, to a client whose
|
|
||||||
kernel has a matching ID."
|
|
||||||
(let ((kernel-id (cadr event)))
|
|
||||||
(setq event (cons (car event) (cddr event)))
|
|
||||||
(jupyter-comm-handler-loop comm client
|
|
||||||
(when (equal kernel-id (oref (oref client kernel) id))
|
|
||||||
;; TODO: Since the event handlers of CLIENT will eventually call the
|
|
||||||
;; `jupyter-handle-message' of a `jupyter-kernel-client' we really
|
|
||||||
;; don't need to do any filtering based off of a `jupyter-session-id',
|
|
||||||
;; but maybe should? The `jupyter-handle-message' method will only
|
|
||||||
;; handle messages that have a parent ID of a previous request so there
|
|
||||||
;; already is filtering at the kernel client level.
|
|
||||||
(jupyter-event-handler client event)))))
|
|
||||||
|
|
||||||
;;;; `jupyter-server' methods
|
;;;; `jupyter-server' methods
|
||||||
|
|
||||||
|
(cl-defgeneric jupyter-server-kernel-connected-p ((client jupyter-server) id)
|
||||||
|
"Return non-nil if CLIENT can communicate with a kernel that has ID.")
|
||||||
(defun jupyter-server--connect-channels (server id)
|
(defun jupyter-server--connect-channels (server id)
|
||||||
(jupyter-send server 'connect-channels id)
|
(jupyter-send server 'connect-channels id)
|
||||||
(jupyter-with-timeout
|
(jupyter-with-timeout
|
||||||
|
@ -326,44 +296,6 @@ with default `jupyter-api-authentication-method'"))
|
||||||
(prog1 (cl-call-next-method)
|
(prog1 (cl-call-next-method)
|
||||||
(jupyter-server--refresh-comm server)))))
|
(jupyter-server--refresh-comm server)))))
|
||||||
|
|
||||||
(cl-defmethod jupyter-comm-start ((comm jupyter-server))
|
|
||||||
(unless (and (slot-boundp comm 'ioloop)
|
|
||||||
(jupyter-ioloop-alive-p (oref comm ioloop)))
|
|
||||||
;; TODO: Is a write to the cookie file and then a read of the cookie file
|
|
||||||
;; whenever connecting a websocket in a subprocess good enough? If, e.g.
|
|
||||||
;; the notebook is restarted and it clears the login information, there are
|
|
||||||
;; sometimes error due to `jupyter-api-request' trying to ask for login
|
|
||||||
;; information which look like "wrong type argument listp, [http://...]".
|
|
||||||
;; They don't seem to happens with the changes mentioned, but is it enough?
|
|
||||||
(url-cookie-write-file)
|
|
||||||
(oset comm ioloop (jupyter-server-ioloop
|
|
||||||
:url (oref comm url)
|
|
||||||
:ws-url (oref comm ws-url)
|
|
||||||
:ws-headers (jupyter-api-auth-headers comm)))
|
|
||||||
(cl-call-next-method)))
|
|
||||||
|
|
||||||
(cl-defmethod jupyter-comm-add-handler ((comm jupyter-server)
|
|
||||||
(kcomm jupyter-server-kernel-comm))
|
|
||||||
(cl-call-next-method)
|
|
||||||
(with-slots (id) (oref kcomm kernel)
|
|
||||||
(unless (jupyter-server-kernel-connected-p comm id)
|
|
||||||
(jupyter-server--connect-channels comm id))))
|
|
||||||
|
|
||||||
(cl-defmethod jupyter-comm-remove-handler ((comm jupyter-server)
|
|
||||||
(kcomm jupyter-server-kernel-comm))
|
|
||||||
(with-slots (id) (oref kcomm kernel)
|
|
||||||
(when (jupyter-server-kernel-connected-p comm id)
|
|
||||||
(jupyter-send comm 'disconnect-channels id)
|
|
||||||
(unless (jupyter-ioloop-wait-until (oref comm ioloop)
|
|
||||||
'disconnect-channels #'identity)
|
|
||||||
(error "Timeout when disconnecting websocket for kernel id %s" id))))
|
|
||||||
(cl-call-next-method))
|
|
||||||
|
|
||||||
(cl-defmethod jupyter-server-kernel-connected-p ((comm jupyter-server) id)
|
|
||||||
"Return non-nil if COMM has a WebSocket connection to a kernel with ID."
|
|
||||||
(and (jupyter-comm-alive-p comm)
|
|
||||||
(member id (process-get (oref (oref comm ioloop) process) :kernel-ids))))
|
|
||||||
|
|
||||||
(defun jupyter-server--verify-kernelspec (server spec)
|
(defun jupyter-server--verify-kernelspec (server spec)
|
||||||
(cl-destructuring-bind (name _ . kspec) spec
|
(cl-destructuring-bind (name _ . kspec) spec
|
||||||
(let ((server-spec (assoc name (jupyter-server-kernelspecs server))))
|
(let ((server-spec (assoc name (jupyter-server-kernelspecs server))))
|
||||||
|
@ -400,53 +332,18 @@ The kernelspecs are returned in the same form as returned by
|
||||||
(cons nil (plist-get spec :spec)))))))
|
(cons nil (plist-get spec :spec)))))))
|
||||||
(plist-get (oref server kernelspecs) :kernelspecs))
|
(plist-get (oref server kernelspecs) :kernelspecs))
|
||||||
|
|
||||||
;;;; `jupyter-server-kernel-comm' methods
|
|
||||||
|
|
||||||
(cl-defmethod jupyter-comm-start ((comm jupyter-server-kernel-comm) &rest _ignore)
|
|
||||||
"Register COMM to receive server events.
|
|
||||||
If SERVER receives events that have the same kernel ID as the
|
|
||||||
kernel associated with COMM, then COMM's `jupyter-event-handler'
|
|
||||||
will receive those events."
|
|
||||||
(with-slots (server) (oref comm kernel)
|
|
||||||
(jupyter-comm-start server)
|
|
||||||
(jupyter-comm-add-handler server comm)))
|
|
||||||
|
|
||||||
(cl-defmethod jupyter-comm-stop ((comm jupyter-server-kernel-comm) &rest _ignore)
|
|
||||||
"Disconnect COMM from receiving server events."
|
|
||||||
(jupyter-comm-remove-handler (oref (oref comm kernel) server) comm))
|
|
||||||
|
|
||||||
(cl-defmethod jupyter-send ((comm jupyter-server-kernel-comm) event-type &rest event)
|
|
||||||
"Use COMM to send an EVENT to the server with type, EVENT-TYPE.
|
|
||||||
SERVER will direct EVENT to the right kernel based on the kernel
|
|
||||||
ID of the kernel associated with COMM."
|
|
||||||
(with-slots (kernel) comm
|
|
||||||
(unless (jupyter-comm-alive-p comm)
|
|
||||||
(jupyter-comm-start comm))
|
|
||||||
(apply #'jupyter-send (oref kernel server) event-type (oref kernel id) event)))
|
|
||||||
|
|
||||||
(cl-defmethod jupyter-comm-alive-p ((comm jupyter-server-kernel-comm))
|
|
||||||
"Return non-nil if COMM can receive server events for its associated kernel."
|
|
||||||
(with-slots (kernel) comm
|
|
||||||
(and (jupyter-server-kernel-connected-p
|
|
||||||
(oref kernel server)
|
|
||||||
(oref kernel id))
|
|
||||||
(catch 'member
|
|
||||||
(jupyter-comm-handler-loop (oref kernel server) client
|
|
||||||
(when (eq client comm)
|
|
||||||
(throw 'member t)))))))
|
|
||||||
|
|
||||||
;; TODO: Remove the need for these methods, they are remnants from an older
|
;; TODO: Remove the need for these methods, they are remnants from an older
|
||||||
;; implementation. They will need to be removed from `jupyter-kernel-client'.
|
;; implementation. They will need to be removed from `jupyter-kernel-client'.
|
||||||
(cl-defmethod jupyter-channel-alive-p ((comm jupyter-server-kernel-comm) _channel)
|
(cl-defmethod jupyter-channel-alive-p ((comm jupyter-server-abstract-kcomm) _channel)
|
||||||
(jupyter-comm-alive-p comm))
|
(jupyter-comm-alive-p comm))
|
||||||
|
|
||||||
(cl-defmethod jupyter-channels-running-p ((comm jupyter-server-kernel-comm))
|
(cl-defmethod jupyter-channels-running-p ((comm jupyter-server-abstract-kcomm))
|
||||||
(jupyter-comm-alive-p comm))
|
(jupyter-comm-alive-p comm))
|
||||||
|
|
||||||
;;;; `jupyter-server-kernel-manager'
|
;;;; `jupyter-server-kernel-manager'
|
||||||
|
|
||||||
(defclass jupyter-server-kernel-manager (jupyter-kernel-manager)
|
(defclass jupyter-server-kernel-manager (jupyter-kernel-manager)
|
||||||
((comm :type jupyter-server-kernel-comm)))
|
((comm :type jupyter-server-abstract-kcomm)))
|
||||||
|
|
||||||
(cl-defmethod jupyter-comm-start ((manager jupyter-server-kernel-manager))
|
(cl-defmethod jupyter-comm-start ((manager jupyter-server-kernel-manager))
|
||||||
"Start a websocket connection to MANAGER's kernel.
|
"Start a websocket connection to MANAGER's kernel.
|
||||||
|
@ -604,7 +501,7 @@ a URL."
|
||||||
((and jupyter-current-client
|
((and jupyter-current-client
|
||||||
(object-of-class-p
|
(object-of-class-p
|
||||||
(oref jupyter-current-client kcomm)
|
(oref jupyter-current-client kcomm)
|
||||||
'jupyter-server-kernel-comm)
|
'jupyter-server-abstract-kcomm)
|
||||||
(thread-first jupyter-current-client
|
(thread-first jupyter-current-client
|
||||||
(oref kcomm)
|
(oref kcomm)
|
||||||
(oref kernel)
|
(oref kernel)
|
||||||
|
|
Loading…
Add table
Reference in a new issue