##// END OF EJS Templates
unicode!
Min RK -
Show More
@@ -1,337 +1,337 b''
1 1 """A base class for contents managers."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 from fnmatch import fnmatch
7 7 import itertools
8 8 import json
9 9 import os
10 10
11 11 from tornado.web import HTTPError
12 12
13 13 from IPython.config.configurable import LoggingConfigurable
14 14 from IPython.nbformat import sign, validate, ValidationError
15 15 from IPython.nbformat.v4 import new_notebook
16 16 from IPython.utils.traitlets import Instance, Unicode, List
17 17
18 18
19 19 class ContentsManager(LoggingConfigurable):
20 20 """Base class for serving files and directories.
21 21
22 22 This serves any text or binary file,
23 23 as well as directories,
24 24 with special handling for JSON notebook documents.
25 25
26 26 Most APIs take a path argument,
27 27 which is always an API-style unicode path,
28 28 and always refers to a directory.
29 29
30 30 - unicode, not url-escaped
31 31 - '/'-separated
32 32 - leading and trailing '/' will be stripped
33 33 - if unspecified, path defaults to '',
34 34 indicating the root path.
35 35
36 36 """
37 37
38 38 notary = Instance(sign.NotebookNotary)
39 39 def _notary_default(self):
40 40 return sign.NotebookNotary(parent=self)
41 41
42 42 hide_globs = List(Unicode, [
43 43 u'__pycache__', '*.pyc', '*.pyo',
44 44 '.DS_Store', '*.so', '*.dylib', '*~',
45 45 ], config=True, help="""
46 46 Glob patterns to hide in file and directory listings.
47 47 """)
48 48
49 49 untitled_notebook = Unicode("Untitled", config=True,
50 50 help="The base name used when creating untitled notebooks."
51 51 )
52 52
53 53 untitled_file = Unicode("untitled", config=True,
54 54 help="The base name used when creating untitled files."
55 55 )
56 56
57 57 untitled_directory = Unicode("Untitled Folder", config=True,
58 58 help="The base name used when creating untitled directories."
59 59 )
60 60
61 61 # ContentsManager API part 1: methods that must be
62 62 # implemented in subclasses.
63 63
64 64 def dir_exists(self, path):
65 65 """Does the API-style path (directory) actually exist?
66 66
67 67 Like os.path.isdir
68 68
69 69 Override this method in subclasses.
70 70
71 71 Parameters
72 72 ----------
73 73 path : string
74 74 The path to check
75 75
76 76 Returns
77 77 -------
78 78 exists : bool
79 79 Whether the path does indeed exist.
80 80 """
81 81 raise NotImplementedError
82 82
83 83 def is_hidden(self, path):
84 84 """Does the API style path correspond to a hidden directory or file?
85 85
86 86 Parameters
87 87 ----------
88 88 path : string
89 89 The path to check. This is an API path (`/` separated,
90 90 relative to root dir).
91 91
92 92 Returns
93 93 -------
94 94 hidden : bool
95 95 Whether the path is hidden.
96 96
97 97 """
98 98 raise NotImplementedError
99 99
100 100 def file_exists(self, path=''):
101 101 """Does a file exist at the given path?
102 102
103 103 Like os.path.isfile
104 104
105 105 Override this method in subclasses.
106 106
107 107 Parameters
108 108 ----------
109 109 name : string
110 110 The name of the file you are checking.
111 111 path : string
112 112 The relative path to the file's directory (with '/' as separator)
113 113
114 114 Returns
115 115 -------
116 116 exists : bool
117 117 Whether the file exists.
118 118 """
119 119 raise NotImplementedError('must be implemented in a subclass')
120 120
121 121 def exists(self, path):
122 122 """Does a file or directory exist at the given name and path?
123 123
124 124 Like os.path.exists
125 125
126 126 Parameters
127 127 ----------
128 128 path : string
129 129 The relative path to the file's directory (with '/' as separator)
130 130
131 131 Returns
132 132 -------
133 133 exists : bool
134 134 Whether the target exists.
135 135 """
136 136 return self.file_exists(path) or self.dir_exists(path)
137 137
138 138 def get_model(self, path, content=True):
139 139 """Get the model of a file or directory with or without content."""
140 140 raise NotImplementedError('must be implemented in a subclass')
141 141
142 142 def save(self, model, path):
143 143 """Save the file or directory and return the model with no content."""
144 144 raise NotImplementedError('must be implemented in a subclass')
145 145
146 146 def update(self, model, path):
147 147 """Update the file or directory and return the model with no content.
148 148
149 149 For use in PATCH requests, to enable renaming a file without
150 150 re-uploading its contents. Only used for renaming at the moment.
151 151 """
152 152 raise NotImplementedError('must be implemented in a subclass')
153 153
154 154 def delete(self, path):
155 155 """Delete file or directory by path."""
156 156 raise NotImplementedError('must be implemented in a subclass')
157 157
158 158 def create_checkpoint(self, path):
159 159 """Create a checkpoint of the current state of a file
160 160
161 161 Returns a checkpoint_id for the new checkpoint.
162 162 """
163 163 raise NotImplementedError("must be implemented in a subclass")
164 164
165 165 def list_checkpoints(self, path):
166 166 """Return a list of checkpoints for a given file"""
167 167 return []
168 168
169 169 def restore_checkpoint(self, checkpoint_id, path):
170 170 """Restore a file from one of its checkpoints"""
171 171 raise NotImplementedError("must be implemented in a subclass")
172 172
173 173 def delete_checkpoint(self, checkpoint_id, path):
174 174 """delete a checkpoint for a file"""
175 175 raise NotImplementedError("must be implemented in a subclass")
176 176
177 177 # ContentsManager API part 2: methods that have useable default
178 178 # implementations, but can be overridden in subclasses.
179 179
180 180 def info_string(self):
181 181 return "Serving contents"
182 182
183 183 def get_kernel_path(self, path, model=None):
184 184 """ Return the path to start kernel in """
185 185 return path
186 186
187 187 def increment_filename(self, filename, path=''):
188 188 """Increment a filename until it is unique.
189 189
190 190 Parameters
191 191 ----------
192 192 filename : unicode
193 193 The name of a file, including extension
194 194 path : unicode
195 195 The API path of the target's directory
196 196
197 197 Returns
198 198 -------
199 199 name : unicode
200 200 A filename that is unique, based on the input filename.
201 201 """
202 202 path = path.strip('/')
203 203 basename, ext = os.path.splitext(filename)
204 204 for i in itertools.count():
205 205 name = u'{basename}{i}{ext}'.format(basename=basename, i=i,
206 206 ext=ext)
207 if not self.file_exists('{}/{}'.format(path, name)):
207 if not self.file_exists(u'{}/{}'.format(path, name)):
208 208 break
209 209 return name
210 210
211 211 def validate_notebook_model(self, model):
212 212 """Add failed-validation message to model"""
213 213 try:
214 214 validate(model['content'])
215 215 except ValidationError as e:
216 model['message'] = 'Notebook Validation failed: {}:\n{}'.format(
216 model['message'] = u'Notebook Validation failed: {}:\n{}'.format(
217 217 e.message, json.dumps(e.instance, indent=1, default=lambda obj: '<UNKNOWN>'),
218 218 )
219 219 return model
220 220
221 221 def new(self, model=None, path='', ext='.ipynb'):
222 222 """Create a new file or directory and return its model with no content."""
223 223 path = path.strip('/')
224 224 if model is None:
225 225 model = {}
226 226 else:
227 227 model.pop('path', None)
228 228 if 'content' not in model and model.get('type', None) != 'directory':
229 229 if ext == '.ipynb':
230 230 model['content'] = new_notebook()
231 231 model['type'] = 'notebook'
232 232 model['format'] = 'json'
233 233 else:
234 234 model['content'] = ''
235 235 model['type'] = 'file'
236 236 model['format'] = 'text'
237 237 if self.dir_exists(path):
238 238 if model['type'] == 'directory':
239 239 untitled = self.untitled_directory
240 240 elif model['type'] == 'notebook':
241 241 untitled = self.untitled_notebook
242 242 elif model['type'] == 'file':
243 243 untitled = self.untitled_file
244 244 else:
245 245 raise HTTPError(400, "Unexpected model type: %r" % model['type'])
246 246
247 247 name = self.increment_filename(untitled + ext, path)
248 path = '{0}/{1}'.format(path, name)
248 path = u'{0}/{1}'.format(path, name)
249 249 model = self.save(model, path)
250 250 return model
251 251
252 252 def copy(self, from_path, to_path=None):
253 253 """Copy an existing file and return its new model.
254 254
255 255 If to_name not specified, increment `from_name-Copy#.ext`.
256 256
257 257 copy_from can be a full path to a file,
258 258 or just a base name. If a base name, `path` is used.
259 259 """
260 260 path = from_path.strip('/')
261 261 if '/' in path:
262 262 from_dir, from_name = path.rsplit('/', 1)
263 263 else:
264 264 from_dir = ''
265 265 from_name = path
266 266
267 267 model = self.get_model(path)
268 268 model.pop('path', None)
269 269 model.pop('name', None)
270 270 if model['type'] == 'directory':
271 271 raise HTTPError(400, "Can't copy directories")
272 272
273 273 if not to_path:
274 274 to_path = from_dir
275 275 if self.dir_exists(to_path):
276 276 base, ext = os.path.splitext(from_name)
277 277 copy_name = u'{0}-Copy{1}'.format(base, ext)
278 278 to_name = self.increment_filename(copy_name, to_path)
279 to_path = '{0}/{1}'.format(to_path, to_name)
279 to_path = u'{0}/{1}'.format(to_path, to_name)
280 280
281 281 model = self.save(model, to_path)
282 282 return model
283 283
284 284 def log_info(self):
285 285 self.log.info(self.info_string())
286 286
287 287 def trust_notebook(self, path):
288 288 """Explicitly trust a notebook
289 289
290 290 Parameters
291 291 ----------
292 292 path : string
293 293 The path of a notebook
294 294 """
295 295 model = self.get_model(path)
296 296 nb = model['content']
297 297 self.log.warn("Trusting notebook %s", path)
298 298 self.notary.mark_cells(nb, True)
299 299 self.save(model, path)
300 300
301 301 def check_and_sign(self, nb, path=''):
302 302 """Check for trusted cells, and sign the notebook.
303 303
304 304 Called as a part of saving notebooks.
305 305
306 306 Parameters
307 307 ----------
308 308 nb : dict
309 309 The notebook dict
310 310 path : string
311 311 The notebook's path (for logging)
312 312 """
313 313 if self.notary.check_cells(nb):
314 314 self.notary.sign(nb)
315 315 else:
316 316 self.log.warn("Saving untrusted notebook %s", path)
317 317
318 318 def mark_trusted_cells(self, nb, path=''):
319 319 """Mark cells as trusted if the notebook signature matches.
320 320
321 321 Called as a part of loading notebooks.
322 322
323 323 Parameters
324 324 ----------
325 325 nb : dict
326 326 The notebook object (in current nbformat)
327 327 path : string
328 328 The notebook's directory (for logging)
329 329 """
330 330 trusted = self.notary.check_signature(nb)
331 331 if not trusted:
332 332 self.log.warn("Notebook %s is not trusted", path)
333 333 self.notary.mark_cells(nb, trusted)
334 334
335 335 def should_list(self, name):
336 336 """Should this file/directory name be displayed in a listing?"""
337 337 return not any(fnmatch(name, glob) for glob in self.hide_globs)
General Comments 0
You need to be logged in to leave comments. Login now