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