##// END OF EJS Templates
include error in 'Unexpected error' message.
MinRK -
Show More
@@ -1,255 +1,255 b''
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, Bool
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
42 42 save_script = Bool(False, config=True,
43 43 help="""Also save notebooks as a Python script.
44 44
45 45 For easier use of import/%loadpy across notebooks, a <notebook-name>.py
46 46 script will be created next to any <notebook-name>.ipynb on each save.
47 47 """
48 48 )
49 49
50 50 filename_ext = Unicode(u'.ipynb')
51 51 allowed_formats = List([u'json',u'py'])
52 52
53 53 # Map notebook_ids to notebook names
54 54 mapping = Dict()
55 55 # Map notebook names to notebook_ids
56 56 rev_mapping = Dict()
57 57
58 58 def list_notebooks(self):
59 59 """List all notebooks in the notebook dir.
60 60
61 61 This returns a list of dicts of the form::
62 62
63 63 dict(notebook_id=notebook,name=name)
64 64 """
65 65 names = glob.glob(os.path.join(self.notebook_dir,
66 66 '*' + self.filename_ext))
67 67 names = [os.path.splitext(os.path.basename(name))[0]
68 68 for name in names]
69 69
70 70 data = []
71 71 for name in names:
72 72 if name not in self.rev_mapping:
73 73 notebook_id = self.new_notebook_id(name)
74 74 else:
75 75 notebook_id = self.rev_mapping[name]
76 76 data.append(dict(notebook_id=notebook_id,name=name))
77 77 data = sorted(data, key=lambda item: item['name'])
78 78 return data
79 79
80 80 def new_notebook_id(self, name):
81 81 """Generate a new notebook_id for a name and store its mappings."""
82 82 # TODO: the following will give stable urls for notebooks, but unless
83 83 # the notebooks are immediately redirected to their new urls when their
84 84 # filemname changes, nasty inconsistencies result. So for now it's
85 85 # disabled and instead we use a random uuid4() call. But we leave the
86 86 # logic here so that we can later reactivate it, whhen the necessary
87 87 # url redirection code is written.
88 88 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
89 89 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
90 90
91 91 notebook_id = unicode(uuid.uuid4())
92 92
93 93 self.mapping[notebook_id] = name
94 94 self.rev_mapping[name] = notebook_id
95 95 return notebook_id
96 96
97 97 def delete_notebook_id(self, notebook_id):
98 98 """Delete a notebook's id only. This doesn't delete the actual notebook."""
99 99 name = self.mapping[notebook_id]
100 100 del self.mapping[notebook_id]
101 101 del self.rev_mapping[name]
102 102
103 103 def notebook_exists(self, notebook_id):
104 104 """Does a notebook exist?"""
105 105 if notebook_id not in self.mapping:
106 106 return False
107 107 path = self.get_path_by_name(self.mapping[notebook_id])
108 108 return os.path.isfile(path)
109 109
110 110 def find_path(self, notebook_id):
111 111 """Return a full path to a notebook given its notebook_id."""
112 112 try:
113 113 name = self.mapping[notebook_id]
114 114 except KeyError:
115 115 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
116 116 return self.get_path_by_name(name)
117 117
118 118 def get_path_by_name(self, name):
119 119 """Return a full path to a notebook given its name."""
120 120 filename = name + self.filename_ext
121 121 path = os.path.join(self.notebook_dir, filename)
122 122 return path
123 123
124 124 def get_notebook(self, notebook_id, format=u'json'):
125 125 """Get the representation of a notebook in format by notebook_id."""
126 126 format = unicode(format)
127 127 if format not in self.allowed_formats:
128 128 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
129 129 last_modified, nb = self.get_notebook_object(notebook_id)
130 130 kwargs = {}
131 131 if format == 'json':
132 132 # don't split lines for sending over the wire, because it
133 133 # should match the Python in-memory format.
134 134 kwargs['split_lines'] = False
135 135 data = current.writes(nb, format, **kwargs)
136 136 name = nb.get('name','notebook')
137 137 return last_modified, name, data
138 138
139 139 def get_notebook_object(self, notebook_id):
140 140 """Get the NotebookNode representation of a notebook by notebook_id."""
141 141 path = self.find_path(notebook_id)
142 142 if not os.path.isfile(path):
143 143 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
144 144 info = os.stat(path)
145 145 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
146 146 with open(path,'r') as f:
147 147 s = f.read()
148 148 try:
149 149 # v1 and v2 and json in the .ipynb files.
150 150 nb = current.reads(s, u'json')
151 151 except:
152 152 raise web.HTTPError(500, u'Unreadable JSON notebook.')
153 153 if 'name' not in nb:
154 154 nb.name = os.path.split(path)[-1].split(u'.')[0]
155 155 return last_modified, nb
156 156
157 157 def save_new_notebook(self, data, name=None, format=u'json'):
158 158 """Save a new notebook and return its notebook_id.
159 159
160 160 If a name is passed in, it overrides any values in the notebook data
161 161 and the value in the data is updated to use that value.
162 162 """
163 163 if format not in self.allowed_formats:
164 164 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
165 165
166 166 try:
167 167 nb = current.reads(data.decode('utf-8'), format)
168 168 except:
169 169 raise web.HTTPError(400, u'Invalid JSON data')
170 170
171 171 if name is None:
172 172 try:
173 173 name = nb.metadata.name
174 174 except AttributeError:
175 175 raise web.HTTPError(400, u'Missing notebook name')
176 176 nb.metadata.name = name
177 177
178 178 notebook_id = self.new_notebook_id(name)
179 179 self.save_notebook_object(notebook_id, nb)
180 180 return notebook_id
181 181
182 182 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
183 183 """Save an existing notebook by notebook_id."""
184 184 if format not in self.allowed_formats:
185 185 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
186 186
187 187 try:
188 188 nb = current.reads(data.decode('utf-8'), format)
189 189 except:
190 190 raise web.HTTPError(400, u'Invalid JSON data')
191 191
192 192 if name is not None:
193 193 nb.metadata.name = name
194 194 self.save_notebook_object(notebook_id, nb)
195 195
196 196 def save_notebook_object(self, notebook_id, nb):
197 197 """Save an existing notebook object by notebook_id."""
198 198 if notebook_id not in self.mapping:
199 199 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
200 200 old_name = self.mapping[notebook_id]
201 201 try:
202 202 new_name = nb.metadata.name
203 203 except AttributeError:
204 204 raise web.HTTPError(400, u'Missing notebook name')
205 205 path = self.get_path_by_name(new_name)
206 206 try:
207 207 with open(path,'w') as f:
208 208 current.write(nb, f, u'json')
209 except:
210 raise web.HTTPError(400, u'Unexpected error while saving notebook')
209 except Exception as e:
210 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
211 211 # save .py script as well
212 212 if self.save_script:
213 213 pypath = os.path.splitext(path)[0] + '.py'
214 214 try:
215 215 with open(pypath,'w') as f:
216 216 current.write(nb, f, u'py')
217 except:
218 raise web.HTTPError(400, u'Unexpected error while saving notebook as script')
217 except Exception as e:
218 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
219 219
220 220 if old_name != new_name:
221 221 old_path = self.get_path_by_name(old_name)
222 222 if os.path.isfile(old_path):
223 223 os.unlink(old_path)
224 224 if self.save_script:
225 225 old_pypath = os.path.splitext(old_path)[0] + '.py'
226 226 if os.path.isfile(old_pypath):
227 227 os.unlink(old_pypath)
228 228 self.mapping[notebook_id] = new_name
229 229 self.rev_mapping[new_name] = notebook_id
230 230
231 231 def delete_notebook(self, notebook_id):
232 232 """Delete notebook by notebook_id."""
233 233 path = self.find_path(notebook_id)
234 234 if not os.path.isfile(path):
235 235 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
236 236 os.unlink(path)
237 237 self.delete_notebook_id(notebook_id)
238 238
239 239 def new_notebook(self):
240 240 """Create a new notebook and returns its notebook_id."""
241 241 i = 0
242 242 while True:
243 243 name = u'Untitled%i' % i
244 244 path = self.get_path_by_name(name)
245 245 if not os.path.isfile(path):
246 246 break
247 247 else:
248 248 i = i+1
249 249 notebook_id = self.new_notebook_id(name)
250 250 metadata = current.new_metadata(name=name)
251 251 nb = current.new_notebook(metadata=metadata)
252 252 with open(path,'w') as f:
253 253 current.write(nb, f, u'json')
254 254 return notebook_id
255 255
General Comments 0
You need to be logged in to leave comments. Login now