##// END OF EJS Templates
Leave commented-out stable url code for reference, with TODO.
Fernando Perez -
Show More
@@ -1,218 +1,228 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
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 filename_ext = Unicode(u'.ipynb')
42 42 allowed_formats = List([u'json',u'py'])
43 43
44 44 # Map notebook_ids to notebook names
45 45 mapping = Dict()
46 46 # Map notebook names to notebook_ids
47 47 rev_mapping = Dict()
48 48
49 49 def list_notebooks(self):
50 50 """List all notebooks in the notebook dir.
51 51
52 52 This returns a list of dicts of the form::
53 53
54 54 dict(notebook_id=notebook,name=name)
55 55 """
56 56 names = glob.glob(os.path.join(self.notebook_dir,
57 57 '*' + self.filename_ext))
58 58 names = [os.path.splitext(os.path.basename(name))[0]
59 59 for name in names]
60 60
61 61 data = []
62 62 for name in names:
63 63 if name not in self.rev_mapping:
64 64 notebook_id = self.new_notebook_id(name)
65 65 else:
66 66 notebook_id = self.rev_mapping[name]
67 67 data.append(dict(notebook_id=notebook_id,name=name))
68 68 data = sorted(data, key=lambda item: item['name'])
69 69 return data
70 70
71 71 def new_notebook_id(self, name):
72 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 82 notebook_id = unicode(uuid.uuid4())
83
74 84 self.mapping[notebook_id] = name
75 85 self.rev_mapping[name] = notebook_id
76 86 return notebook_id
77 87
78 88 def delete_notebook_id(self, notebook_id):
79 89 """Delete a notebook's id only. This doesn't delete the actual notebook."""
80 90 name = self.mapping[notebook_id]
81 91 del self.mapping[notebook_id]
82 92 del self.rev_mapping[name]
83 93
84 94 def notebook_exists(self, notebook_id):
85 95 """Does a notebook exist?"""
86 96 if notebook_id not in self.mapping:
87 97 return False
88 98 path = self.get_path_by_name(self.mapping[notebook_id])
89 99 return os.path.isfile(path)
90 100
91 101 def find_path(self, notebook_id):
92 102 """Return a full path to a notebook given its notebook_id."""
93 103 try:
94 104 name = self.mapping[notebook_id]
95 105 except KeyError:
96 106 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
97 107 return self.get_path_by_name(name)
98 108
99 109 def get_path_by_name(self, name):
100 110 """Return a full path to a notebook given its name."""
101 111 filename = name + self.filename_ext
102 112 path = os.path.join(self.notebook_dir, filename)
103 113 return path
104 114
105 115 def get_notebook(self, notebook_id, format=u'json'):
106 116 """Get the representation of a notebook in format by notebook_id."""
107 117 format = unicode(format)
108 118 if format not in self.allowed_formats:
109 119 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
110 120 last_modified, nb = self.get_notebook_object(notebook_id)
111 121 data = current.writes(nb, format)
112 122 name = nb.get('name','notebook')
113 123 return last_modified, name, data
114 124
115 125 def get_notebook_object(self, notebook_id):
116 126 """Get the NotebookNode representation of a notebook by notebook_id."""
117 127 path = self.find_path(notebook_id)
118 128 if not os.path.isfile(path):
119 129 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
120 130 info = os.stat(path)
121 131 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
122 132 with open(path,'r') as f:
123 133 s = f.read()
124 134 try:
125 135 # v1 and v2 and json in the .ipynb files.
126 136 nb = current.reads(s, u'json')
127 137 except:
128 138 raise web.HTTPError(500, u'Unreadable JSON notebook.')
129 139 if 'name' not in nb:
130 140 nb.name = os.path.split(path)[-1].split(u'.')[0]
131 141 return last_modified, nb
132 142
133 143 def save_new_notebook(self, data, name=None, format=u'json'):
134 144 """Save a new notebook and return its notebook_id.
135 145
136 146 If a name is passed in, it overrides any values in the notebook data
137 147 and the value in the data is updated to use that value.
138 148 """
139 149 if format not in self.allowed_formats:
140 150 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
141 151
142 152 try:
143 153 nb = current.reads(data, format)
144 154 except:
145 155 raise web.HTTPError(400, u'Invalid JSON data')
146 156
147 157 if name is None:
148 158 try:
149 159 name = nb.metadata.name
150 160 except AttributeError:
151 161 raise web.HTTPError(400, u'Missing notebook name')
152 162 nb.metadata.name = name
153 163
154 164 notebook_id = self.new_notebook_id(name)
155 165 self.save_notebook_object(notebook_id, nb)
156 166 return notebook_id
157 167
158 168 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
159 169 """Save an existing notebook by notebook_id."""
160 170 if format not in self.allowed_formats:
161 171 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
162 172
163 173 try:
164 174 nb = current.reads(data, format)
165 175 except:
166 176 raise web.HTTPError(400, u'Invalid JSON data')
167 177
168 178 if name is not None:
169 179 nb.metadata.name = name
170 180 self.save_notebook_object(notebook_id, nb)
171 181
172 182 def save_notebook_object(self, notebook_id, nb):
173 183 """Save an existing notebook object by notebook_id."""
174 184 if notebook_id not in self.mapping:
175 185 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
176 186 old_name = self.mapping[notebook_id]
177 187 try:
178 188 new_name = nb.metadata.name
179 189 except AttributeError:
180 190 raise web.HTTPError(400, u'Missing notebook name')
181 191 path = self.get_path_by_name(new_name)
182 192 try:
183 193 with open(path,'w') as f:
184 194 current.write(nb, f, u'json')
185 195 except:
186 196 raise web.HTTPError(400, u'Unexpected error while saving notebook')
187 197 if old_name != new_name:
188 198 old_path = self.get_path_by_name(old_name)
189 199 if os.path.isfile(old_path):
190 200 os.unlink(old_path)
191 201 self.mapping[notebook_id] = new_name
192 202 self.rev_mapping[new_name] = notebook_id
193 203
194 204 def delete_notebook(self, notebook_id):
195 205 """Delete notebook by notebook_id."""
196 206 path = self.find_path(notebook_id)
197 207 if not os.path.isfile(path):
198 208 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
199 209 os.unlink(path)
200 210 self.delete_notebook_id(notebook_id)
201 211
202 212 def new_notebook(self):
203 213 """Create a new notebook and returns its notebook_id."""
204 214 i = 0
205 215 while True:
206 216 name = u'Untitled%i' % i
207 217 path = self.get_path_by_name(name)
208 218 if not os.path.isfile(path):
209 219 break
210 220 else:
211 221 i = i+1
212 222 notebook_id = self.new_notebook_id(name)
213 223 metadata = current.new_metadata(name=name)
214 224 nb = current.new_notebook(metadata=metadata)
215 225 with open(path,'w') as f:
216 226 current.write(nb, f, u'json')
217 227 return notebook_id
218 228
General Comments 0
You need to be logged in to leave comments. Login now