##// END OF EJS Templates
Clean up get_kernel_path logic
Thomas Kluyver -
Show More
@@ -1,33 +1,23 b''
1 """A dummy contents manager for when the logic is done client side (in JavaScript)."""
1 """A dummy contents manager for when the logic is done client side (in JavaScript)."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from .manager import ContentsManager
6 from .manager import ContentsManager
7
7
8 class ClientSideContentsManager(ContentsManager):
8 class ClientSideContentsManager(ContentsManager):
9 """Dummy contents manager for use with client-side contents APIs like GDrive
9 """Dummy contents manager for use with client-side contents APIs like GDrive
10
10
11 The view handlers for notebooks and directories (/tree/) check with the
11 The view handlers for notebooks and directories (/tree/) check with the
12 ContentsManager that their target exists so they can return 404 if not. Using
12 ContentsManager that their target exists so they can return 404 if not. Using
13 this class as the contents manager allows those pages to render without
13 this class as the contents manager allows those pages to render without
14 checking something that the server doesn't know about.
14 checking something that the server doesn't know about.
15 """
15 """
16 def dir_exists(self, path):
16 def dir_exists(self, path):
17 return True
17 return True
18
18
19 def is_hidden(self, path):
19 def is_hidden(self, path):
20 return False
20 return False
21
21
22 def file_exists(self, name, path=''):
22 def file_exists(self, name, path=''):
23 return True
23 return True
24
25 def get_kernel_path(self, path, model=None):
26 """Return the API path for the kernel
27
28 KernelManagers can turn this value into a filesystem path,
29 or ignore it altogether.
30
31 Here just always return home directory
32 """
33 return '/'
@@ -1,380 +1,384 b''
1 """A base class for contents managers."""
1 """A base class for contents managers."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from fnmatch import fnmatch
6 from fnmatch import fnmatch
7 import itertools
7 import itertools
8 import json
8 import json
9 import os
9 import os
10 import re
10 import re
11
11
12 from tornado.web import HTTPError
12 from tornado.web import HTTPError
13
13
14 from IPython.config.configurable import LoggingConfigurable
14 from IPython.config.configurable import LoggingConfigurable
15 from IPython.nbformat import sign, validate, ValidationError
15 from IPython.nbformat import sign, validate, ValidationError
16 from IPython.nbformat.v4 import new_notebook
16 from IPython.nbformat.v4 import new_notebook
17 from IPython.utils.traitlets import Instance, Unicode, List
17 from IPython.utils.traitlets import Instance, Unicode, List
18
18
19 copy_pat = re.compile(r'\-Copy\d*\.')
19 copy_pat = re.compile(r'\-Copy\d*\.')
20
20
21 class ContentsManager(LoggingConfigurable):
21 class ContentsManager(LoggingConfigurable):
22 """Base class for serving files and directories.
22 """Base class for serving files and directories.
23
23
24 This serves any text or binary file,
24 This serves any text or binary file,
25 as well as directories,
25 as well as directories,
26 with special handling for JSON notebook documents.
26 with special handling for JSON notebook documents.
27
27
28 Most APIs take a path argument,
28 Most APIs take a path argument,
29 which is always an API-style unicode path,
29 which is always an API-style unicode path,
30 and always refers to a directory.
30 and always refers to a directory.
31
31
32 - unicode, not url-escaped
32 - unicode, not url-escaped
33 - '/'-separated
33 - '/'-separated
34 - leading and trailing '/' will be stripped
34 - leading and trailing '/' will be stripped
35 - if unspecified, path defaults to '',
35 - if unspecified, path defaults to '',
36 indicating the root path.
36 indicating the root path.
37
37
38 """
38 """
39
39
40 notary = Instance(sign.NotebookNotary)
40 notary = Instance(sign.NotebookNotary)
41 def _notary_default(self):
41 def _notary_default(self):
42 return sign.NotebookNotary(parent=self)
42 return sign.NotebookNotary(parent=self)
43
43
44 hide_globs = List(Unicode, [
44 hide_globs = List(Unicode, [
45 u'__pycache__', '*.pyc', '*.pyo',
45 u'__pycache__', '*.pyc', '*.pyo',
46 '.DS_Store', '*.so', '*.dylib', '*~',
46 '.DS_Store', '*.so', '*.dylib', '*~',
47 ], config=True, help="""
47 ], config=True, help="""
48 Glob patterns to hide in file and directory listings.
48 Glob patterns to hide in file and directory listings.
49 """)
49 """)
50
50
51 untitled_notebook = Unicode("Untitled", config=True,
51 untitled_notebook = Unicode("Untitled", config=True,
52 help="The base name used when creating untitled notebooks."
52 help="The base name used when creating untitled notebooks."
53 )
53 )
54
54
55 untitled_file = Unicode("untitled", config=True,
55 untitled_file = Unicode("untitled", config=True,
56 help="The base name used when creating untitled files."
56 help="The base name used when creating untitled files."
57 )
57 )
58
58
59 untitled_directory = Unicode("Untitled Folder", config=True,
59 untitled_directory = Unicode("Untitled Folder", config=True,
60 help="The base name used when creating untitled directories."
60 help="The base name used when creating untitled directories."
61 )
61 )
62
62
63 # ContentsManager API part 1: methods that must be
63 # ContentsManager API part 1: methods that must be
64 # implemented in subclasses.
64 # implemented in subclasses.
65
65
66 def dir_exists(self, path):
66 def dir_exists(self, path):
67 """Does the API-style path (directory) actually exist?
67 """Does the API-style path (directory) actually exist?
68
68
69 Like os.path.isdir
69 Like os.path.isdir
70
70
71 Override this method in subclasses.
71 Override this method in subclasses.
72
72
73 Parameters
73 Parameters
74 ----------
74 ----------
75 path : string
75 path : string
76 The path to check
76 The path to check
77
77
78 Returns
78 Returns
79 -------
79 -------
80 exists : bool
80 exists : bool
81 Whether the path does indeed exist.
81 Whether the path does indeed exist.
82 """
82 """
83 raise NotImplementedError
83 raise NotImplementedError
84
84
85 def is_hidden(self, path):
85 def is_hidden(self, path):
86 """Does the API style path correspond to a hidden directory or file?
86 """Does the API style path correspond to a hidden directory or file?
87
87
88 Parameters
88 Parameters
89 ----------
89 ----------
90 path : string
90 path : string
91 The path to check. This is an API path (`/` separated,
91 The path to check. This is an API path (`/` separated,
92 relative to root dir).
92 relative to root dir).
93
93
94 Returns
94 Returns
95 -------
95 -------
96 hidden : bool
96 hidden : bool
97 Whether the path is hidden.
97 Whether the path is hidden.
98
98
99 """
99 """
100 raise NotImplementedError
100 raise NotImplementedError
101
101
102 def file_exists(self, path=''):
102 def file_exists(self, path=''):
103 """Does a file exist at the given path?
103 """Does a file exist at the given path?
104
104
105 Like os.path.isfile
105 Like os.path.isfile
106
106
107 Override this method in subclasses.
107 Override this method in subclasses.
108
108
109 Parameters
109 Parameters
110 ----------
110 ----------
111 name : string
111 name : string
112 The name of the file you are checking.
112 The name of the file you are checking.
113 path : string
113 path : string
114 The relative path to the file's directory (with '/' as separator)
114 The relative path to the file's directory (with '/' as separator)
115
115
116 Returns
116 Returns
117 -------
117 -------
118 exists : bool
118 exists : bool
119 Whether the file exists.
119 Whether the file exists.
120 """
120 """
121 raise NotImplementedError('must be implemented in a subclass')
121 raise NotImplementedError('must be implemented in a subclass')
122
122
123 def exists(self, path):
123 def exists(self, path):
124 """Does a file or directory exist at the given path?
124 """Does a file or directory exist at the given path?
125
125
126 Like os.path.exists
126 Like os.path.exists
127
127
128 Parameters
128 Parameters
129 ----------
129 ----------
130 path : string
130 path : string
131 The relative path to the file's directory (with '/' as separator)
131 The relative path to the file's directory (with '/' as separator)
132
132
133 Returns
133 Returns
134 -------
134 -------
135 exists : bool
135 exists : bool
136 Whether the target exists.
136 Whether the target exists.
137 """
137 """
138 return self.file_exists(path) or self.dir_exists(path)
138 return self.file_exists(path) or self.dir_exists(path)
139
139
140 def get(self, path, content=True, type_=None, format=None):
140 def get(self, path, content=True, type_=None, format=None):
141 """Get the model of a file or directory with or without content."""
141 """Get the model of a file or directory with or without content."""
142 raise NotImplementedError('must be implemented in a subclass')
142 raise NotImplementedError('must be implemented in a subclass')
143
143
144 def save(self, model, path):
144 def save(self, model, path):
145 """Save the file or directory and return the model with no content."""
145 """Save the file or directory and return the model with no content."""
146 raise NotImplementedError('must be implemented in a subclass')
146 raise NotImplementedError('must be implemented in a subclass')
147
147
148 def update(self, model, path):
148 def update(self, model, path):
149 """Update the file or directory and return the model with no content.
149 """Update the file or directory and return the model with no content.
150
150
151 For use in PATCH requests, to enable renaming a file without
151 For use in PATCH requests, to enable renaming a file without
152 re-uploading its contents. Only used for renaming at the moment.
152 re-uploading its contents. Only used for renaming at the moment.
153 """
153 """
154 raise NotImplementedError('must be implemented in a subclass')
154 raise NotImplementedError('must be implemented in a subclass')
155
155
156 def delete(self, path):
156 def delete(self, path):
157 """Delete file or directory by path."""
157 """Delete file or directory by path."""
158 raise NotImplementedError('must be implemented in a subclass')
158 raise NotImplementedError('must be implemented in a subclass')
159
159
160 def create_checkpoint(self, path):
160 def create_checkpoint(self, path):
161 """Create a checkpoint of the current state of a file
161 """Create a checkpoint of the current state of a file
162
162
163 Returns a checkpoint_id for the new checkpoint.
163 Returns a checkpoint_id for the new checkpoint.
164 """
164 """
165 raise NotImplementedError("must be implemented in a subclass")
165 raise NotImplementedError("must be implemented in a subclass")
166
166
167 def list_checkpoints(self, path):
167 def list_checkpoints(self, path):
168 """Return a list of checkpoints for a given file"""
168 """Return a list of checkpoints for a given file"""
169 return []
169 return []
170
170
171 def restore_checkpoint(self, checkpoint_id, path):
171 def restore_checkpoint(self, checkpoint_id, path):
172 """Restore a file from one of its checkpoints"""
172 """Restore a file from one of its checkpoints"""
173 raise NotImplementedError("must be implemented in a subclass")
173 raise NotImplementedError("must be implemented in a subclass")
174
174
175 def delete_checkpoint(self, checkpoint_id, path):
175 def delete_checkpoint(self, checkpoint_id, path):
176 """delete a checkpoint for a file"""
176 """delete a checkpoint for a file"""
177 raise NotImplementedError("must be implemented in a subclass")
177 raise NotImplementedError("must be implemented in a subclass")
178
178
179 # ContentsManager API part 2: methods that have useable default
179 # ContentsManager API part 2: methods that have useable default
180 # implementations, but can be overridden in subclasses.
180 # implementations, but can be overridden in subclasses.
181
181
182 def info_string(self):
182 def info_string(self):
183 return "Serving contents"
183 return "Serving contents"
184
184
185 def get_kernel_path(self, path, model=None):
185 def get_kernel_path(self, path, model=None):
186 """Return the API path for the kernel
186 """Return the API path for the kernel
187
187
188 KernelManagers can turn this value into a filesystem path,
188 KernelManagers can turn this value into a filesystem path,
189 or ignore it altogether.
189 or ignore it altogether.
190
191 The default value here will start kernels in the directory of the
192 notebook server. FileContentsManager overrides this to use the
193 directory containing the notebook.
190 """
194 """
191 return path
195 return ''
192
196
193 def increment_filename(self, filename, path='', insert=''):
197 def increment_filename(self, filename, path='', insert=''):
194 """Increment a filename until it is unique.
198 """Increment a filename until it is unique.
195
199
196 Parameters
200 Parameters
197 ----------
201 ----------
198 filename : unicode
202 filename : unicode
199 The name of a file, including extension
203 The name of a file, including extension
200 path : unicode
204 path : unicode
201 The API path of the target's directory
205 The API path of the target's directory
202
206
203 Returns
207 Returns
204 -------
208 -------
205 name : unicode
209 name : unicode
206 A filename that is unique, based on the input filename.
210 A filename that is unique, based on the input filename.
207 """
211 """
208 path = path.strip('/')
212 path = path.strip('/')
209 basename, ext = os.path.splitext(filename)
213 basename, ext = os.path.splitext(filename)
210 for i in itertools.count():
214 for i in itertools.count():
211 if i:
215 if i:
212 insert_i = '{}{}'.format(insert, i)
216 insert_i = '{}{}'.format(insert, i)
213 else:
217 else:
214 insert_i = ''
218 insert_i = ''
215 name = u'{basename}{insert}{ext}'.format(basename=basename,
219 name = u'{basename}{insert}{ext}'.format(basename=basename,
216 insert=insert_i, ext=ext)
220 insert=insert_i, ext=ext)
217 if not self.exists(u'{}/{}'.format(path, name)):
221 if not self.exists(u'{}/{}'.format(path, name)):
218 break
222 break
219 return name
223 return name
220
224
221 def validate_notebook_model(self, model):
225 def validate_notebook_model(self, model):
222 """Add failed-validation message to model"""
226 """Add failed-validation message to model"""
223 try:
227 try:
224 validate(model['content'])
228 validate(model['content'])
225 except ValidationError as e:
229 except ValidationError as e:
226 model['message'] = u'Notebook Validation failed: {}:\n{}'.format(
230 model['message'] = u'Notebook Validation failed: {}:\n{}'.format(
227 e.message, json.dumps(e.instance, indent=1, default=lambda obj: '<UNKNOWN>'),
231 e.message, json.dumps(e.instance, indent=1, default=lambda obj: '<UNKNOWN>'),
228 )
232 )
229 return model
233 return model
230
234
231 def new_untitled(self, path='', type='', ext=''):
235 def new_untitled(self, path='', type='', ext=''):
232 """Create a new untitled file or directory in path
236 """Create a new untitled file or directory in path
233
237
234 path must be a directory
238 path must be a directory
235
239
236 File extension can be specified.
240 File extension can be specified.
237
241
238 Use `new` to create files with a fully specified path (including filename).
242 Use `new` to create files with a fully specified path (including filename).
239 """
243 """
240 path = path.strip('/')
244 path = path.strip('/')
241 if not self.dir_exists(path):
245 if not self.dir_exists(path):
242 raise HTTPError(404, 'No such directory: %s' % path)
246 raise HTTPError(404, 'No such directory: %s' % path)
243
247
244 model = {}
248 model = {}
245 if type:
249 if type:
246 model['type'] = type
250 model['type'] = type
247
251
248 if ext == '.ipynb':
252 if ext == '.ipynb':
249 model.setdefault('type', 'notebook')
253 model.setdefault('type', 'notebook')
250 else:
254 else:
251 model.setdefault('type', 'file')
255 model.setdefault('type', 'file')
252
256
253 insert = ''
257 insert = ''
254 if model['type'] == 'directory':
258 if model['type'] == 'directory':
255 untitled = self.untitled_directory
259 untitled = self.untitled_directory
256 insert = ' '
260 insert = ' '
257 elif model['type'] == 'notebook':
261 elif model['type'] == 'notebook':
258 untitled = self.untitled_notebook
262 untitled = self.untitled_notebook
259 ext = '.ipynb'
263 ext = '.ipynb'
260 elif model['type'] == 'file':
264 elif model['type'] == 'file':
261 untitled = self.untitled_file
265 untitled = self.untitled_file
262 else:
266 else:
263 raise HTTPError(400, "Unexpected model type: %r" % model['type'])
267 raise HTTPError(400, "Unexpected model type: %r" % model['type'])
264
268
265 name = self.increment_filename(untitled + ext, path, insert=insert)
269 name = self.increment_filename(untitled + ext, path, insert=insert)
266 path = u'{0}/{1}'.format(path, name)
270 path = u'{0}/{1}'.format(path, name)
267 return self.new(model, path)
271 return self.new(model, path)
268
272
269 def new(self, model=None, path=''):
273 def new(self, model=None, path=''):
270 """Create a new file or directory and return its model with no content.
274 """Create a new file or directory and return its model with no content.
271
275
272 To create a new untitled entity in a directory, use `new_untitled`.
276 To create a new untitled entity in a directory, use `new_untitled`.
273 """
277 """
274 path = path.strip('/')
278 path = path.strip('/')
275 if model is None:
279 if model is None:
276 model = {}
280 model = {}
277
281
278 if path.endswith('.ipynb'):
282 if path.endswith('.ipynb'):
279 model.setdefault('type', 'notebook')
283 model.setdefault('type', 'notebook')
280 else:
284 else:
281 model.setdefault('type', 'file')
285 model.setdefault('type', 'file')
282
286
283 # no content, not a directory, so fill out new-file model
287 # no content, not a directory, so fill out new-file model
284 if 'content' not in model and model['type'] != 'directory':
288 if 'content' not in model and model['type'] != 'directory':
285 if model['type'] == 'notebook':
289 if model['type'] == 'notebook':
286 model['content'] = new_notebook()
290 model['content'] = new_notebook()
287 model['format'] = 'json'
291 model['format'] = 'json'
288 else:
292 else:
289 model['content'] = ''
293 model['content'] = ''
290 model['type'] = 'file'
294 model['type'] = 'file'
291 model['format'] = 'text'
295 model['format'] = 'text'
292
296
293 model = self.save(model, path)
297 model = self.save(model, path)
294 return model
298 return model
295
299
296 def copy(self, from_path, to_path=None):
300 def copy(self, from_path, to_path=None):
297 """Copy an existing file and return its new model.
301 """Copy an existing file and return its new model.
298
302
299 If to_path not specified, it will be the parent directory of from_path.
303 If to_path not specified, it will be the parent directory of from_path.
300 If to_path is a directory, filename will increment `from_path-Copy#.ext`.
304 If to_path is a directory, filename will increment `from_path-Copy#.ext`.
301
305
302 from_path must be a full path to a file.
306 from_path must be a full path to a file.
303 """
307 """
304 path = from_path.strip('/')
308 path = from_path.strip('/')
305 if '/' in path:
309 if '/' in path:
306 from_dir, from_name = path.rsplit('/', 1)
310 from_dir, from_name = path.rsplit('/', 1)
307 else:
311 else:
308 from_dir = ''
312 from_dir = ''
309 from_name = path
313 from_name = path
310
314
311 model = self.get(path)
315 model = self.get(path)
312 model.pop('path', None)
316 model.pop('path', None)
313 model.pop('name', None)
317 model.pop('name', None)
314 if model['type'] == 'directory':
318 if model['type'] == 'directory':
315 raise HTTPError(400, "Can't copy directories")
319 raise HTTPError(400, "Can't copy directories")
316
320
317 if not to_path:
321 if not to_path:
318 to_path = from_dir
322 to_path = from_dir
319 if self.dir_exists(to_path):
323 if self.dir_exists(to_path):
320 name = copy_pat.sub(u'.', from_name)
324 name = copy_pat.sub(u'.', from_name)
321 to_name = self.increment_filename(name, to_path, insert='-Copy')
325 to_name = self.increment_filename(name, to_path, insert='-Copy')
322 to_path = u'{0}/{1}'.format(to_path, to_name)
326 to_path = u'{0}/{1}'.format(to_path, to_name)
323
327
324 model = self.save(model, to_path)
328 model = self.save(model, to_path)
325 return model
329 return model
326
330
327 def log_info(self):
331 def log_info(self):
328 self.log.info(self.info_string())
332 self.log.info(self.info_string())
329
333
330 def trust_notebook(self, path):
334 def trust_notebook(self, path):
331 """Explicitly trust a notebook
335 """Explicitly trust a notebook
332
336
333 Parameters
337 Parameters
334 ----------
338 ----------
335 path : string
339 path : string
336 The path of a notebook
340 The path of a notebook
337 """
341 """
338 model = self.get(path)
342 model = self.get(path)
339 nb = model['content']
343 nb = model['content']
340 self.log.warn("Trusting notebook %s", path)
344 self.log.warn("Trusting notebook %s", path)
341 self.notary.mark_cells(nb, True)
345 self.notary.mark_cells(nb, True)
342 self.save(model, path)
346 self.save(model, path)
343
347
344 def check_and_sign(self, nb, path=''):
348 def check_and_sign(self, nb, path=''):
345 """Check for trusted cells, and sign the notebook.
349 """Check for trusted cells, and sign the notebook.
346
350
347 Called as a part of saving notebooks.
351 Called as a part of saving notebooks.
348
352
349 Parameters
353 Parameters
350 ----------
354 ----------
351 nb : dict
355 nb : dict
352 The notebook dict
356 The notebook dict
353 path : string
357 path : string
354 The notebook's path (for logging)
358 The notebook's path (for logging)
355 """
359 """
356 if self.notary.check_cells(nb):
360 if self.notary.check_cells(nb):
357 self.notary.sign(nb)
361 self.notary.sign(nb)
358 else:
362 else:
359 self.log.warn("Saving untrusted notebook %s", path)
363 self.log.warn("Saving untrusted notebook %s", path)
360
364
361 def mark_trusted_cells(self, nb, path=''):
365 def mark_trusted_cells(self, nb, path=''):
362 """Mark cells as trusted if the notebook signature matches.
366 """Mark cells as trusted if the notebook signature matches.
363
367
364 Called as a part of loading notebooks.
368 Called as a part of loading notebooks.
365
369
366 Parameters
370 Parameters
367 ----------
371 ----------
368 nb : dict
372 nb : dict
369 The notebook object (in current nbformat)
373 The notebook object (in current nbformat)
370 path : string
374 path : string
371 The notebook's path (for logging)
375 The notebook's path (for logging)
372 """
376 """
373 trusted = self.notary.check_signature(nb)
377 trusted = self.notary.check_signature(nb)
374 if not trusted:
378 if not trusted:
375 self.log.warn("Notebook %s is not trusted", path)
379 self.log.warn("Notebook %s is not trusted", path)
376 self.notary.mark_cells(nb, trusted)
380 self.notary.mark_cells(nb, trusted)
377
381
378 def should_list(self, name):
382 def should_list(self, name):
379 """Should this file/directory name be displayed in a listing?"""
383 """Should this file/directory name be displayed in a listing?"""
380 return not any(fnmatch(name, glob) for glob in self.hide_globs)
384 return not any(fnmatch(name, glob) for glob in self.hide_globs)
@@ -1,127 +1,123 b''
1 """A MultiKernelManager for use in the notebook webserver
1 """A MultiKernelManager for use in the notebook webserver
2
2
3 - raises HTTPErrors
3 - raises HTTPErrors
4 - creates REST API models
4 - creates REST API models
5 """
5 """
6
6
7 # Copyright (c) IPython Development Team.
7 # Copyright (c) IPython Development Team.
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9
9
10 import os
10 import os
11
11
12 from tornado import web
12 from tornado import web
13
13
14 from IPython.kernel.multikernelmanager import MultiKernelManager
14 from IPython.kernel.multikernelmanager import MultiKernelManager
15 from IPython.utils.traitlets import List, Unicode, TraitError
15 from IPython.utils.traitlets import List, Unicode, TraitError
16
16
17 from IPython.html.utils import to_os_path
17 from IPython.html.utils import to_os_path
18 from IPython.utils.py3compat import getcwd
18 from IPython.utils.py3compat import getcwd
19
19
20
20
21 class MappingKernelManager(MultiKernelManager):
21 class MappingKernelManager(MultiKernelManager):
22 """A KernelManager that handles notebook mapping and HTTP error handling"""
22 """A KernelManager that handles notebook mapping and HTTP error handling"""
23
23
24 def _kernel_manager_class_default(self):
24 def _kernel_manager_class_default(self):
25 return "IPython.kernel.ioloop.IOLoopKernelManager"
25 return "IPython.kernel.ioloop.IOLoopKernelManager"
26
26
27 kernel_argv = List(Unicode)
27 kernel_argv = List(Unicode)
28
28
29 root_dir = Unicode(config=True)
29 root_dir = Unicode(config=True)
30
30
31 def _root_dir_default(self):
31 def _root_dir_default(self):
32 try:
32 try:
33 return self.parent.notebook_dir
33 return self.parent.notebook_dir
34 except AttributeError:
34 except AttributeError:
35 return getcwd()
35 return getcwd()
36
36
37 def _root_dir_changed(self, name, old, new):
37 def _root_dir_changed(self, name, old, new):
38 """Do a bit of validation of the root dir."""
38 """Do a bit of validation of the root dir."""
39 if not os.path.isabs(new):
39 if not os.path.isabs(new):
40 # If we receive a non-absolute path, make it absolute.
40 # If we receive a non-absolute path, make it absolute.
41 self.root_dir = os.path.abspath(new)
41 self.root_dir = os.path.abspath(new)
42 return
42 return
43 if not os.path.exists(new) or not os.path.isdir(new):
43 if not os.path.exists(new) or not os.path.isdir(new):
44 raise TraitError("kernel root dir %r is not a directory" % new)
44 raise TraitError("kernel root dir %r is not a directory" % new)
45
45
46 #-------------------------------------------------------------------------
46 #-------------------------------------------------------------------------
47 # Methods for managing kernels and sessions
47 # Methods for managing kernels and sessions
48 #-------------------------------------------------------------------------
48 #-------------------------------------------------------------------------
49
49
50 def _handle_kernel_died(self, kernel_id):
50 def _handle_kernel_died(self, kernel_id):
51 """notice that a kernel died"""
51 """notice that a kernel died"""
52 self.log.warn("Kernel %s died, removing from map.", kernel_id)
52 self.log.warn("Kernel %s died, removing from map.", kernel_id)
53 self.remove_kernel(kernel_id)
53 self.remove_kernel(kernel_id)
54
54
55 def cwd_for_path(self, path):
55 def cwd_for_path(self, path):
56 """Turn API path into absolute OS path."""
56 """Turn API path into absolute OS path."""
57 # short circuit for NotebookManagers that pass in absolute paths
58 if os.path.exists(path):
59 return path
60
61 os_path = to_os_path(path, self.root_dir)
57 os_path = to_os_path(path, self.root_dir)
62 # in the case of notebooks and kernels not being on the same filesystem,
58 # in the case of notebooks and kernels not being on the same filesystem,
63 # walk up to root_dir if the paths don't exist
59 # walk up to root_dir if the paths don't exist
64 while not os.path.exists(os_path) and os_path != self.root_dir:
60 while not os.path.isdir(os_path) and os_path != self.root_dir:
65 os_path = os.path.dirname(os_path)
61 os_path = os.path.dirname(os_path)
66 return os_path
62 return os_path
67
63
68 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
64 def start_kernel(self, kernel_id=None, path=None, kernel_name='python', **kwargs):
69 """Start a kernel for a session and return its kernel_id.
65 """Start a kernel for a session and return its kernel_id.
70
66
71 Parameters
67 Parameters
72 ----------
68 ----------
73 kernel_id : uuid
69 kernel_id : uuid
74 The uuid to associate the new kernel with. If this
70 The uuid to associate the new kernel with. If this
75 is not None, this kernel will be persistent whenever it is
71 is not None, this kernel will be persistent whenever it is
76 requested.
72 requested.
77 path : API path
73 path : API path
78 The API path (unicode, '/' delimited) for the cwd.
74 The API path (unicode, '/' delimited) for the cwd.
79 Will be transformed to an OS path relative to root_dir.
75 Will be transformed to an OS path relative to root_dir.
80 kernel_name : str
76 kernel_name : str
81 The name identifying which kernel spec to launch. This is ignored if
77 The name identifying which kernel spec to launch. This is ignored if
82 an existing kernel is returned, but it may be checked in the future.
78 an existing kernel is returned, but it may be checked in the future.
83 """
79 """
84 if kernel_id is None:
80 if kernel_id is None:
85 if path is not None:
81 if path is not None:
86 kwargs['cwd'] = self.cwd_for_path(path)
82 kwargs['cwd'] = self.cwd_for_path(path)
87 kernel_id = super(MappingKernelManager, self).start_kernel(
83 kernel_id = super(MappingKernelManager, self).start_kernel(
88 kernel_name=kernel_name, **kwargs)
84 kernel_name=kernel_name, **kwargs)
89 self.log.info("Kernel started: %s" % kernel_id)
85 self.log.info("Kernel started: %s" % kernel_id)
90 self.log.debug("Kernel args: %r" % kwargs)
86 self.log.debug("Kernel args: %r" % kwargs)
91 # register callback for failed auto-restart
87 # register callback for failed auto-restart
92 self.add_restart_callback(kernel_id,
88 self.add_restart_callback(kernel_id,
93 lambda : self._handle_kernel_died(kernel_id),
89 lambda : self._handle_kernel_died(kernel_id),
94 'dead',
90 'dead',
95 )
91 )
96 else:
92 else:
97 self._check_kernel_id(kernel_id)
93 self._check_kernel_id(kernel_id)
98 self.log.info("Using existing kernel: %s" % kernel_id)
94 self.log.info("Using existing kernel: %s" % kernel_id)
99 return kernel_id
95 return kernel_id
100
96
101 def shutdown_kernel(self, kernel_id, now=False):
97 def shutdown_kernel(self, kernel_id, now=False):
102 """Shutdown a kernel by kernel_id"""
98 """Shutdown a kernel by kernel_id"""
103 self._check_kernel_id(kernel_id)
99 self._check_kernel_id(kernel_id)
104 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
100 super(MappingKernelManager, self).shutdown_kernel(kernel_id, now=now)
105
101
106 def kernel_model(self, kernel_id):
102 def kernel_model(self, kernel_id):
107 """Return a dictionary of kernel information described in the
103 """Return a dictionary of kernel information described in the
108 JSON standard model."""
104 JSON standard model."""
109 self._check_kernel_id(kernel_id)
105 self._check_kernel_id(kernel_id)
110 model = {"id":kernel_id,
106 model = {"id":kernel_id,
111 "name": self._kernels[kernel_id].kernel_name}
107 "name": self._kernels[kernel_id].kernel_name}
112 return model
108 return model
113
109
114 def list_kernels(self):
110 def list_kernels(self):
115 """Returns a list of kernel_id's of kernels running."""
111 """Returns a list of kernel_id's of kernels running."""
116 kernels = []
112 kernels = []
117 kernel_ids = super(MappingKernelManager, self).list_kernel_ids()
113 kernel_ids = super(MappingKernelManager, self).list_kernel_ids()
118 for kernel_id in kernel_ids:
114 for kernel_id in kernel_ids:
119 model = self.kernel_model(kernel_id)
115 model = self.kernel_model(kernel_id)
120 kernels.append(model)
116 kernels.append(model)
121 return kernels
117 return kernels
122
118
123 # override _check_kernel_id to raise 404 instead of KeyError
119 # override _check_kernel_id to raise 404 instead of KeyError
124 def _check_kernel_id(self, kernel_id):
120 def _check_kernel_id(self, kernel_id):
125 """Check a that a kernel_id exists and raise 404 if not."""
121 """Check a that a kernel_id exists and raise 404 if not."""
126 if kernel_id not in self:
122 if kernel_id not in self:
127 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
123 raise web.HTTPError(404, u'Kernel does not exist: %s' % kernel_id)
General Comments 0
You need to be logged in to leave comments. Login now