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