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