##// END OF EJS Templates
Fix a couple of static analysis warnings
Thomas Kluyver -
Show More
@@ -1,480 +1,480 b''
1 1 """A notebook manager that uses the local file system for storage.
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 io
21 21 import os
22 22 import glob
23 23 import shutil
24 24
25 25 from tornado import web
26 26
27 27 from .nbmanager import NotebookManager
28 28 from IPython.nbformat import current
29 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
29 from IPython.utils.traitlets import Unicode, Bool, TraitError
30 30 from IPython.utils.py3compat import getcwd
31 31 from IPython.utils import tz
32 32 from IPython.html.utils import is_hidden, to_os_path
33 33
34 34 def sort_key(item):
35 35 """Case-insensitive sorting."""
36 36 return item['name'].lower()
37 37
38 38 #-----------------------------------------------------------------------------
39 39 # Classes
40 40 #-----------------------------------------------------------------------------
41 41
42 42 class FileNotebookManager(NotebookManager):
43 43
44 44 save_script = Bool(False, config=True,
45 45 help="""Automatically create a Python script when saving the notebook.
46 46
47 47 For easier use of import, %run and %load across notebooks, a
48 48 <notebook-name>.py script will be created next to any
49 49 <notebook-name>.ipynb on each save. This can also be set with the
50 50 short `--script` flag.
51 51 """
52 52 )
53 53 notebook_dir = Unicode(getcwd(), config=True)
54 54
55 55 def _notebook_dir_changed(self, name, old, new):
56 56 """Do a bit of validation of the notebook dir."""
57 57 if not os.path.isabs(new):
58 58 # If we receive a non-absolute path, make it absolute.
59 59 self.notebook_dir = os.path.abspath(new)
60 60 return
61 61 if not os.path.exists(new) or not os.path.isdir(new):
62 62 raise TraitError("notebook dir %r is not a directory" % new)
63 63
64 64 checkpoint_dir = Unicode(config=True,
65 65 help="""The location in which to keep notebook checkpoints
66 66
67 67 By default, it is notebook-dir/.ipynb_checkpoints
68 68 """
69 69 )
70 70 def _checkpoint_dir_default(self):
71 71 return os.path.join(self.notebook_dir, '.ipynb_checkpoints')
72 72
73 73 def _checkpoint_dir_changed(self, name, old, new):
74 74 """do a bit of validation of the checkpoint dir"""
75 75 if not os.path.isabs(new):
76 76 # If we receive a non-absolute path, make it absolute.
77 77 abs_new = os.path.abspath(new)
78 78 self.checkpoint_dir = abs_new
79 79 return
80 80 if os.path.exists(new) and not os.path.isdir(new):
81 81 raise TraitError("checkpoint dir %r is not a directory" % new)
82 82 if not os.path.exists(new):
83 83 self.log.info("Creating checkpoint dir %s", new)
84 84 try:
85 85 os.mkdir(new)
86 86 except:
87 87 raise TraitError("Couldn't create checkpoint dir %r" % new)
88 88
89 89 def get_notebook_names(self, path=''):
90 90 """List all notebook names in the notebook dir and path."""
91 91 path = path.strip('/')
92 92 if not os.path.isdir(self._get_os_path(path=path)):
93 93 raise web.HTTPError(404, 'Directory not found: ' + path)
94 94 names = glob.glob(self._get_os_path('*'+self.filename_ext, path))
95 95 names = [os.path.basename(name)
96 96 for name in names]
97 97 return names
98 98
99 99 def path_exists(self, path):
100 100 """Does the API-style path (directory) actually exist?
101 101
102 102 Parameters
103 103 ----------
104 104 path : string
105 105 The path to check. This is an API path (`/` separated,
106 106 relative to base notebook-dir).
107 107
108 108 Returns
109 109 -------
110 110 exists : bool
111 111 Whether the path is indeed a directory.
112 112 """
113 113 path = path.strip('/')
114 114 os_path = self._get_os_path(path=path)
115 115 return os.path.isdir(os_path)
116 116
117 117 def is_hidden(self, path):
118 118 """Does the API style path correspond to a hidden directory or file?
119 119
120 120 Parameters
121 121 ----------
122 122 path : string
123 123 The path to check. This is an API path (`/` separated,
124 124 relative to base notebook-dir).
125 125
126 126 Returns
127 127 -------
128 128 exists : bool
129 129 Whether the path is hidden.
130 130
131 131 """
132 132 path = path.strip('/')
133 133 os_path = self._get_os_path(path=path)
134 134 return is_hidden(os_path, self.notebook_dir)
135 135
136 136 def _get_os_path(self, name=None, path=''):
137 137 """Given a notebook name and a URL path, return its file system
138 138 path.
139 139
140 140 Parameters
141 141 ----------
142 142 name : string
143 143 The name of a notebook file with the .ipynb extension
144 144 path : string
145 145 The relative URL path (with '/' as separator) to the named
146 146 notebook.
147 147
148 148 Returns
149 149 -------
150 150 path : string
151 151 A file system path that combines notebook_dir (location where
152 152 server started), the relative path, and the filename with the
153 153 current operating system's url.
154 154 """
155 155 if name is not None:
156 156 path = path + '/' + name
157 157 return to_os_path(path, self.notebook_dir)
158 158
159 159 def notebook_exists(self, name, path=''):
160 160 """Returns a True if the notebook exists. Else, returns False.
161 161
162 162 Parameters
163 163 ----------
164 164 name : string
165 165 The name of the notebook you are checking.
166 166 path : string
167 167 The relative path to the notebook (with '/' as separator)
168 168
169 169 Returns
170 170 -------
171 171 bool
172 172 """
173 173 path = path.strip('/')
174 174 nbpath = self._get_os_path(name, path=path)
175 175 return os.path.isfile(nbpath)
176 176
177 177 # TODO: Remove this after we create the contents web service and directories are
178 178 # no longer listed by the notebook web service.
179 179 def list_dirs(self, path):
180 180 """List the directories for a given API style path."""
181 181 path = path.strip('/')
182 182 os_path = self._get_os_path('', path)
183 183 if not os.path.isdir(os_path) or is_hidden(os_path, self.notebook_dir):
184 184 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
185 185 dir_names = os.listdir(os_path)
186 186 dirs = []
187 187 for name in dir_names:
188 188 os_path = self._get_os_path(name, path)
189 189 if os.path.isdir(os_path) and not is_hidden(os_path, self.notebook_dir)\
190 190 and self.should_list(name):
191 191 try:
192 192 model = self.get_dir_model(name, path)
193 193 except IOError:
194 194 pass
195 195 dirs.append(model)
196 196 dirs = sorted(dirs, key=sort_key)
197 197 return dirs
198 198
199 199 # TODO: Remove this after we create the contents web service and directories are
200 200 # no longer listed by the notebook web service.
201 201 def get_dir_model(self, name, path=''):
202 202 """Get the directory model given a directory name and its API style path"""
203 203 path = path.strip('/')
204 204 os_path = self._get_os_path(name, path)
205 205 if not os.path.isdir(os_path):
206 206 raise IOError('directory does not exist: %r' % os_path)
207 207 info = os.stat(os_path)
208 208 last_modified = tz.utcfromtimestamp(info.st_mtime)
209 209 created = tz.utcfromtimestamp(info.st_ctime)
210 210 # Create the notebook model.
211 211 model ={}
212 212 model['name'] = name
213 213 model['path'] = path
214 214 model['last_modified'] = last_modified
215 215 model['created'] = created
216 216 model['type'] = 'directory'
217 217 return model
218 218
219 219 def list_notebooks(self, path):
220 220 """Returns a list of dictionaries that are the standard model
221 221 for all notebooks in the relative 'path'.
222 222
223 223 Parameters
224 224 ----------
225 225 path : str
226 226 the URL path that describes the relative path for the
227 227 listed notebooks
228 228
229 229 Returns
230 230 -------
231 231 notebooks : list of dicts
232 232 a list of the notebook models without 'content'
233 233 """
234 234 path = path.strip('/')
235 235 notebook_names = self.get_notebook_names(path)
236 236 notebooks = [self.get_notebook(name, path, content=False)
237 237 for name in notebook_names if self.should_list(name)]
238 238 notebooks = sorted(notebooks, key=sort_key)
239 239 return notebooks
240 240
241 241 def get_notebook(self, name, path='', content=True):
242 242 """ Takes a path and name for a notebook and returns its model
243 243
244 244 Parameters
245 245 ----------
246 246 name : str
247 247 the name of the notebook
248 248 path : str
249 249 the URL path that describes the relative path for
250 250 the notebook
251 251
252 252 Returns
253 253 -------
254 254 model : dict
255 255 the notebook model. If contents=True, returns the 'contents'
256 256 dict in the model as well.
257 257 """
258 258 path = path.strip('/')
259 259 if not self.notebook_exists(name=name, path=path):
260 260 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
261 261 os_path = self._get_os_path(name, path)
262 262 info = os.stat(os_path)
263 263 last_modified = tz.utcfromtimestamp(info.st_mtime)
264 264 created = tz.utcfromtimestamp(info.st_ctime)
265 265 # Create the notebook model.
266 266 model ={}
267 267 model['name'] = name
268 268 model['path'] = path
269 269 model['last_modified'] = last_modified
270 270 model['created'] = created
271 271 model['type'] = 'notebook'
272 272 if content:
273 273 with io.open(os_path, 'r', encoding='utf-8') as f:
274 274 try:
275 275 nb = current.read(f, u'json')
276 276 except Exception as e:
277 277 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
278 278 self.mark_trusted_cells(nb, path, name)
279 279 model['content'] = nb
280 280 return model
281 281
282 282 def save_notebook(self, model, name='', path=''):
283 283 """Save the notebook model and return the model with no content."""
284 284 path = path.strip('/')
285 285
286 286 if 'content' not in model:
287 287 raise web.HTTPError(400, u'No notebook JSON data provided')
288 288
289 289 # One checkpoint should always exist
290 290 if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
291 291 self.create_checkpoint(name, path)
292 292
293 293 new_path = model.get('path', path).strip('/')
294 294 new_name = model.get('name', name)
295 295
296 296 if path != new_path or name != new_name:
297 297 self.rename_notebook(name, path, new_name, new_path)
298 298
299 299 # Save the notebook file
300 300 os_path = self._get_os_path(new_name, new_path)
301 301 nb = current.to_notebook_json(model['content'])
302 302
303 303 self.check_and_sign(nb, new_path, new_name)
304 304
305 305 if 'name' in nb['metadata']:
306 306 nb['metadata']['name'] = u''
307 307 try:
308 308 self.log.debug("Autosaving notebook %s", os_path)
309 309 with io.open(os_path, 'w', encoding='utf-8') as f:
310 310 current.write(nb, f, u'json')
311 311 except Exception as e:
312 312 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
313 313
314 314 # Save .py script as well
315 315 if self.save_script:
316 316 py_path = os.path.splitext(os_path)[0] + '.py'
317 317 self.log.debug("Writing script %s", py_path)
318 318 try:
319 319 with io.open(py_path, 'w', encoding='utf-8') as f:
320 320 current.write(nb, f, u'py')
321 321 except Exception as e:
322 322 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
323 323
324 324 model = self.get_notebook(new_name, new_path, content=False)
325 325 return model
326 326
327 327 def update_notebook(self, model, name, path=''):
328 328 """Update the notebook's path and/or name"""
329 329 path = path.strip('/')
330 330 new_name = model.get('name', name)
331 331 new_path = model.get('path', path).strip('/')
332 332 if path != new_path or name != new_name:
333 333 self.rename_notebook(name, path, new_name, new_path)
334 334 model = self.get_notebook(new_name, new_path, content=False)
335 335 return model
336 336
337 337 def delete_notebook(self, name, path=''):
338 338 """Delete notebook by name and path."""
339 339 path = path.strip('/')
340 340 os_path = self._get_os_path(name, path)
341 341 if not os.path.isfile(os_path):
342 342 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
343 343
344 344 # clear checkpoints
345 345 for checkpoint in self.list_checkpoints(name, path):
346 346 checkpoint_id = checkpoint['id']
347 347 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
348 348 if os.path.isfile(cp_path):
349 349 self.log.debug("Unlinking checkpoint %s", cp_path)
350 350 os.unlink(cp_path)
351 351
352 352 self.log.debug("Unlinking notebook %s", os_path)
353 353 os.unlink(os_path)
354 354
355 355 def rename_notebook(self, old_name, old_path, new_name, new_path):
356 356 """Rename a notebook."""
357 357 old_path = old_path.strip('/')
358 358 new_path = new_path.strip('/')
359 359 if new_name == old_name and new_path == old_path:
360 360 return
361 361
362 362 new_os_path = self._get_os_path(new_name, new_path)
363 363 old_os_path = self._get_os_path(old_name, old_path)
364 364
365 365 # Should we proceed with the move?
366 366 if os.path.isfile(new_os_path):
367 367 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
368 368 if self.save_script:
369 369 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
370 370 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
371 371 if os.path.isfile(new_py_path):
372 372 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
373 373
374 374 # Move the notebook file
375 375 try:
376 376 os.rename(old_os_path, new_os_path)
377 377 except Exception as e:
378 378 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
379 379
380 380 # Move the checkpoints
381 381 old_checkpoints = self.list_checkpoints(old_name, old_path)
382 382 for cp in old_checkpoints:
383 383 checkpoint_id = cp['id']
384 384 old_cp_path = self.get_checkpoint_path(checkpoint_id, old_name, old_path)
385 385 new_cp_path = self.get_checkpoint_path(checkpoint_id, new_name, new_path)
386 386 if os.path.isfile(old_cp_path):
387 387 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
388 388 os.rename(old_cp_path, new_cp_path)
389 389
390 390 # Move the .py script
391 391 if self.save_script:
392 392 os.rename(old_py_path, new_py_path)
393 393
394 394 # Checkpoint-related utilities
395 395
396 396 def get_checkpoint_path(self, checkpoint_id, name, path=''):
397 397 """find the path to a checkpoint"""
398 398 path = path.strip('/')
399 399 basename, _ = os.path.splitext(name)
400 400 filename = u"{name}-{checkpoint_id}{ext}".format(
401 401 name=basename,
402 402 checkpoint_id=checkpoint_id,
403 403 ext=self.filename_ext,
404 404 )
405 405 cp_path = os.path.join(path, self.checkpoint_dir, filename)
406 406 return cp_path
407 407
408 408 def get_checkpoint_model(self, checkpoint_id, name, path=''):
409 409 """construct the info dict for a given checkpoint"""
410 410 path = path.strip('/')
411 411 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
412 412 stats = os.stat(cp_path)
413 413 last_modified = tz.utcfromtimestamp(stats.st_mtime)
414 414 info = dict(
415 415 id = checkpoint_id,
416 416 last_modified = last_modified,
417 417 )
418 418 return info
419 419
420 420 # public checkpoint API
421 421
422 422 def create_checkpoint(self, name, path=''):
423 423 """Create a checkpoint from the current state of a notebook"""
424 424 path = path.strip('/')
425 425 nb_path = self._get_os_path(name, path)
426 426 # only the one checkpoint ID:
427 427 checkpoint_id = u"checkpoint"
428 428 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
429 429 self.log.debug("creating checkpoint for notebook %s", name)
430 430 if not os.path.exists(self.checkpoint_dir):
431 431 os.mkdir(self.checkpoint_dir)
432 432 shutil.copy2(nb_path, cp_path)
433 433
434 434 # return the checkpoint info
435 435 return self.get_checkpoint_model(checkpoint_id, name, path)
436 436
437 437 def list_checkpoints(self, name, path=''):
438 438 """list the checkpoints for a given notebook
439 439
440 440 This notebook manager currently only supports one checkpoint per notebook.
441 441 """
442 442 path = path.strip('/')
443 443 checkpoint_id = "checkpoint"
444 444 path = self.get_checkpoint_path(checkpoint_id, name, path)
445 445 if not os.path.exists(path):
446 446 return []
447 447 else:
448 448 return [self.get_checkpoint_model(checkpoint_id, name, path)]
449 449
450 450
451 451 def restore_checkpoint(self, checkpoint_id, name, path=''):
452 452 """restore a notebook to a checkpointed state"""
453 453 path = path.strip('/')
454 454 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
455 455 nb_path = self._get_os_path(name, path)
456 456 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
457 457 if not os.path.isfile(cp_path):
458 458 self.log.debug("checkpoint file does not exist: %s", cp_path)
459 459 raise web.HTTPError(404,
460 460 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
461 461 )
462 462 # ensure notebook is readable (never restore from an unreadable notebook)
463 463 with io.open(cp_path, 'r', encoding='utf-8') as f:
464 nb = current.read(f, u'json')
464 current.read(f, u'json')
465 465 shutil.copy2(cp_path, nb_path)
466 466 self.log.debug("copying %s -> %s", cp_path, nb_path)
467 467
468 468 def delete_checkpoint(self, checkpoint_id, name, path=''):
469 469 """delete a notebook's checkpoint"""
470 470 path = path.strip('/')
471 471 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
472 472 if not os.path.isfile(cp_path):
473 473 raise web.HTTPError(404,
474 474 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
475 475 )
476 476 self.log.debug("unlinking %s", cp_path)
477 477 os.unlink(cp_path)
478 478
479 479 def info_string(self):
480 480 return "Serving notebooks from local directory: %s" % self.notebook_dir
General Comments 0
You need to be logged in to leave comments. Login now