-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathnotebook.go
140 lines (128 loc) · 4.07 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
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
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
/*
server API description:
https://jupyter-server.readthedocs.io/en/latest/developers/rest-api.html
notebok content description:
https://jupyter-notebook.readthedocs.io/en/stable/extending/contents.html
nbformat description:
https://nbformat.readthedocs.io/en/latest/format_description.html
*/
// we will use template for JSON notebook initial content data
tmpl := make(TmplRecord)
tmpl["User"] = n.User
tmpl["Name"] = n.FileName
tmpl["Created"] = time.Now().Format(time.RFC3339)
jsonData := []byte(tmplPage("userprocessor.tmpl", tmpl))
if Config.Verbose > 0 {
log.Println("json data", string(jsonData))
}
rurl = fmt.Sprintf("%s/api/contents/users/%s/%s", n.Host, n.User, n.FileName)
if Config.Verbose > 0 {
log.Printf("HTTP POST %s %+v", rurl, string(jsonData))
}
rec, err = notebookCall("PUT", 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
}