##// END OF EJS Templates
Fixing bug in new metadata implementation.
Brian E. Granger -
Show More
@@ -1,218 +1,219 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 notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
73 notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
74 'file://'+self.get_path_by_name(name).encode('utf-8')))
74 'file://'+self.get_path_by_name(name).encode('utf-8')))
75 self.mapping[notebook_id] = name
75 self.mapping[notebook_id] = name
76 self.rev_mapping[name] = notebook_id
76 self.rev_mapping[name] = notebook_id
77 return notebook_id
77 return notebook_id
78
78
79 def delete_notebook_id(self, notebook_id):
79 def delete_notebook_id(self, notebook_id):
80 """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."""
81 name = self.mapping[notebook_id]
81 name = self.mapping[notebook_id]
82 del self.mapping[notebook_id]
82 del self.mapping[notebook_id]
83 del self.rev_mapping[name]
83 del self.rev_mapping[name]
84
84
85 def notebook_exists(self, notebook_id):
85 def notebook_exists(self, notebook_id):
86 """Does a notebook exist?"""
86 """Does a notebook exist?"""
87 if notebook_id not in self.mapping:
87 if notebook_id not in self.mapping:
88 return False
88 return False
89 path = self.get_path_by_name(self.mapping[notebook_id])
89 path = self.get_path_by_name(self.mapping[notebook_id])
90 return os.path.isfile(path)
90 return os.path.isfile(path)
91
91
92 def find_path(self, notebook_id):
92 def find_path(self, notebook_id):
93 """Return a full path to a notebook given its notebook_id."""
93 """Return a full path to a notebook given its notebook_id."""
94 try:
94 try:
95 name = self.mapping[notebook_id]
95 name = self.mapping[notebook_id]
96 except KeyError:
96 except KeyError:
97 raise web.HTTPError(404)
97 raise web.HTTPError(404)
98 return self.get_path_by_name(name)
98 return self.get_path_by_name(name)
99
99
100 def get_path_by_name(self, name):
100 def get_path_by_name(self, name):
101 """Return a full path to a notebook given its name."""
101 """Return a full path to a notebook given its name."""
102 filename = name + self.filename_ext
102 filename = name + self.filename_ext
103 path = os.path.join(self.notebook_dir, filename)
103 path = os.path.join(self.notebook_dir, filename)
104 return path
104 return path
105
105
106 def get_notebook(self, notebook_id, format=u'json'):
106 def get_notebook(self, notebook_id, format=u'json'):
107 """Get the representation of a notebook in format by notebook_id."""
107 """Get the representation of a notebook in format by notebook_id."""
108 format = unicode(format)
108 format = unicode(format)
109 if format not in self.allowed_formats:
109 if format not in self.allowed_formats:
110 raise web.HTTPError(415)
110 raise web.HTTPError(415)
111 last_modified, nb = self.get_notebook_object(notebook_id)
111 last_modified, nb = self.get_notebook_object(notebook_id)
112 data = current.writes(nb, format)
112 data = current.writes(nb, format)
113 name = nb.get('name','notebook')
113 name = nb.get('name','notebook')
114 return last_modified, name, data
114 return last_modified, name, data
115
115
116 def get_notebook_object(self, notebook_id):
116 def get_notebook_object(self, notebook_id):
117 """Get the NotebookNode representation of a notebook by notebook_id."""
117 """Get the NotebookNode representation of a notebook by notebook_id."""
118 path = self.find_path(notebook_id)
118 path = self.find_path(notebook_id)
119 if not os.path.isfile(path):
119 if not os.path.isfile(path):
120 raise web.HTTPError(404)
120 raise web.HTTPError(404)
121 info = os.stat(path)
121 info = os.stat(path)
122 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
122 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
123 with open(path,'r') as f:
123 with open(path,'r') as f:
124 s = f.read()
124 s = f.read()
125 try:
125 try:
126 # v1 and v2 and json in the .ipynb files.
126 # v1 and v2 and json in the .ipynb files.
127 nb = current.reads(s, u'json')
127 nb = current.reads(s, u'json')
128 except:
128 except:
129 raise web.HTTPError(404)
129 raise web.HTTPError(404)
130 if 'name' not in nb:
130 if 'name' not in nb:
131 nb.name = os.path.split(path)[-1].split(u'.')[0]
131 nb.name = os.path.split(path)[-1].split(u'.')[0]
132 return last_modified, nb
132 return last_modified, nb
133
133
134 def save_new_notebook(self, data, name=None, format=u'json'):
134 def save_new_notebook(self, data, name=None, format=u'json'):
135 """Save a new notebook and return its notebook_id.
135 """Save a new notebook and return its notebook_id.
136
136
137 If a name is passed in, it overrides any values in the notebook data
137 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.
138 and the value in the data is updated to use that value.
139 """
139 """
140 if format not in self.allowed_formats:
140 if format not in self.allowed_formats:
141 raise web.HTTPError(415)
141 raise web.HTTPError(415)
142
142
143 try:
143 try:
144 nb = current.reads(data, format)
144 nb = current.reads(data, format)
145 except:
145 except:
146 raise web.HTTPError(400)
146 raise web.HTTPError(400)
147
147
148 if name is None:
148 if name is None:
149 try:
149 try:
150 name = nb.metadata.name
150 name = nb.metadata.name
151 except AttributeError:
151 except AttributeError:
152 raise web.HTTPError(400)
152 raise web.HTTPError(400)
153 nb.metadata.name = name
153 nb.metadata.name = name
154
154
155 notebook_id = self.new_notebook_id(name)
155 notebook_id = self.new_notebook_id(name)
156 self.save_notebook_object(notebook_id, nb)
156 self.save_notebook_object(notebook_id, nb)
157 return notebook_id
157 return notebook_id
158
158
159 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
159 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
160 """Save an existing notebook by notebook_id."""
160 """Save an existing notebook by notebook_id."""
161 if format not in self.allowed_formats:
161 if format not in self.allowed_formats:
162 raise web.HTTPError(415)
162 raise web.HTTPError(415)
163
163
164 try:
164 try:
165 nb = current.reads(data, format)
165 nb = current.reads(data, format)
166 except:
166 except:
167 raise web.HTTPError(400)
167 raise web.HTTPError(400)
168
168
169 if name is not None:
169 if name is not None:
170 nb.metadata.name = name
170 nb.metadata.name = name
171 self.save_notebook_object(notebook_id, nb)
171 self.save_notebook_object(notebook_id, nb)
172
172
173 def save_notebook_object(self, notebook_id, nb):
173 def save_notebook_object(self, notebook_id, nb):
174 """Save an existing notebook object by notebook_id."""
174 """Save an existing notebook object by notebook_id."""
175 if notebook_id not in self.mapping:
175 if notebook_id not in self.mapping:
176 raise web.HTTPError(404)
176 raise web.HTTPError(404)
177 old_name = self.mapping[notebook_id]
177 old_name = self.mapping[notebook_id]
178 try:
178 try:
179 new_name = nb.metadata.name
179 new_name = nb.metadata.name
180 except AttributeError:
180 except AttributeError:
181 raise web.HTTPError(400)
181 raise web.HTTPError(400)
182 path = self.get_path_by_name(new_name)
182 path = self.get_path_by_name(new_name)
183 try:
183 try:
184 with open(path,'w') as f:
184 with open(path,'w') as f:
185 current.write(nb, f, u'json')
185 current.write(nb, f, u'json')
186 except:
186 except:
187 raise web.HTTPError(400)
187 raise web.HTTPError(400)
188 if old_name != new_name:
188 if old_name != new_name:
189 old_path = self.get_path_by_name(old_name)
189 old_path = self.get_path_by_name(old_name)
190 if os.path.isfile(old_path):
190 if os.path.isfile(old_path):
191 os.unlink(old_path)
191 os.unlink(old_path)
192 self.mapping[notebook_id] = new_name
192 self.mapping[notebook_id] = new_name
193 self.rev_mapping[new_name] = notebook_id
193 self.rev_mapping[new_name] = notebook_id
194
194
195 def delete_notebook(self, notebook_id):
195 def delete_notebook(self, notebook_id):
196 """Delete notebook by notebook_id."""
196 """Delete notebook by notebook_id."""
197 path = self.find_path(notebook_id)
197 path = self.find_path(notebook_id)
198 if not os.path.isfile(path):
198 if not os.path.isfile(path):
199 raise web.HTTPError(404)
199 raise web.HTTPError(404)
200 os.unlink(path)
200 os.unlink(path)
201 self.delete_notebook_id(notebook_id)
201 self.delete_notebook_id(notebook_id)
202
202
203 def new_notebook(self):
203 def new_notebook(self):
204 """Create a new notebook and returns its notebook_id."""
204 """Create a new notebook and returns its notebook_id."""
205 i = 0
205 i = 0
206 while True:
206 while True:
207 name = u'Untitled%i' % i
207 name = u'Untitled%i' % i
208 path = self.get_path_by_name(name)
208 path = self.get_path_by_name(name)
209 if not os.path.isfile(path):
209 if not os.path.isfile(path):
210 break
210 break
211 else:
211 else:
212 i = i+1
212 i = i+1
213 notebook_id = self.new_notebook_id(name)
213 notebook_id = self.new_notebook_id(name)
214 nb = current.new_notebook(name=name)
214 metadata = current.new_metadata(name=name)
215 nb = current.new_notebook(metadata=metadata)
215 with open(path,'w') as f:
216 with open(path,'w') as f:
216 current.write(nb, f, u'json')
217 current.write(nb, f, u'json')
217 return notebook_id
218 return notebook_id
218
219
General Comments 0
You need to be logged in to leave comments. Login now