##// END OF EJS Templates
Fix typo in help string
Fernando Perez -
Show More
@@ -1,264 +1,264 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.application import boolean_flag
26 from IPython.config.application import boolean_flag
27 from IPython.config.configurable import LoggingConfigurable
27 from IPython.config.configurable import LoggingConfigurable
28 from IPython.nbformat import current
28 from IPython.nbformat import current
29 from IPython.utils.traitlets import Unicode, List, Dict, Bool
29 from IPython.utils.traitlets import Unicode, List, Dict, Bool
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Aliases and Flags
32 # Aliases and Flags
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 manager_flags =boolean_flag('script', 'NotebookManager.save_script',
35 manager_flags =boolean_flag('script', 'NotebookManager.save_script',
36 'Auto-save a .py script everytime the .ipynb notebook is saved',
36 'Auto-save a .py script everytime the .ipynb notebook is saved',
37 'Do not auto-save .py scripts for every notebook')
37 'Do not auto-save .py scripts for every notebook')
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # Classes
40 # Classes
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 class NotebookManager(LoggingConfigurable):
43 class NotebookManager(LoggingConfigurable):
44
44
45 notebook_dir = Unicode(os.getcwd(), config=True, help="""
45 notebook_dir = Unicode(os.getcwd(), config=True, help="""
46 The directory to use for notebooks.
46 The directory to use for notebooks.
47 """)
47 """)
48
48
49 save_script = Bool(False, config=True,
49 save_script = Bool(False, config=True,
50 help="""Automaticall create a Python script when saving the notebook.
50 help="""Automatically create a Python script when saving the notebook.
51
51
52 For easier use of import, %run and %loadpy across notebooks, a
52 For easier use of import, %run and %loadpy across notebooks, a
53 <notebook-name>.py script will be created next to any
53 <notebook-name>.py script will be created next to any
54 <notebook-name>.ipynb on each save. This can also be set with the
54 <notebook-name>.ipynb on each save. This can also be set with the
55 short `--script` flag.
55 short `--script` flag.
56 """
56 """
57 )
57 )
58
58
59 filename_ext = Unicode(u'.ipynb')
59 filename_ext = Unicode(u'.ipynb')
60 allowed_formats = List([u'json',u'py'])
60 allowed_formats = List([u'json',u'py'])
61
61
62 # Map notebook_ids to notebook names
62 # Map notebook_ids to notebook names
63 mapping = Dict()
63 mapping = Dict()
64 # Map notebook names to notebook_ids
64 # Map notebook names to notebook_ids
65 rev_mapping = Dict()
65 rev_mapping = Dict()
66
66
67 def list_notebooks(self):
67 def list_notebooks(self):
68 """List all notebooks in the notebook dir.
68 """List all notebooks in the notebook dir.
69
69
70 This returns a list of dicts of the form::
70 This returns a list of dicts of the form::
71
71
72 dict(notebook_id=notebook,name=name)
72 dict(notebook_id=notebook,name=name)
73 """
73 """
74 names = glob.glob(os.path.join(self.notebook_dir,
74 names = glob.glob(os.path.join(self.notebook_dir,
75 '*' + self.filename_ext))
75 '*' + self.filename_ext))
76 names = [os.path.splitext(os.path.basename(name))[0]
76 names = [os.path.splitext(os.path.basename(name))[0]
77 for name in names]
77 for name in names]
78
78
79 data = []
79 data = []
80 for name in names:
80 for name in names:
81 if name not in self.rev_mapping:
81 if name not in self.rev_mapping:
82 notebook_id = self.new_notebook_id(name)
82 notebook_id = self.new_notebook_id(name)
83 else:
83 else:
84 notebook_id = self.rev_mapping[name]
84 notebook_id = self.rev_mapping[name]
85 data.append(dict(notebook_id=notebook_id,name=name))
85 data.append(dict(notebook_id=notebook_id,name=name))
86 data = sorted(data, key=lambda item: item['name'])
86 data = sorted(data, key=lambda item: item['name'])
87 return data
87 return data
88
88
89 def new_notebook_id(self, name):
89 def new_notebook_id(self, name):
90 """Generate a new notebook_id for a name and store its mappings."""
90 """Generate a new notebook_id for a name and store its mappings."""
91 # TODO: the following will give stable urls for notebooks, but unless
91 # TODO: the following will give stable urls for notebooks, but unless
92 # the notebooks are immediately redirected to their new urls when their
92 # the notebooks are immediately redirected to their new urls when their
93 # filemname changes, nasty inconsistencies result. So for now it's
93 # filemname changes, nasty inconsistencies result. So for now it's
94 # disabled and instead we use a random uuid4() call. But we leave the
94 # disabled and instead we use a random uuid4() call. But we leave the
95 # logic here so that we can later reactivate it, whhen the necessary
95 # logic here so that we can later reactivate it, whhen the necessary
96 # url redirection code is written.
96 # url redirection code is written.
97 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
97 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
98 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
98 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
99
99
100 notebook_id = unicode(uuid.uuid4())
100 notebook_id = unicode(uuid.uuid4())
101
101
102 self.mapping[notebook_id] = name
102 self.mapping[notebook_id] = name
103 self.rev_mapping[name] = notebook_id
103 self.rev_mapping[name] = notebook_id
104 return notebook_id
104 return notebook_id
105
105
106 def delete_notebook_id(self, notebook_id):
106 def delete_notebook_id(self, notebook_id):
107 """Delete a notebook's id only. This doesn't delete the actual notebook."""
107 """Delete a notebook's id only. This doesn't delete the actual notebook."""
108 name = self.mapping[notebook_id]
108 name = self.mapping[notebook_id]
109 del self.mapping[notebook_id]
109 del self.mapping[notebook_id]
110 del self.rev_mapping[name]
110 del self.rev_mapping[name]
111
111
112 def notebook_exists(self, notebook_id):
112 def notebook_exists(self, notebook_id):
113 """Does a notebook exist?"""
113 """Does a notebook exist?"""
114 if notebook_id not in self.mapping:
114 if notebook_id not in self.mapping:
115 return False
115 return False
116 path = self.get_path_by_name(self.mapping[notebook_id])
116 path = self.get_path_by_name(self.mapping[notebook_id])
117 return os.path.isfile(path)
117 return os.path.isfile(path)
118
118
119 def find_path(self, notebook_id):
119 def find_path(self, notebook_id):
120 """Return a full path to a notebook given its notebook_id."""
120 """Return a full path to a notebook given its notebook_id."""
121 try:
121 try:
122 name = self.mapping[notebook_id]
122 name = self.mapping[notebook_id]
123 except KeyError:
123 except KeyError:
124 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
124 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
125 return self.get_path_by_name(name)
125 return self.get_path_by_name(name)
126
126
127 def get_path_by_name(self, name):
127 def get_path_by_name(self, name):
128 """Return a full path to a notebook given its name."""
128 """Return a full path to a notebook given its name."""
129 filename = name + self.filename_ext
129 filename = name + self.filename_ext
130 path = os.path.join(self.notebook_dir, filename)
130 path = os.path.join(self.notebook_dir, filename)
131 return path
131 return path
132
132
133 def get_notebook(self, notebook_id, format=u'json'):
133 def get_notebook(self, notebook_id, format=u'json'):
134 """Get the representation of a notebook in format by notebook_id."""
134 """Get the representation of a notebook in format by notebook_id."""
135 format = unicode(format)
135 format = unicode(format)
136 if format not in self.allowed_formats:
136 if format not in self.allowed_formats:
137 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
137 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
138 last_modified, nb = self.get_notebook_object(notebook_id)
138 last_modified, nb = self.get_notebook_object(notebook_id)
139 kwargs = {}
139 kwargs = {}
140 if format == 'json':
140 if format == 'json':
141 # don't split lines for sending over the wire, because it
141 # don't split lines for sending over the wire, because it
142 # should match the Python in-memory format.
142 # should match the Python in-memory format.
143 kwargs['split_lines'] = False
143 kwargs['split_lines'] = False
144 data = current.writes(nb, format, **kwargs)
144 data = current.writes(nb, format, **kwargs)
145 name = nb.get('name','notebook')
145 name = nb.get('name','notebook')
146 return last_modified, name, data
146 return last_modified, name, data
147
147
148 def get_notebook_object(self, notebook_id):
148 def get_notebook_object(self, notebook_id):
149 """Get the NotebookNode representation of a notebook by notebook_id."""
149 """Get the NotebookNode representation of a notebook by notebook_id."""
150 path = self.find_path(notebook_id)
150 path = self.find_path(notebook_id)
151 if not os.path.isfile(path):
151 if not os.path.isfile(path):
152 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
152 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
153 info = os.stat(path)
153 info = os.stat(path)
154 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
154 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
155 with open(path,'r') as f:
155 with open(path,'r') as f:
156 s = f.read()
156 s = f.read()
157 try:
157 try:
158 # v1 and v2 and json in the .ipynb files.
158 # v1 and v2 and json in the .ipynb files.
159 nb = current.reads(s, u'json')
159 nb = current.reads(s, u'json')
160 except:
160 except:
161 raise web.HTTPError(500, u'Unreadable JSON notebook.')
161 raise web.HTTPError(500, u'Unreadable JSON notebook.')
162 if 'name' not in nb:
162 if 'name' not in nb:
163 nb.name = os.path.split(path)[-1].split(u'.')[0]
163 nb.name = os.path.split(path)[-1].split(u'.')[0]
164 return last_modified, nb
164 return last_modified, nb
165
165
166 def save_new_notebook(self, data, name=None, format=u'json'):
166 def save_new_notebook(self, data, name=None, format=u'json'):
167 """Save a new notebook and return its notebook_id.
167 """Save a new notebook and return its notebook_id.
168
168
169 If a name is passed in, it overrides any values in the notebook data
169 If a name is passed in, it overrides any values in the notebook data
170 and the value in the data is updated to use that value.
170 and the value in the data is updated to use that value.
171 """
171 """
172 if format not in self.allowed_formats:
172 if format not in self.allowed_formats:
173 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
173 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
174
174
175 try:
175 try:
176 nb = current.reads(data.decode('utf-8'), format)
176 nb = current.reads(data.decode('utf-8'), format)
177 except:
177 except:
178 raise web.HTTPError(400, u'Invalid JSON data')
178 raise web.HTTPError(400, u'Invalid JSON data')
179
179
180 if name is None:
180 if name is None:
181 try:
181 try:
182 name = nb.metadata.name
182 name = nb.metadata.name
183 except AttributeError:
183 except AttributeError:
184 raise web.HTTPError(400, u'Missing notebook name')
184 raise web.HTTPError(400, u'Missing notebook name')
185 nb.metadata.name = name
185 nb.metadata.name = name
186
186
187 notebook_id = self.new_notebook_id(name)
187 notebook_id = self.new_notebook_id(name)
188 self.save_notebook_object(notebook_id, nb)
188 self.save_notebook_object(notebook_id, nb)
189 return notebook_id
189 return notebook_id
190
190
191 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
191 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
192 """Save an existing notebook by notebook_id."""
192 """Save an existing notebook by notebook_id."""
193 if format not in self.allowed_formats:
193 if format not in self.allowed_formats:
194 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
194 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
195
195
196 try:
196 try:
197 nb = current.reads(data.decode('utf-8'), format)
197 nb = current.reads(data.decode('utf-8'), format)
198 except:
198 except:
199 raise web.HTTPError(400, u'Invalid JSON data')
199 raise web.HTTPError(400, u'Invalid JSON data')
200
200
201 if name is not None:
201 if name is not None:
202 nb.metadata.name = name
202 nb.metadata.name = name
203 self.save_notebook_object(notebook_id, nb)
203 self.save_notebook_object(notebook_id, nb)
204
204
205 def save_notebook_object(self, notebook_id, nb):
205 def save_notebook_object(self, notebook_id, nb):
206 """Save an existing notebook object by notebook_id."""
206 """Save an existing notebook object by notebook_id."""
207 if notebook_id not in self.mapping:
207 if notebook_id not in self.mapping:
208 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
208 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
209 old_name = self.mapping[notebook_id]
209 old_name = self.mapping[notebook_id]
210 try:
210 try:
211 new_name = nb.metadata.name
211 new_name = nb.metadata.name
212 except AttributeError:
212 except AttributeError:
213 raise web.HTTPError(400, u'Missing notebook name')
213 raise web.HTTPError(400, u'Missing notebook name')
214 path = self.get_path_by_name(new_name)
214 path = self.get_path_by_name(new_name)
215 try:
215 try:
216 with open(path,'w') as f:
216 with open(path,'w') as f:
217 current.write(nb, f, u'json')
217 current.write(nb, f, u'json')
218 except Exception as e:
218 except Exception as e:
219 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
219 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
220 # save .py script as well
220 # save .py script as well
221 if self.save_script:
221 if self.save_script:
222 pypath = os.path.splitext(path)[0] + '.py'
222 pypath = os.path.splitext(path)[0] + '.py'
223 try:
223 try:
224 with open(pypath,'w') as f:
224 with open(pypath,'w') as f:
225 current.write(nb, f, u'py')
225 current.write(nb, f, u'py')
226 except Exception as e:
226 except Exception as e:
227 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
227 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
228
228
229 if old_name != new_name:
229 if old_name != new_name:
230 old_path = self.get_path_by_name(old_name)
230 old_path = self.get_path_by_name(old_name)
231 if os.path.isfile(old_path):
231 if os.path.isfile(old_path):
232 os.unlink(old_path)
232 os.unlink(old_path)
233 if self.save_script:
233 if self.save_script:
234 old_pypath = os.path.splitext(old_path)[0] + '.py'
234 old_pypath = os.path.splitext(old_path)[0] + '.py'
235 if os.path.isfile(old_pypath):
235 if os.path.isfile(old_pypath):
236 os.unlink(old_pypath)
236 os.unlink(old_pypath)
237 self.mapping[notebook_id] = new_name
237 self.mapping[notebook_id] = new_name
238 self.rev_mapping[new_name] = notebook_id
238 self.rev_mapping[new_name] = notebook_id
239
239
240 def delete_notebook(self, notebook_id):
240 def delete_notebook(self, notebook_id):
241 """Delete notebook by notebook_id."""
241 """Delete notebook by notebook_id."""
242 path = self.find_path(notebook_id)
242 path = self.find_path(notebook_id)
243 if not os.path.isfile(path):
243 if not os.path.isfile(path):
244 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
244 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
245 os.unlink(path)
245 os.unlink(path)
246 self.delete_notebook_id(notebook_id)
246 self.delete_notebook_id(notebook_id)
247
247
248 def new_notebook(self):
248 def new_notebook(self):
249 """Create a new notebook and returns its notebook_id."""
249 """Create a new notebook and returns its notebook_id."""
250 i = 0
250 i = 0
251 while True:
251 while True:
252 name = u'Untitled%i' % i
252 name = u'Untitled%i' % i
253 path = self.get_path_by_name(name)
253 path = self.get_path_by_name(name)
254 if not os.path.isfile(path):
254 if not os.path.isfile(path):
255 break
255 break
256 else:
256 else:
257 i = i+1
257 i = i+1
258 notebook_id = self.new_notebook_id(name)
258 notebook_id = self.new_notebook_id(name)
259 metadata = current.new_metadata(name=name)
259 metadata = current.new_metadata(name=name)
260 nb = current.new_notebook(metadata=metadata)
260 nb = current.new_notebook(metadata=metadata)
261 with open(path,'w') as f:
261 with open(path,'w') as f:
262 current.write(nb, f, u'json')
262 current.write(nb, f, u'json')
263 return notebook_id
263 return notebook_id
264
264
General Comments 0
You need to be logged in to leave comments. Login now