##// END OF EJS Templates
Merge branch 'htmlnotebook_list_notebooks' of https://github.com/stefanv/ipython into stefanv-htmlnotebook_list_notebooks
Brian E. Granger -
r4627:a82fa5d2 merge
parent child Browse files
Show More
@@ -1,233 +1,236 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
23
23 from tornado import web
24 from tornado import web
24
25
25 from IPython.config.configurable import LoggingConfigurable
26 from IPython.config.configurable import LoggingConfigurable
26 from IPython.nbformat import current
27 from IPython.nbformat import current
27 from IPython.utils.traitlets import Unicode, List, Dict
28 from IPython.utils.traitlets import Unicode, List, Dict
28
29
29
30
30 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
31 # Code
32 # Code
32 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
33
34
34
35
35 class NotebookManager(LoggingConfigurable):
36 class NotebookManager(LoggingConfigurable):
36
37
37 notebook_dir = Unicode(os.getcwd(), config=True, help="""
38 notebook_dir = Unicode(os.getcwd(), config=True, help="""
38 The directory to use for notebooks.
39 The directory to use for notebooks.
39 """)
40 """)
40 filename_ext = Unicode(u'.ipynb')
41 filename_ext = Unicode(u'.ipynb')
41 allowed_formats = List([u'json',u'xml',u'py'])
42 allowed_formats = List([u'json',u'xml',u'py'])
42
43
43 # Map notebook_ids to notebook names
44 # Map notebook_ids to notebook names
44 mapping = Dict()
45 mapping = Dict()
45 # Map notebook names to notebook_ids
46 # Map notebook names to notebook_ids
46 rev_mapping = Dict()
47 rev_mapping = Dict()
47
48
48 def list_notebooks(self):
49 def list_notebooks(self):
49 """List all notebooks in the notebook dir.
50 """List all notebooks in the notebook dir.
50
51
51 This returns a list of dicts of the form::
52 This returns a list of dicts of the form::
52
53
53 dict(notebook_id=notebook,name=name)
54 dict(notebook_id=notebook,name=name)
54 """
55 """
55 names = os.listdir(self.notebook_dir)
56 names = glob.glob(os.path.join(self.notebook_dir,
56 names = [name.split(u'.')[0]
57 '*' + self.filename_ext))
57 for name in names if name.endswith(self.filename_ext)]
58 names = [os.path.splitext(os.path.basename(name))[0]
59 for name in names]
60
58 data = []
61 data = []
59 for name in names:
62 for name in names:
60 if name not in self.rev_mapping:
63 if name not in self.rev_mapping:
61 notebook_id = self.new_notebook_id(name)
64 notebook_id = self.new_notebook_id(name)
62 else:
65 else:
63 notebook_id = self.rev_mapping[name]
66 notebook_id = self.rev_mapping[name]
64 data.append(dict(notebook_id=notebook_id,name=name))
67 data.append(dict(notebook_id=notebook_id,name=name))
65 data = sorted(data, key=lambda item: item['name'])
68 data = sorted(data, key=lambda item: item['name'])
66 return data
69 return data
67
70
68 def new_notebook_id(self, name):
71 def new_notebook_id(self, name):
69 """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."""
70 notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
73 notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
71 'file://'+self.get_path_by_name(name).encode('utf-8')))
74 'file://'+self.get_path_by_name(name).encode('utf-8')))
72 self.mapping[notebook_id] = name
75 self.mapping[notebook_id] = name
73 self.rev_mapping[name] = notebook_id
76 self.rev_mapping[name] = notebook_id
74 return notebook_id
77 return notebook_id
75
78
76 def delete_notebook_id(self, notebook_id):
79 def delete_notebook_id(self, notebook_id):
77 """Delete a notebook's id only. This doesn't delete the actual notebook."""
80 """Delete a notebook's id only. This doesn't delete the actual notebook."""
78 name = self.mapping[notebook_id]
81 name = self.mapping[notebook_id]
79 del self.mapping[notebook_id]
82 del self.mapping[notebook_id]
80 del self.rev_mapping[name]
83 del self.rev_mapping[name]
81
84
82 def notebook_exists(self, notebook_id):
85 def notebook_exists(self, notebook_id):
83 """Does a notebook exist?"""
86 """Does a notebook exist?"""
84 if notebook_id not in self.mapping:
87 if notebook_id not in self.mapping:
85 return False
88 return False
86 path = self.get_path_by_name(self.mapping[notebook_id])
89 path = self.get_path_by_name(self.mapping[notebook_id])
87 return os.path.isfile(path)
90 return os.path.isfile(path)
88
91
89 def find_path(self, notebook_id):
92 def find_path(self, notebook_id):
90 """Return a full path to a notebook given its notebook_id."""
93 """Return a full path to a notebook given its notebook_id."""
91 try:
94 try:
92 name = self.mapping[notebook_id]
95 name = self.mapping[notebook_id]
93 except KeyError:
96 except KeyError:
94 raise web.HTTPError(404)
97 raise web.HTTPError(404)
95 return self.get_path_by_name(name)
98 return self.get_path_by_name(name)
96
99
97 def get_path_by_name(self, name):
100 def get_path_by_name(self, name):
98 """Return a full path to a notebook given its name."""
101 """Return a full path to a notebook given its name."""
99 filename = name + self.filename_ext
102 filename = name + self.filename_ext
100 path = os.path.join(self.notebook_dir, filename)
103 path = os.path.join(self.notebook_dir, filename)
101 return path
104 return path
102
105
103 def get_notebook(self, notebook_id, format=u'json'):
106 def get_notebook(self, notebook_id, format=u'json'):
104 """Get the representation of a notebook in format by notebook_id."""
107 """Get the representation of a notebook in format by notebook_id."""
105 format = unicode(format)
108 format = unicode(format)
106 if format not in self.allowed_formats:
109 if format not in self.allowed_formats:
107 raise web.HTTPError(415)
110 raise web.HTTPError(415)
108 last_modified, nb = self.get_notebook_object(notebook_id)
111 last_modified, nb = self.get_notebook_object(notebook_id)
109 data = current.writes(nb, format)
112 data = current.writes(nb, format)
110 name = nb.get('name','notebook')
113 name = nb.get('name','notebook')
111 return last_modified, name, data
114 return last_modified, name, data
112
115
113 def get_notebook_object(self, notebook_id):
116 def get_notebook_object(self, notebook_id):
114 """Get the NotebookNode representation of a notebook by notebook_id."""
117 """Get the NotebookNode representation of a notebook by notebook_id."""
115 path = self.find_path(notebook_id)
118 path = self.find_path(notebook_id)
116 if not os.path.isfile(path):
119 if not os.path.isfile(path):
117 raise web.HTTPError(404)
120 raise web.HTTPError(404)
118 info = os.stat(path)
121 info = os.stat(path)
119 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
122 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
120 try:
123 try:
121 with open(path,'r') as f:
124 with open(path,'r') as f:
122 s = f.read()
125 s = f.read()
123 try:
126 try:
124 # v2 and later have xml in the .ipynb files.
127 # v2 and later have xml in the .ipynb files.
125 nb = current.reads(s, 'xml')
128 nb = current.reads(s, 'xml')
126 except:
129 except:
127 # v1 had json in the .ipynb files.
130 # v1 had json in the .ipynb files.
128 nb = current.reads(s, 'json')
131 nb = current.reads(s, 'json')
129 # v1 notebooks don't have a name field, so use the filename.
132 # v1 notebooks don't have a name field, so use the filename.
130 nb.name = os.path.split(path)[-1].split(u'.')[0]
133 nb.name = os.path.split(path)[-1].split(u'.')[0]
131 except:
134 except:
132 raise web.HTTPError(404)
135 raise web.HTTPError(404)
133 return last_modified, nb
136 return last_modified, nb
134
137
135 def save_new_notebook(self, data, name=None, format=u'json'):
138 def save_new_notebook(self, data, name=None, format=u'json'):
136 """Save a new notebook and return its notebook_id.
139 """Save a new notebook and return its notebook_id.
137
140
138 If a name is passed in, it overrides any values in the notebook data
141 If a name is passed in, it overrides any values in the notebook data
139 and the value in the data is updated to use that value.
142 and the value in the data is updated to use that value.
140 """
143 """
141 if format not in self.allowed_formats:
144 if format not in self.allowed_formats:
142 raise web.HTTPError(415)
145 raise web.HTTPError(415)
143
146
144 try:
147 try:
145 nb = current.reads(data, format)
148 nb = current.reads(data, format)
146 except:
149 except:
147 if format == u'xml':
150 if format == u'xml':
148 # v1 notebooks might come in with a format='xml' but be json.
151 # v1 notebooks might come in with a format='xml' but be json.
149 try:
152 try:
150 nb = current.reads(data, u'json')
153 nb = current.reads(data, u'json')
151 except:
154 except:
152 raise web.HTTPError(400)
155 raise web.HTTPError(400)
153 else:
156 else:
154 raise web.HTTPError(400)
157 raise web.HTTPError(400)
155
158
156 if name is None:
159 if name is None:
157 try:
160 try:
158 name = nb.name
161 name = nb.name
159 except AttributeError:
162 except AttributeError:
160 raise web.HTTPError(400)
163 raise web.HTTPError(400)
161 nb.name = name
164 nb.name = name
162
165
163 notebook_id = self.new_notebook_id(name)
166 notebook_id = self.new_notebook_id(name)
164 self.save_notebook_object(notebook_id, nb)
167 self.save_notebook_object(notebook_id, nb)
165 return notebook_id
168 return notebook_id
166
169
167 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
170 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
168 """Save an existing notebook by notebook_id."""
171 """Save an existing notebook by notebook_id."""
169 if format not in self.allowed_formats:
172 if format not in self.allowed_formats:
170 raise web.HTTPError(415)
173 raise web.HTTPError(415)
171
174
172 try:
175 try:
173 nb = current.reads(data, format)
176 nb = current.reads(data, format)
174 except:
177 except:
175 if format == u'xml':
178 if format == u'xml':
176 # v1 notebooks might come in with a format='xml' but be json.
179 # v1 notebooks might come in with a format='xml' but be json.
177 try:
180 try:
178 nb = current.reads(data, u'json')
181 nb = current.reads(data, u'json')
179 except:
182 except:
180 raise web.HTTPError(400)
183 raise web.HTTPError(400)
181 else:
184 else:
182 raise web.HTTPError(400)
185 raise web.HTTPError(400)
183
186
184 if name is not None:
187 if name is not None:
185 nb.name = name
188 nb.name = name
186 self.save_notebook_object(notebook_id, nb)
189 self.save_notebook_object(notebook_id, nb)
187
190
188 def save_notebook_object(self, notebook_id, nb):
191 def save_notebook_object(self, notebook_id, nb):
189 """Save an existing notebook object by notebook_id."""
192 """Save an existing notebook object by notebook_id."""
190 if notebook_id not in self.mapping:
193 if notebook_id not in self.mapping:
191 raise web.HTTPError(404)
194 raise web.HTTPError(404)
192 old_name = self.mapping[notebook_id]
195 old_name = self.mapping[notebook_id]
193 try:
196 try:
194 new_name = nb.name
197 new_name = nb.name
195 except AttributeError:
198 except AttributeError:
196 raise web.HTTPError(400)
199 raise web.HTTPError(400)
197 path = self.get_path_by_name(new_name)
200 path = self.get_path_by_name(new_name)
198 try:
201 try:
199 with open(path,'w') as f:
202 with open(path,'w') as f:
200 current.write(nb, f, u'xml')
203 current.write(nb, f, u'xml')
201 except:
204 except:
202 raise web.HTTPError(400)
205 raise web.HTTPError(400)
203 if old_name != new_name:
206 if old_name != new_name:
204 old_path = self.get_path_by_name(old_name)
207 old_path = self.get_path_by_name(old_name)
205 if os.path.isfile(old_path):
208 if os.path.isfile(old_path):
206 os.unlink(old_path)
209 os.unlink(old_path)
207 self.mapping[notebook_id] = new_name
210 self.mapping[notebook_id] = new_name
208 self.rev_mapping[new_name] = notebook_id
211 self.rev_mapping[new_name] = notebook_id
209
212
210 def delete_notebook(self, notebook_id):
213 def delete_notebook(self, notebook_id):
211 """Delete notebook by notebook_id."""
214 """Delete notebook by notebook_id."""
212 path = self.find_path(notebook_id)
215 path = self.find_path(notebook_id)
213 if not os.path.isfile(path):
216 if not os.path.isfile(path):
214 raise web.HTTPError(404)
217 raise web.HTTPError(404)
215 os.unlink(path)
218 os.unlink(path)
216 self.delete_notebook_id(notebook_id)
219 self.delete_notebook_id(notebook_id)
217
220
218 def new_notebook(self):
221 def new_notebook(self):
219 """Create a new notebook and returns its notebook_id."""
222 """Create a new notebook and returns its notebook_id."""
220 i = 0
223 i = 0
221 while True:
224 while True:
222 name = u'Untitled%i' % i
225 name = u'Untitled%i' % i
223 path = self.get_path_by_name(name)
226 path = self.get_path_by_name(name)
224 if not os.path.isfile(path):
227 if not os.path.isfile(path):
225 break
228 break
226 else:
229 else:
227 i = i+1
230 i = i+1
228 notebook_id = self.new_notebook_id(name)
231 notebook_id = self.new_notebook_id(name)
229 nb = current.new_notebook(name=name)
232 nb = current.new_notebook(name=name)
230 with open(path,'w') as f:
233 with open(path,'w') as f:
231 current.write(nb, f, u'xml')
234 current.write(nb, f, u'xml')
232 return notebook_id
235 return notebook_id
233
236
General Comments 0
You need to be logged in to leave comments. Login now