##// END OF EJS Templates
cleaning up named_notebook_path
Paul Ivanov -
Show More
@@ -1,257 +1,271 b''
1 """A base class notebook manager.
1 """A base class notebook manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 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 os
19 import os
20 import uuid
20 import uuid
21
21
22 from tornado import web
22 from tornado import web
23 from urllib import quote, unquote
23 from urllib import quote, unquote
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 List, Dict, Unicode, TraitError
27 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Classes
30 # Classes
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33 class NotebookManager(LoggingConfigurable):
33 class NotebookManager(LoggingConfigurable):
34
34
35 # Todo:
35 # Todo:
36 # The notebook_dir attribute is used to mean a couple of different things:
36 # The notebook_dir attribute is used to mean a couple of different things:
37 # 1. Where the notebooks are stored if FileNotebookManager is used.
37 # 1. Where the notebooks are stored if FileNotebookManager is used.
38 # 2. The cwd of the kernel for a project.
38 # 2. The cwd of the kernel for a project.
39 # Right now we use this attribute in a number of different places and
39 # Right now we use this attribute in a number of different places and
40 # we are going to have to disentangle all of this.
40 # we are going to have to disentangle all of this.
41 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
41 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
42 The directory to use for notebooks.
42 The directory to use for notebooks.
43 """)
43 """)
44
44
45 def named_notebook_path(self, notebook_path):
45 def named_notebook_path(self, notebook_path):
46
46 """Given a notebook_path name, returns a (name, path) tuple, where
47 name is a .ipynb file, and path is the directory for the file.
48
49 Parameters
50 ----------
51 notebook_path : string
52 A path that may be a .ipynb name or a directory
53
54 Returns
55 -------
56 name : string or None
57 the filename of the notebook, or None if not a .ipynb extesnsion
58 path : string or None
59 the path to the directory which contains the notebook
60 """
47 names = notebook_path.split('/')
61 names = notebook_path.split('/')
48 if len(names) > 1:
62 if len(names) > 1:
49 name = names[-1]
63 name = names[-1]
50 if name.endswith(".ipynb"):
64 if name.endswith(".ipynb"):
51 name = name
65 name = name
52 path = notebook_path[0:-len(name)-1]+'/'
66 path = notebook_path[:-1]+'/'
53 else:
67 else:
54 name = None
68 name = None
55 path = notebook_path+'/'
69 path = notebook_path+'/'
56 else:
70 else:
57 name = names[0]
71 name = names[0]
58 if name.endswith(".ipynb"):
72 if name.endswith(".ipynb"):
59 name = name
73 name = name
60 path = None
74 path = None
61 else:
75 else:
62 name = None
76 name = None
63 path = notebook_path+'/'
77 path = notebook_path+'/'
64 return name, path
78 return name, path
65
79
66 def url_encode(self, path):
80 def url_encode(self, path):
67 parts = path.split('/')
81 parts = path.split('/')
68 return os.path.join(*[quote(p) for p in parts])
82 return os.path.join(*[quote(p) for p in parts])
69
83
70 def url_decode(self, path):
84 def url_decode(self, path):
71 parts = path.split('/')
85 parts = path.split('/')
72 return os.path.join(*[unquote(p) for p in parts])
86 return os.path.join(*[unquote(p) for p in parts])
73
87
74 def _notebook_dir_changed(self, new):
88 def _notebook_dir_changed(self, new):
75 """do a bit of validation of the notebook dir"""
89 """do a bit of validation of the notebook dir"""
76 if not os.path.isabs(new):
90 if not os.path.isabs(new):
77 # If we receive a non-absolute path, make it absolute.
91 # If we receive a non-absolute path, make it absolute.
78 abs_new = os.path.abspath(new)
92 abs_new = os.path.abspath(new)
79 #self.notebook_dir = os.path.dirname(abs_new)
93 #self.notebook_dir = os.path.dirname(abs_new)
80 return
94 return
81 if os.path.exists(new) and not os.path.isdir(new):
95 if os.path.exists(new) and not os.path.isdir(new):
82 raise TraitError("notebook dir %r is not a directory" % new)
96 raise TraitError("notebook dir %r is not a directory" % new)
83 if not os.path.exists(new):
97 if not os.path.exists(new):
84 self.log.info("Creating notebook dir %s", new)
98 self.log.info("Creating notebook dir %s", new)
85 try:
99 try:
86 os.mkdir(new)
100 os.mkdir(new)
87 except:
101 except:
88 raise TraitError("Couldn't create notebook dir %r" % new)
102 raise TraitError("Couldn't create notebook dir %r" % new)
89
103
90 allowed_formats = List([u'json',u'py'])
104 allowed_formats = List([u'json',u'py'])
91
105
92 def add_new_folder(self, path=None):
106 def add_new_folder(self, path=None):
93 new_path = os.path.join(self.notebook_dir, path)
107 new_path = os.path.join(self.notebook_dir, path)
94 if not os.path.exists(new_path):
108 if not os.path.exists(new_path):
95 os.makedirs(new_path)
109 os.makedirs(new_path)
96 else:
110 else:
97 raise web.HTTPError(409, u'Directory already exists or creation permission not allowed.')
111 raise web.HTTPError(409, u'Directory already exists or creation permission not allowed.')
98
112
99 def load_notebook_names(self, path):
113 def load_notebook_names(self, path):
100 """Load the notebook names into memory.
114 """Load the notebook names into memory.
101
115
102 This should be called once immediately after the notebook manager
116 This should be called once immediately after the notebook manager
103 is created to load the existing notebooks into the mapping in
117 is created to load the existing notebooks into the mapping in
104 memory.
118 memory.
105 """
119 """
106 self.list_notebooks(path)
120 self.list_notebooks(path)
107
121
108 def list_notebooks(self):
122 def list_notebooks(self):
109 """List all notebooks.
123 """List all notebooks.
110
124
111 This returns a list of dicts, each of the form::
125 This returns a list of dicts, each of the form::
112
126
113 dict(notebook_id=notebook,name=name)
127 dict(notebook_id=notebook,name=name)
114
128
115 This list of dicts should be sorted by name::
129 This list of dicts should be sorted by name::
116
130
117 data = sorted(data, key=lambda item: item['name'])
131 data = sorted(data, key=lambda item: item['name'])
118 """
132 """
119 raise NotImplementedError('must be implemented in a subclass')
133 raise NotImplementedError('must be implemented in a subclass')
120
134
121
135
122 def notebook_exists(self, notebook_path):
136 def notebook_exists(self, notebook_path):
123 """Does a notebook exist?"""
137 """Does a notebook exist?"""
124
138
125
139
126 def notebook_model(self, notebook_name, notebook_path=None, content=True):
140 def notebook_model(self, notebook_name, notebook_path=None, content=True):
127 """ Creates the standard notebook model """
141 """ Creates the standard notebook model """
128 last_modified, contents = self.read_notebook_object(notebook_name, notebook_path)
142 last_modified, contents = self.read_notebook_object(notebook_name, notebook_path)
129 model = {"name": notebook_name,
143 model = {"name": notebook_name,
130 "path": notebook_path,
144 "path": notebook_path,
131 "last_modified (UTC)": last_modified.ctime()}
145 "last_modified (UTC)": last_modified.ctime()}
132 if content == True:
146 if content == True:
133 model['content'] = contents
147 model['content'] = contents
134 return model
148 return model
135
149
136 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
150 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
137 """Get the representation of a notebook in format by notebook_name."""
151 """Get the representation of a notebook in format by notebook_name."""
138 format = unicode(format)
152 format = unicode(format)
139 if format not in self.allowed_formats:
153 if format not in self.allowed_formats:
140 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
154 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
141 kwargs = {}
155 kwargs = {}
142 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
156 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
143 if format == 'json':
157 if format == 'json':
144 # don't split lines for sending over the wire, because it
158 # don't split lines for sending over the wire, because it
145 # should match the Python in-memory format.
159 # should match the Python in-memory format.
146 kwargs['split_lines'] = False
160 kwargs['split_lines'] = False
147 representation = current.writes(nb, format, **kwargs)
161 representation = current.writes(nb, format, **kwargs)
148 name = nb.metadata.get('name', 'notebook')
162 name = nb.metadata.get('name', 'notebook')
149 return last_mod, representation, name
163 return last_mod, representation, name
150
164
151 def read_notebook_object(self, notebook_name, notebook_path=None):
165 def read_notebook_object(self, notebook_name, notebook_path=None):
152 """Get the object representation of a notebook by notebook_id."""
166 """Get the object representation of a notebook by notebook_id."""
153 raise NotImplementedError('must be implemented in a subclass')
167 raise NotImplementedError('must be implemented in a subclass')
154
168
155 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
169 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
156 """Save a new notebook and return its name.
170 """Save a new notebook and return its name.
157
171
158 If a name is passed in, it overrides any values in the notebook data
172 If a name is passed in, it overrides any values in the notebook data
159 and the value in the data is updated to use that value.
173 and the value in the data is updated to use that value.
160 """
174 """
161 if format not in self.allowed_formats:
175 if format not in self.allowed_formats:
162 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
176 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
163
177
164 try:
178 try:
165 nb = current.reads(data.decode('utf-8'), format)
179 nb = current.reads(data.decode('utf-8'), format)
166 except:
180 except:
167 raise web.HTTPError(400, u'Invalid JSON data')
181 raise web.HTTPError(400, u'Invalid JSON data')
168
182
169 if name is None:
183 if name is None:
170 try:
184 try:
171 name = nb.metadata.name
185 name = nb.metadata.name
172 except AttributeError:
186 except AttributeError:
173 raise web.HTTPError(400, u'Missing notebook name')
187 raise web.HTTPError(400, u'Missing notebook name')
174 nb.metadata.name = name
188 nb.metadata.name = name
175
189
176 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
190 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
177 return notebook_name
191 return notebook_name
178
192
179 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
193 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
180 """Save an existing notebook by notebook_name."""
194 """Save an existing notebook by notebook_name."""
181 if format not in self.allowed_formats:
195 if format not in self.allowed_formats:
182 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
196 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
183
197
184 try:
198 try:
185 nb = current.reads(data.decode('utf-8'), format)
199 nb = current.reads(data.decode('utf-8'), format)
186 except:
200 except:
187 raise web.HTTPError(400, u'Invalid JSON data')
201 raise web.HTTPError(400, u'Invalid JSON data')
188
202
189 if name is not None:
203 if name is not None:
190 nb.metadata.name = name
204 nb.metadata.name = name
191 self.write_notebook_object(nb, name, notebook_path, new_name)
205 self.write_notebook_object(nb, name, notebook_path, new_name)
192
206
193 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
207 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
194 """Write a notebook object and return its notebook_name.
208 """Write a notebook object and return its notebook_name.
195
209
196 If notebook_name is None, this method should create a new notebook_name.
210 If notebook_name is None, this method should create a new notebook_name.
197 If notebook_name is not None, this method should check to make sure it
211 If notebook_name is not None, this method should check to make sure it
198 exists and is valid.
212 exists and is valid.
199 """
213 """
200 raise NotImplementedError('must be implemented in a subclass')
214 raise NotImplementedError('must be implemented in a subclass')
201
215
202 def delete_notebook(self, notebook_name, notebook_path):
216 def delete_notebook(self, notebook_name, notebook_path):
203 """Delete notebook by notebook_id."""
217 """Delete notebook by notebook_id."""
204 raise NotImplementedError('must be implemented in a subclass')
218 raise NotImplementedError('must be implemented in a subclass')
205
219
206 def increment_filename(self, name):
220 def increment_filename(self, name):
207 """Increment a filename to make it unique.
221 """Increment a filename to make it unique.
208
222
209 This exists for notebook stores that must have unique names. When a notebook
223 This exists for notebook stores that must have unique names. When a notebook
210 is created or copied this method constructs a unique filename, typically
224 is created or copied this method constructs a unique filename, typically
211 by appending an integer to the name.
225 by appending an integer to the name.
212 """
226 """
213 return name
227 return name
214
228
215 def new_notebook(self, notebook_path=None):
229 def new_notebook(self, notebook_path=None):
216 """Create a new notebook and return its notebook_name."""
230 """Create a new notebook and return its notebook_name."""
217 name = self.increment_filename('Untitled', notebook_path)
231 name = self.increment_filename('Untitled', notebook_path)
218 metadata = current.new_metadata(name=name)
232 metadata = current.new_metadata(name=name)
219 nb = current.new_notebook(metadata=metadata)
233 nb = current.new_notebook(metadata=metadata)
220 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
234 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
221 return notebook_name
235 return notebook_name
222
236
223 def copy_notebook(self, name, path=None):
237 def copy_notebook(self, name, path=None):
224 """Copy an existing notebook and return its new notebook_name."""
238 """Copy an existing notebook and return its new notebook_name."""
225 last_mod, nb = self.read_notebook_object(name, path)
239 last_mod, nb = self.read_notebook_object(name, path)
226 name = nb.metadata.name + '-Copy'
240 name = nb.metadata.name + '-Copy'
227 name = self.increment_filename(name, path)
241 name = self.increment_filename(name, path)
228 nb.metadata.name = name
242 nb.metadata.name = name
229 notebook_name = self.write_notebook_object(nb, notebook_path = path)
243 notebook_name = self.write_notebook_object(nb, notebook_path = path)
230 return notebook_name
244 return notebook_name
231
245
232 # Checkpoint-related
246 # Checkpoint-related
233
247
234 def create_checkpoint(self, notebook_name, notebook_path=None):
248 def create_checkpoint(self, notebook_name, notebook_path=None):
235 """Create a checkpoint of the current state of a notebook
249 """Create a checkpoint of the current state of a notebook
236
250
237 Returns a checkpoint_id for the new checkpoint.
251 Returns a checkpoint_id for the new checkpoint.
238 """
252 """
239 raise NotImplementedError("must be implemented in a subclass")
253 raise NotImplementedError("must be implemented in a subclass")
240
254
241 def list_checkpoints(self, notebook_name, notebook_path=None):
255 def list_checkpoints(self, notebook_name, notebook_path=None):
242 """Return a list of checkpoints for a given notebook"""
256 """Return a list of checkpoints for a given notebook"""
243 return []
257 return []
244
258
245 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
259 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
246 """Restore a notebook from one of its checkpoints"""
260 """Restore a notebook from one of its checkpoints"""
247 raise NotImplementedError("must be implemented in a subclass")
261 raise NotImplementedError("must be implemented in a subclass")
248
262
249 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
263 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
250 """delete a checkpoint for a notebook"""
264 """delete a checkpoint for a notebook"""
251 raise NotImplementedError("must be implemented in a subclass")
265 raise NotImplementedError("must be implemented in a subclass")
252
266
253 def log_info(self):
267 def log_info(self):
254 self.log.info(self.info_string())
268 self.log.info(self.info_string())
255
269
256 def info_string(self):
270 def info_string(self):
257 return "Serving notebooks"
271 return "Serving notebooks"
General Comments 0
You need to be logged in to leave comments. Login now