forked from CHESSComputing/CHAPaaS
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathnotebook.go
123 lines (113 loc) · 3.56 KB
/
notebook.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
"time"
)
// NotebookRecord defines notebook record structure
type NotebookRecord struct {
Name string `json:"name"`
Path string `json:"path"`
LastModified string `json:"last_modified"`
Created string `json:"created"`
Content NotebookContent
}
// NotebookContent defines notebook content structure
type NotebookContent struct {
Cells []Cell
}
// Cell defines notebook cell structure
type Cell struct {
CellType string
ExecutionCounter int
Id string
Source string
}
// Notebook represents jupyter notebook object
type Notebook struct {
Host string // jupyter hostname
Token string // jupyter server token
Root string // jupyter root area
User string // jupyter user name
FileName string // notebook file name
}
/*
* The JupyterRoot defines top level directory where we run Jupyter app
* Within this area we must create /users where we'll store each individual
* user notebooks. Therefore, in a code we use /api/contents/users API
* which includes /users path which should exist in JupyterRoot
* Create() api of Notebook struct will properly creats /users area within JupyterRoot area
*/
// Create creates notebook user area and notebook file
func (n *Notebook) Create() error {
// ensure that new user's area exists under JupyterRoot
path := fmt.Sprintf("%s/users/%s", Config.JupyterRoot, n.User)
if Config.Verbose > 0 {
log.Println("create notebook", path)
}
err := os.MkdirAll(path, 0755)
if err != nil {
return err
}
// check if JupyterRoot/users/<user> area has the notebook file name
rurl := fmt.Sprintf("%s/api/contents/users/%s/%s", n.Host, n.User, n.FileName)
rec, err := notebookCall("GET", rurl, n.Token, nil)
if Config.Verbose > 0 {
log.Printf("HTTP GET %s, it has %+v, error=%v", rurl, rec, err)
}
if err == nil && rec.Created != "" {
// we have existing notebook file
return nil
}
// create notebook file if it does not exist
// https://jupyter-server.readthedocs.io/en/latest/developers/rest-api.html
var jsonData = []byte(`{"type": "notebook"}`)
rurl = fmt.Sprintf("%s/api/contents/users/%s", n.Host, n.User)
if Config.Verbose > 0 {
log.Printf("HTTP POST %s %+v", rurl, string(jsonData))
}
rec, err = notebookCall("POST", rurl, n.Token, bytes.NewBuffer(jsonData))
if Config.Verbose > 0 {
log.Printf("jupyter response %+v, error %v", rec, err)
}
return err
}
// Capture fetches content of notebook file
func (n *Notebook) Capture() (NotebookRecord, error) {
rurl := fmt.Sprintf("%s/api/contents/users/%s/%s", n.Host, n.User, n.FileName)
rec, err := notebookCall("GET", rurl, n.Token, nil)
return rec, err
}
// helper function to make API call to jupyter notebook
func notebookCall(method, rurl, token string, body io.Reader) (NotebookRecord, error) {
var rec NotebookRecord
client := &http.Client{
Timeout: time.Second * 10,
}
req, err := http.NewRequest(method, rurl, body)
if Config.Verbose > 0 {
log.Printf("Jupyter notebook request %+v, error=%v", req, err)
}
if err != nil {
return rec, err
}
req.Header.Add("Accept", "application/json")
req.Header.Add("Authorization", fmt.Sprintf("Token %s", token))
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
req.Header.Add("X-Frame-Options", "SAMEORIGIN")
resp, err := client.Do(req)
if Config.Verbose > 0 {
log.Printf("Jupyter notebook response %+v, error=%v", resp, err)
}
if err != nil {
return rec, err
}
defer resp.Body.Close()
err = json.NewDecoder(resp.Body).Decode(&rec)
return rec, nil
}