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