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