##// END OF EJS Templates
fix `--notebook-dir` configurable when there is no trailing slash
MinRK -
Show More
@@ -1,224 +1,223 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 * Zach Sailer
6 * Zach Sailer
7 """
7 """
8
8
9 #-----------------------------------------------------------------------------
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
10 # Copyright (C) 2011 The IPython Development Team
11 #
11 #
12 # Distributed under the terms of the BSD License. The full license is in
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15
15
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 # Imports
17 # Imports
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19
19
20 import os
20 import os
21 import uuid
21 import uuid
22 from urllib import quote, unquote
22 from urllib import quote, unquote
23
23
24 from tornado import web
24 from tornado import web
25
25
26 from IPython.html.utils import url_path_join
26 from IPython.html.utils import url_path_join
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 List, Dict, Unicode, TraitError
29 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
30
30
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32 # Classes
32 # Classes
33 #-----------------------------------------------------------------------------
33 #-----------------------------------------------------------------------------
34
34
35 class NotebookManager(LoggingConfigurable):
35 class NotebookManager(LoggingConfigurable):
36
36
37 # Todo:
37 # Todo:
38 # The notebook_dir attribute is used to mean a couple of different things:
38 # The notebook_dir attribute is used to mean a couple of different things:
39 # 1. Where the notebooks are stored if FileNotebookManager is used.
39 # 1. Where the notebooks are stored if FileNotebookManager is used.
40 # 2. The cwd of the kernel for a project.
40 # 2. The cwd of the kernel for a project.
41 # Right now we use this attribute in a number of different places and
41 # Right now we use this attribute in a number of different places and
42 # we are going to have to disentangle all of this.
42 # we are going to have to disentangle all of this.
43 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
43 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
44 The directory to use for notebooks.
44 The directory to use for notebooks.
45 """)
45 """)
46
46
47 filename_ext = Unicode(u'.ipynb')
47 filename_ext = Unicode(u'.ipynb')
48
48
49 def named_notebook_path(self, notebook_path):
49 def named_notebook_path(self, notebook_path):
50 """Given notebook_path (*always* a URL path to notebook), returns a
50 """Given notebook_path (*always* a URL path to notebook), returns a
51 (name, path) tuple, where name is a .ipynb file, and path is the
51 (name, path) tuple, where name is a .ipynb file, and path is the
52 URL path that describes the file system path for the file.
52 URL path that describes the file system path for the file.
53 It *always* starts *and* ends with a '/' character.
53 It *always* starts *and* ends with a '/' character.
54
54
55 Parameters
55 Parameters
56 ----------
56 ----------
57 notebook_path : string
57 notebook_path : string
58 A path that may be a .ipynb name or a directory
58 A path that may be a .ipynb name or a directory
59
59
60 Returns
60 Returns
61 -------
61 -------
62 name : string or None
62 name : string or None
63 the filename of the notebook, or None if not a .ipynb extension
63 the filename of the notebook, or None if not a .ipynb extension
64 path : string
64 path : string
65 the path to the directory which contains the notebook
65 the path to the directory which contains the notebook
66 """
66 """
67 names = notebook_path.split('/')
67 names = notebook_path.split('/')
68 names = [n for n in names if n != ''] # remove duplicate splits
68 names = [n for n in names if n != ''] # remove duplicate splits
69
69
70 names = [''] + names
70 names = [''] + names
71
71
72 if names and names[-1].endswith(".ipynb"):
72 if names and names[-1].endswith(".ipynb"):
73 name = names[-1]
73 name = names[-1]
74 path = "/".join(names[:-1]) + '/'
74 path = "/".join(names[:-1]) + '/'
75 else:
75 else:
76 name = None
76 name = None
77 path = "/".join(names) + '/'
77 path = "/".join(names) + '/'
78 return name, path
78 return name, path
79
79
80 def get_os_path(self, fname=None, path='/'):
80 def get_os_path(self, fname=None, path='/'):
81 """Given a notebook name and a URL path, return its file system
81 """Given a notebook name and a URL path, return its file system
82 path.
82 path.
83
83
84 Parameters
84 Parameters
85 ----------
85 ----------
86 fname : string
86 fname : string
87 The name of a notebook file with the .ipynb extension
87 The name of a notebook file with the .ipynb extension
88 path : string
88 path : string
89 The relative URL path (with '/' as separator) to the named
89 The relative URL path (with '/' as separator) to the named
90 notebook.
90 notebook.
91
91
92 Returns
92 Returns
93 -------
93 -------
94 path : string
94 path : string
95 A file system path that combines notebook_dir (location where
95 A file system path that combines notebook_dir (location where
96 server started), the relative path, and the filename with the
96 server started), the relative path, and the filename with the
97 current operating system's url.
97 current operating system's url.
98 """
98 """
99 parts = path.split('/')
99 parts = path.split('/')
100 parts = [p for p in parts if p != ''] # remove duplicate splits
100 parts = [p for p in parts if p != ''] # remove duplicate splits
101 if fname is not None:
101 if fname is not None:
102 parts += [fname]
102 parts += [fname]
103 path = os.path.join(self.notebook_dir, *parts)
103 path = os.path.join(self.notebook_dir, *parts)
104 return path
104 return path
105
105
106 def url_encode(self, path):
106 def url_encode(self, path):
107 """Takes a URL path with special characters and returns
107 """Takes a URL path with special characters and returns
108 the path with all these characters URL encoded"""
108 the path with all these characters URL encoded"""
109 parts = path.split('/')
109 parts = path.split('/')
110 return '/'.join([quote(p) for p in parts])
110 return '/'.join([quote(p) for p in parts])
111
111
112 def url_decode(self, path):
112 def url_decode(self, path):
113 """Takes a URL path with encoded special characters and
113 """Takes a URL path with encoded special characters and
114 returns the URL with special characters decoded"""
114 returns the URL with special characters decoded"""
115 parts = path.split('/')
115 parts = path.split('/')
116 return '/'.join([unquote(p) for p in parts])
116 return '/'.join([unquote(p) for p in parts])
117
117
118 def _notebook_dir_changed(self, name, old, new):
118 def _notebook_dir_changed(self, name, old, new):
119 """Do a bit of validation of the notebook dir."""
119 """Do a bit of validation of the notebook dir."""
120 if not os.path.isabs(new):
120 if not os.path.isabs(new):
121 # If we receive a non-absolute path, make it absolute.
121 # If we receive a non-absolute path, make it absolute.
122 abs_new = os.path.abspath(new)
122 self.notebook_dir = os.path.abspath(new)
123 self.notebook_dir = os.path.dirname(abs_new)
124 return
123 return
125 if os.path.exists(new) and not os.path.isdir(new):
124 if os.path.exists(new) and not os.path.isdir(new):
126 raise TraitError("notebook dir %r is not a directory" % new)
125 raise TraitError("notebook dir %r is not a directory" % new)
127 if not os.path.exists(new):
126 if not os.path.exists(new):
128 self.log.info("Creating notebook dir %s", new)
127 self.log.info("Creating notebook dir %s", new)
129 try:
128 try:
130 os.mkdir(new)
129 os.mkdir(new)
131 except:
130 except:
132 raise TraitError("Couldn't create notebook dir %r" % new)
131 raise TraitError("Couldn't create notebook dir %r" % new)
133
132
134 # Main notebook API
133 # Main notebook API
135
134
136 def increment_filename(self, basename, path='/'):
135 def increment_filename(self, basename, path='/'):
137 """Increment a notebook filename without the .ipynb to make it unique.
136 """Increment a notebook filename without the .ipynb to make it unique.
138
137
139 Parameters
138 Parameters
140 ----------
139 ----------
141 basename : unicode
140 basename : unicode
142 The name of a notebook without the ``.ipynb`` file extension.
141 The name of a notebook without the ``.ipynb`` file extension.
143 path : unicode
142 path : unicode
144 The URL path of the notebooks directory
143 The URL path of the notebooks directory
145 """
144 """
146 return basename
145 return basename
147
146
148 def list_notebooks(self):
147 def list_notebooks(self):
149 """Return a list of notebook dicts without content.
148 """Return a list of notebook dicts without content.
150
149
151 This returns a list of dicts, each of the form::
150 This returns a list of dicts, each of the form::
152
151
153 dict(notebook_id=notebook,name=name)
152 dict(notebook_id=notebook,name=name)
154
153
155 This list of dicts should be sorted by name::
154 This list of dicts should be sorted by name::
156
155
157 data = sorted(data, key=lambda item: item['name'])
156 data = sorted(data, key=lambda item: item['name'])
158 """
157 """
159 raise NotImplementedError('must be implemented in a subclass')
158 raise NotImplementedError('must be implemented in a subclass')
160
159
161 def get_notebook_model(self, name, path='/', content=True):
160 def get_notebook_model(self, name, path='/', content=True):
162 """Get the notebook model with or without content."""
161 """Get the notebook model with or without content."""
163 raise NotImplementedError('must be implemented in a subclass')
162 raise NotImplementedError('must be implemented in a subclass')
164
163
165 def save_notebook_model(self, model, name, path='/'):
164 def save_notebook_model(self, model, name, path='/'):
166 """Save the notebook model and return the model with no content."""
165 """Save the notebook model and return the model with no content."""
167 raise NotImplementedError('must be implemented in a subclass')
166 raise NotImplementedError('must be implemented in a subclass')
168
167
169 def update_notebook_model(self, model, name, path='/'):
168 def update_notebook_model(self, model, name, path='/'):
170 """Update the notebook model and return the model with no content."""
169 """Update the notebook model and return the model with no content."""
171 raise NotImplementedError('must be implemented in a subclass')
170 raise NotImplementedError('must be implemented in a subclass')
172
171
173 def delete_notebook_model(self, name, path):
172 def delete_notebook_model(self, name, path):
174 """Delete notebook by name and path."""
173 """Delete notebook by name and path."""
175 raise NotImplementedError('must be implemented in a subclass')
174 raise NotImplementedError('must be implemented in a subclass')
176
175
177 def create_notebook_model(self, model=None, path='/'):
176 def create_notebook_model(self, model=None, path='/'):
178 """Create a new untitled notebook and return its model with no content."""
177 """Create a new untitled notebook and return its model with no content."""
179 name = self.increment_filename('Untitled', path)
178 name = self.increment_filename('Untitled', path)
180 if model is None:
179 if model is None:
181 model = {}
180 model = {}
182 metadata = current.new_metadata(name=u'')
181 metadata = current.new_metadata(name=u'')
183 nb = current.new_notebook(metadata=metadata)
182 nb = current.new_notebook(metadata=metadata)
184 model['content'] = nb
183 model['content'] = nb
185 model['name'] = name
184 model['name'] = name
186 model['path'] = path
185 model['path'] = path
187 model = self.save_notebook_model(model, name, path)
186 model = self.save_notebook_model(model, name, path)
188 return model
187 return model
189
188
190 def copy_notebook(self, name, path='/', content=False):
189 def copy_notebook(self, name, path='/', content=False):
191 """Copy an existing notebook and return its new model."""
190 """Copy an existing notebook and return its new model."""
192 model = self.get_notebook_model(name, path)
191 model = self.get_notebook_model(name, path)
193 name = os.path.splitext(name)[0] + '-Copy'
192 name = os.path.splitext(name)[0] + '-Copy'
194 name = self.increment_filename(name, path) + self.filename_ext
193 name = self.increment_filename(name, path) + self.filename_ext
195 model['name'] = name
194 model['name'] = name
196 model = self.save_notebook_model(model, name, path, content=content)
195 model = self.save_notebook_model(model, name, path, content=content)
197 return model
196 return model
198
197
199 # Checkpoint-related
198 # Checkpoint-related
200
199
201 def create_checkpoint(self, name, path='/'):
200 def create_checkpoint(self, name, path='/'):
202 """Create a checkpoint of the current state of a notebook
201 """Create a checkpoint of the current state of a notebook
203
202
204 Returns a checkpoint_id for the new checkpoint.
203 Returns a checkpoint_id for the new checkpoint.
205 """
204 """
206 raise NotImplementedError("must be implemented in a subclass")
205 raise NotImplementedError("must be implemented in a subclass")
207
206
208 def list_checkpoints(self, name, path='/'):
207 def list_checkpoints(self, name, path='/'):
209 """Return a list of checkpoints for a given notebook"""
208 """Return a list of checkpoints for a given notebook"""
210 return []
209 return []
211
210
212 def restore_checkpoint(self, checkpoint_id, name, path='/'):
211 def restore_checkpoint(self, checkpoint_id, name, path='/'):
213 """Restore a notebook from one of its checkpoints"""
212 """Restore a notebook from one of its checkpoints"""
214 raise NotImplementedError("must be implemented in a subclass")
213 raise NotImplementedError("must be implemented in a subclass")
215
214
216 def delete_checkpoint(self, checkpoint_id, name, path='/'):
215 def delete_checkpoint(self, checkpoint_id, name, path='/'):
217 """delete a checkpoint for a notebook"""
216 """delete a checkpoint for a notebook"""
218 raise NotImplementedError("must be implemented in a subclass")
217 raise NotImplementedError("must be implemented in a subclass")
219
218
220 def log_info(self):
219 def log_info(self):
221 self.log.info(self.info_string())
220 self.log.info(self.info_string())
222
221
223 def info_string(self):
222 def info_string(self):
224 return "Serving notebooks"
223 return "Serving notebooks"
General Comments 0
You need to be logged in to leave comments. Login now