2015-01-31 10:13:49 -06:00
|
|
|
;;; ein-contents-api.el --- Interface to Jupyter's Contents API
|
|
|
|
|
|
|
|
;; Copyright (C) 2015 - John Miller
|
|
|
|
|
|
|
|
;; Authors: Takafumi Arakaki <aka.tkf at gmail.com>
|
|
|
|
;; John M. Miller <millejoh at mac.com>
|
|
|
|
|
|
|
|
;; This file is NOT part of GNU Emacs.
|
|
|
|
|
|
|
|
;; ein-contents-api.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-contents-api.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-notebooklist.el. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
;;;
|
|
|
|
;;; An interface to the Jupyter Contents API as described in
|
|
|
|
;;; https://github.com/ipython/ipython/wiki/IPEP-27%3A-Contents-Service.
|
|
|
|
;;;
|
|
|
|
|
|
|
|
;;
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
(require 'ein-core)
|
|
|
|
|
|
|
|
(defstruct ein:$content
|
|
|
|
"Content returned from the Jupyter notebook server:
|
|
|
|
`ein:$content-url-or-port'
|
|
|
|
URL or port of Jupyter server.
|
|
|
|
|
|
|
|
`ein:$content-name
|
|
|
|
The name/filename of the content. Always equivalent to the last
|
|
|
|
part of the path field
|
|
|
|
|
|
|
|
`ein:$content-path
|
|
|
|
The full file path. It will not start with /, and it will be /-delimited.
|
|
|
|
|
|
|
|
`ein:$content-type
|
|
|
|
One of three values: :directory, :file, :notebook.
|
|
|
|
|
|
|
|
`ein:$content-writable
|
|
|
|
Indicates if requester has permission to modified the requested content.
|
|
|
|
|
|
|
|
`ein:$content-created
|
|
|
|
|
|
|
|
`ein:$content-last-modified
|
|
|
|
|
|
|
|
`ein:$content-mimetype
|
|
|
|
Specify the mime-type of :file content, null otherwise.
|
|
|
|
|
|
|
|
`ein:$content-raw-content
|
|
|
|
Contents of resource as returned by Jupyter. Depending on content-type will hold:
|
|
|
|
:directory : JSON list of models for each item in the directory.
|
|
|
|
:file : Text of file as a string or base64 encoded string if mimetype
|
|
|
|
is other than 'text/plain'.
|
|
|
|
:notebook : JSON structure of the file.
|
|
|
|
|
|
|
|
`ein:$content-format
|
|
|
|
Value will depend on content-type:
|
|
|
|
:directory : :json.
|
|
|
|
:file : Either :text or :base64
|
|
|
|
:notebook : :json.
|
|
|
|
"
|
|
|
|
url-or-port
|
|
|
|
name
|
|
|
|
path
|
|
|
|
type
|
|
|
|
writable
|
|
|
|
created
|
|
|
|
last-modified
|
|
|
|
mimetype
|
|
|
|
raw-content
|
|
|
|
format)
|
|
|
|
|
|
|
|
(defun ein:content-url (url-or-port path &optional name)
|
|
|
|
(if name
|
|
|
|
(ein:url url-or-port "api/contents" path name)
|
|
|
|
(ein:url url-or-port "api/contents" path)))
|
|
|
|
|
|
|
|
(defun ein:content-list-contents (path &optional url-or-port force-sync)
|
2015-01-31 10:31:20 -06:00
|
|
|
"Return the contents of the object at the specified path from the Jupyter server."
|
2015-01-31 10:13:49 -06:00
|
|
|
(let* ((url-or-port (or url-or-port (ein:default-url-or-port)))
|
|
|
|
(url (ein:content-url url-or-port path))
|
|
|
|
(new-content (make-ein:$content :url-or-port url-or-port)))
|
|
|
|
(ein:query-singleton-ajax
|
|
|
|
(list 'content-list-contents url-or-port path)
|
|
|
|
url
|
|
|
|
:type "GET"
|
|
|
|
:parser #'ein:json-read
|
|
|
|
:sync force-sync
|
|
|
|
:success (apply-partially #'ein:new-content new-content)
|
|
|
|
:error (apply-partially #'ein-content-list-contents-error url))
|
|
|
|
new-content))
|
|
|
|
|
2015-01-31 10:31:20 -06:00
|
|
|
(defun* ein:new-content (content &key data &allow-other-keys)
|
|
|
|
(setf (ein:$content-name content) (plist-get data :name)
|
|
|
|
(ein:$content-path content) (plist-get data :path)
|
|
|
|
(ein:$content-type content) (plist-get data :type)
|
|
|
|
(ein:$content-created content) (plist-get data :created)
|
|
|
|
(ein:$content-last-modified content) (plist-get data :last_modified)
|
|
|
|
(ein:$content-format content) (plist-get data :format)
|
|
|
|
(ein:$content-writable content) (plist-get data :writable)
|
|
|
|
(ein:$content-mimetype content) (plist-get data :mimetype)
|
|
|
|
(ein:$content-raw-content content) (plist-get data :content)))
|
|
|
|
|
2015-01-31 10:13:49 -06:00
|
|
|
(defun* ein:content-list-contents-error (url &key symbol-status response &allow-other-keys)
|
|
|
|
(ein:log 'verbose
|
|
|
|
"Error thrown: %S" (request-response-error-thrown response))
|
|
|
|
(ein:log 'error
|
|
|
|
"Content list call %s failed with status %s." url symbol-status))
|
|
|
|
|
2015-01-31 10:31:20 -06:00
|
|
|
;; ***
|
2015-01-31 10:13:49 -06:00
|
|
|
|
|
|
|
(defvar *ein:content-hierarchy* (make-hash-table))
|
|
|
|
|
|
|
|
(defun ein:make-content-hierarchy (path url-or-port)
|
|
|
|
(let* ((node (ein:content-list-contents path url-or-port t))
|
|
|
|
(items (ein:$content-raw-content node)))
|
|
|
|
(ein:flatten (loop for item in items
|
|
|
|
for c = (make-ein:$content :url-or-port url-or-port)
|
|
|
|
do (ein:new-content c :data item)
|
|
|
|
collect
|
|
|
|
(cond ((string= (ein:$content-type c) "directory")
|
|
|
|
(cons c
|
|
|
|
(ein:make-content-hierarchy (ein:$content-path c) url-or-port)))
|
|
|
|
(t c))))))
|
|
|
|
|
|
|
|
(defun ein:refresh-content-hierarchy (&optional url-or-port)
|
|
|
|
(let ((url-or-port (or url-or-port (ein:default-url-or-port))))
|
|
|
|
(setf (gethash url-or-port *ein:content-hierarchy*)
|
|
|
|
(ein:make-content-hierarchy "" url-or-port))))
|
|
|
|
|
2015-01-31 10:31:20 -06:00
|
|
|
;;; Get file contents
|
|
|
|
|
|
|
|
(defun ein:content-rename (content new-path)
|
|
|
|
(let ((url-or-port (ein:$content-url-or-port content))
|
|
|
|
(path (ein:$content-path content)))
|
|
|
|
(ein:query-singleton-ajax
|
|
|
|
(list 'get-file url-or-port path)
|
|
|
|
(ein:content-url url-or-port path)
|
|
|
|
:type "PATCH"
|
2015-01-31 10:32:52 -06:00
|
|
|
:data (json-encode `(:path ,new-path))
|
|
|
|
:parser #'ein:json-read
|
2015-01-31 10:31:20 -06:00
|
|
|
:success (apply-partially #'update-content-path content)
|
|
|
|
:error (apply-partially #'ein-content-rename-error path))))
|
|
|
|
|
|
|
|
(defun* update-content-path (content &key data &allow-other-keys)
|
|
|
|
(setf (ein:$content-path content) (plist-get data :path)
|
|
|
|
(ein:$content-name content) (plist-get data :name)
|
|
|
|
(ein:$content-last-modified content) (plist-get data :last_modified)))
|
|
|
|
|
|
|
|
(defun* ein:content-rename-error (path &key symbol-status response &allow-other-keys)
|
|
|
|
(ein:log 'verbose
|
|
|
|
"Error thrown: %S" (request-response-error-thrown response))
|
|
|
|
(ein:log 'error
|
|
|
|
"Renaming content %s failed with status %s." path symbol-status))
|