##// END OF EJS Templates
Decode data for saving notebook, allowing saving in Python 3.
Thomas Kluyver -
Show More
@@ -1,228 +1,228
1 1 """A notebook manager that uses the local file system for storage.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import datetime
20 20 import os
21 21 import uuid
22 22 import glob
23 23
24 24 from tornado import web
25 25
26 26 from IPython.config.configurable import LoggingConfigurable
27 27 from IPython.nbformat import current
28 28 from IPython.utils.traitlets import Unicode, List, Dict
29 29
30 30
31 31 #-----------------------------------------------------------------------------
32 32 # Code
33 33 #-----------------------------------------------------------------------------
34 34
35 35
36 36 class NotebookManager(LoggingConfigurable):
37 37
38 38 notebook_dir = Unicode(os.getcwd(), config=True, help="""
39 39 The directory to use for notebooks.
40 40 """)
41 41 filename_ext = Unicode(u'.ipynb')
42 42 allowed_formats = List([u'json',u'py'])
43 43
44 44 # Map notebook_ids to notebook names
45 45 mapping = Dict()
46 46 # Map notebook names to notebook_ids
47 47 rev_mapping = Dict()
48 48
49 49 def list_notebooks(self):
50 50 """List all notebooks in the notebook dir.
51 51
52 52 This returns a list of dicts of the form::
53 53
54 54 dict(notebook_id=notebook,name=name)
55 55 """
56 56 names = glob.glob(os.path.join(self.notebook_dir,
57 57 '*' + self.filename_ext))
58 58 names = [os.path.splitext(os.path.basename(name))[0]
59 59 for name in names]
60 60
61 61 data = []
62 62 for name in names:
63 63 if name not in self.rev_mapping:
64 64 notebook_id = self.new_notebook_id(name)
65 65 else:
66 66 notebook_id = self.rev_mapping[name]
67 67 data.append(dict(notebook_id=notebook_id,name=name))
68 68 data = sorted(data, key=lambda item: item['name'])
69 69 return data
70 70
71 71 def new_notebook_id(self, name):
72 72 """Generate a new notebook_id for a name and store its mappings."""
73 73 # TODO: the following will give stable urls for notebooks, but unless
74 74 # the notebooks are immediately redirected to their new urls when their
75 75 # filemname changes, nasty inconsistencies result. So for now it's
76 76 # disabled and instead we use a random uuid4() call. But we leave the
77 77 # logic here so that we can later reactivate it, whhen the necessary
78 78 # url redirection code is written.
79 79 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
80 80 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
81 81
82 82 notebook_id = unicode(uuid.uuid4())
83 83
84 84 self.mapping[notebook_id] = name
85 85 self.rev_mapping[name] = notebook_id
86 86 return notebook_id
87 87
88 88 def delete_notebook_id(self, notebook_id):
89 89 """Delete a notebook's id only. This doesn't delete the actual notebook."""
90 90 name = self.mapping[notebook_id]
91 91 del self.mapping[notebook_id]
92 92 del self.rev_mapping[name]
93 93
94 94 def notebook_exists(self, notebook_id):
95 95 """Does a notebook exist?"""
96 96 if notebook_id not in self.mapping:
97 97 return False
98 98 path = self.get_path_by_name(self.mapping[notebook_id])
99 99 return os.path.isfile(path)
100 100
101 101 def find_path(self, notebook_id):
102 102 """Return a full path to a notebook given its notebook_id."""
103 103 try:
104 104 name = self.mapping[notebook_id]
105 105 except KeyError:
106 106 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
107 107 return self.get_path_by_name(name)
108 108
109 109 def get_path_by_name(self, name):
110 110 """Return a full path to a notebook given its name."""
111 111 filename = name + self.filename_ext
112 112 path = os.path.join(self.notebook_dir, filename)
113 113 return path
114 114
115 115 def get_notebook(self, notebook_id, format=u'json'):
116 116 """Get the representation of a notebook in format by notebook_id."""
117 117 format = unicode(format)
118 118 if format not in self.allowed_formats:
119 119 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
120 120 last_modified, nb = self.get_notebook_object(notebook_id)
121 121 data = current.writes(nb, format)
122 122 name = nb.get('name','notebook')
123 123 return last_modified, name, data
124 124
125 125 def get_notebook_object(self, notebook_id):
126 126 """Get the NotebookNode representation of a notebook by notebook_id."""
127 127 path = self.find_path(notebook_id)
128 128 if not os.path.isfile(path):
129 129 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
130 130 info = os.stat(path)
131 131 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
132 132 with open(path,'r') as f:
133 133 s = f.read()
134 134 try:
135 135 # v1 and v2 and json in the .ipynb files.
136 136 nb = current.reads(s, u'json')
137 137 except:
138 138 raise web.HTTPError(500, u'Unreadable JSON notebook.')
139 139 if 'name' not in nb:
140 140 nb.name = os.path.split(path)[-1].split(u'.')[0]
141 141 return last_modified, nb
142 142
143 143 def save_new_notebook(self, data, name=None, format=u'json'):
144 144 """Save a new notebook and return its notebook_id.
145 145
146 146 If a name is passed in, it overrides any values in the notebook data
147 147 and the value in the data is updated to use that value.
148 148 """
149 149 if format not in self.allowed_formats:
150 150 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
151 151
152 152 try:
153 nb = current.reads(data, format)
153 nb = current.reads(data.decode('utf-8'), format)
154 154 except:
155 155 raise web.HTTPError(400, u'Invalid JSON data')
156 156
157 157 if name is None:
158 158 try:
159 159 name = nb.metadata.name
160 160 except AttributeError:
161 161 raise web.HTTPError(400, u'Missing notebook name')
162 162 nb.metadata.name = name
163 163
164 164 notebook_id = self.new_notebook_id(name)
165 165 self.save_notebook_object(notebook_id, nb)
166 166 return notebook_id
167 167
168 168 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
169 169 """Save an existing notebook by notebook_id."""
170 170 if format not in self.allowed_formats:
171 171 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
172 172
173 173 try:
174 nb = current.reads(data, format)
174 nb = current.reads(data.decode('utf-8'), format)
175 175 except:
176 176 raise web.HTTPError(400, u'Invalid JSON data')
177 177
178 178 if name is not None:
179 179 nb.metadata.name = name
180 180 self.save_notebook_object(notebook_id, nb)
181 181
182 182 def save_notebook_object(self, notebook_id, nb):
183 183 """Save an existing notebook object by notebook_id."""
184 184 if notebook_id not in self.mapping:
185 185 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
186 186 old_name = self.mapping[notebook_id]
187 187 try:
188 188 new_name = nb.metadata.name
189 189 except AttributeError:
190 190 raise web.HTTPError(400, u'Missing notebook name')
191 191 path = self.get_path_by_name(new_name)
192 192 try:
193 193 with open(path,'w') as f:
194 194 current.write(nb, f, u'json')
195 195 except:
196 196 raise web.HTTPError(400, u'Unexpected error while saving notebook')
197 197 if old_name != new_name:
198 198 old_path = self.get_path_by_name(old_name)
199 199 if os.path.isfile(old_path):
200 200 os.unlink(old_path)
201 201 self.mapping[notebook_id] = new_name
202 202 self.rev_mapping[new_name] = notebook_id
203 203
204 204 def delete_notebook(self, notebook_id):
205 205 """Delete notebook by notebook_id."""
206 206 path = self.find_path(notebook_id)
207 207 if not os.path.isfile(path):
208 208 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
209 209 os.unlink(path)
210 210 self.delete_notebook_id(notebook_id)
211 211
212 212 def new_notebook(self):
213 213 """Create a new notebook and returns its notebook_id."""
214 214 i = 0
215 215 while True:
216 216 name = u'Untitled%i' % i
217 217 path = self.get_path_by_name(name)
218 218 if not os.path.isfile(path):
219 219 break
220 220 else:
221 221 i = i+1
222 222 notebook_id = self.new_notebook_id(name)
223 223 metadata = current.new_metadata(name=name)
224 224 nb = current.new_notebook(metadata=metadata)
225 225 with open(path,'w') as f:
226 226 current.write(nb, f, u'json')
227 227 return notebook_id
228 228
General Comments 0
You need to be logged in to leave comments. Login now