##// END OF EJS Templates
Merge pull request #5116 from minrk/os_path...
Brian E. Granger -
r15428:6b11b356 merge
parent child Browse files
Show More
@@ -76,15 +76,12 b' class NbconvertFileHandler(IPythonHandler):'
76 76 exporter = get_exporter(format, config=self.config)
77 77
78 78 path = path.strip('/')
79 os_path = self.notebook_manager.get_os_path(name, path)
80 if not os.path.isfile(os_path):
81 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
79 model = self.notebook_manager.get_notebook(name=name, path=path)
82 80
83 info = os.stat(os_path)
84 self.set_header('Last-Modified', tz.utcfromtimestamp(info.st_mtime))
81 self.set_header('Last-Modified', model['last_modified'])
85 82
86 83 try:
87 output, resources = exporter.from_filename(os_path)
84 output, resources = exporter.from_notebook_node(model['content'])
88 85 except Exception as e:
89 86 raise web.HTTPError(500, "nbconvert failed: %s" % e)
90 87
@@ -88,7 +88,7 b' from IPython.utils.localinterfaces import localhost'
88 88 from IPython.utils import submodule
89 89 from IPython.utils.traitlets import (
90 90 Dict, Unicode, Integer, List, Bool, Bytes,
91 DottedObjectName
91 DottedObjectName, TraitError,
92 92 )
93 93 from IPython.utils import py3compat
94 94 from IPython.utils.path import filefind, get_ipython_dir
@@ -201,8 +201,11 b' class NotebookWebApplication(web.Application):'
201 201 handlers.extend(load_handlers('services.clusters.handlers'))
202 202 handlers.extend(load_handlers('services.sessions.handlers'))
203 203 handlers.extend(load_handlers('services.nbconvert.handlers'))
204 handlers.extend([
205 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
204 # FIXME: /files/ should be handled by the Contents service when it exists
205 nbm = settings['notebook_manager']
206 if hasattr(nbm, 'notebook_dir'):
207 handlers.extend([
208 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
206 209 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
207 210 ])
208 211 # prepend base_url onto the patterns that we match
@@ -278,7 +281,7 b' aliases.update({'
278 281 'transport': 'KernelManager.transport',
279 282 'keyfile': 'NotebookApp.keyfile',
280 283 'certfile': 'NotebookApp.certfile',
281 'notebook-dir': 'NotebookManager.notebook_dir',
284 'notebook-dir': 'NotebookApp.notebook_dir',
282 285 'browser': 'NotebookApp.browser',
283 286 })
284 287
@@ -507,6 +510,24 b' class NotebookApp(BaseIPythonApplication):'
507 510 def _info_file_default(self):
508 511 info_file = "nbserver-%s.json"%os.getpid()
509 512 return os.path.join(self.profile_dir.security_dir, info_file)
513
514 notebook_dir = Unicode(py3compat.getcwd(), config=True,
515 help="The directory to use for notebooks and kernels."
516 )
517
518 def _notebook_dir_changed(self, name, old, new):
519 """Do a bit of validation of the notebook dir."""
520 if not os.path.isabs(new):
521 # If we receive a non-absolute path, make it absolute.
522 self.notebook_dir = os.path.abspath(new)
523 return
524 if not os.path.isdir(new):
525 raise TraitError("No such notebook dir: %r" % new)
526
527 # setting App.notebook_dir implies setting notebook and kernel dirs as well
528 self.config.FileNotebookManager.notebook_dir = new
529 self.config.MappingKernelManager.root_dir = new
530
510 531
511 532 def parse_command_line(self, argv=None):
512 533 super(NotebookApp, self).parse_command_line(argv)
@@ -519,7 +540,7 b' class NotebookApp(BaseIPythonApplication):'
519 540 self.log.critical("No such file or directory: %s", f)
520 541 self.exit(1)
521 542 if os.path.isdir(f):
522 self.config.FileNotebookManager.notebook_dir = f
543 self.notebook_dir = f
523 544 elif os.path.isfile(f):
524 545 self.file_to_run = f
525 546
@@ -730,7 +751,7 b' class NotebookApp(BaseIPythonApplication):'
730 751 'port': self.port,
731 752 'secure': bool(self.certfile),
732 753 'base_url': self.base_url,
733 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
754 'notebook_dir': os.path.abspath(self.notebook_dir),
734 755 }
735 756
736 757 def write_server_info_file(self):
@@ -16,6 +16,8 b' Authors:'
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 import os
20
19 21 from tornado import web
20 22
21 23 from IPython.kernel.multikernelmanager import MultiKernelManager
@@ -23,6 +25,9 b' from IPython.utils.traitlets import ('
23 25 Dict, List, Unicode,
24 26 )
25 27
28 from IPython.html.utils import to_os_path
29 from IPython.utils.py3compat import getcwd
30
26 31 #-----------------------------------------------------------------------------
27 32 # Classes
28 33 #-----------------------------------------------------------------------------
@@ -35,6 +40,17 b' class MappingKernelManager(MultiKernelManager):'
35 40 return "IPython.kernel.ioloop.IOLoopKernelManager"
36 41
37 42 kernel_argv = List(Unicode)
43
44 root_dir = Unicode(getcwd(), config=True)
45
46 def _root_dir_changed(self, name, old, new):
47 """Do a bit of validation of the root dir."""
48 if not os.path.isabs(new):
49 # If we receive a non-absolute path, make it absolute.
50 self.root_dir = os.path.abspath(new)
51 return
52 if not os.path.exists(new) or not os.path.isdir(new):
53 raise TraitError("kernel root dir %r is not a directory" % new)
38 54
39 55 #-------------------------------------------------------------------------
40 56 # Methods for managing kernels and sessions
@@ -44,8 +60,17 b' class MappingKernelManager(MultiKernelManager):'
44 60 """notice that a kernel died"""
45 61 self.log.warn("Kernel %s died, removing from map.", kernel_id)
46 62 self.remove_kernel(kernel_id)
47
48 def start_kernel(self, kernel_id=None, **kwargs):
63
64 def cwd_for_path(self, path):
65 """Turn API path into absolute OS path."""
66 os_path = to_os_path(path, self.root_dir)
67 # in the case of notebooks and kernels not being on the same filesystem,
68 # walk up to root_dir if the paths don't exist
69 while not os.path.exists(os_path) and os_path != self.root_dir:
70 os_path = os.path.dirname(os_path)
71 return os_path
72
73 def start_kernel(self, kernel_id=None, path=None, **kwargs):
49 74 """Start a kernel for a session an return its kernel_id.
50 75
51 76 Parameters
@@ -54,9 +79,14 b' class MappingKernelManager(MultiKernelManager):'
54 79 The uuid to associate the new kernel with. If this
55 80 is not None, this kernel will be persistent whenever it is
56 81 requested.
82 path : API path
83 The API path (unicode, '/' delimited) for the cwd.
84 Will be transformed to an OS path relative to root_dir.
57 85 """
58 86 if kernel_id is None:
59 87 kwargs['extra_arguments'] = self.kernel_argv
88 if path is not None:
89 kwargs['cwd'] = self.cwd_for_path(path)
60 90 kernel_id = super(MappingKernelManager, self).start_kernel(**kwargs)
61 91 self.log.info("Kernel started: %s" % kernel_id)
62 92 self.log.debug("Kernel args: %r" % kwargs)
@@ -18,7 +18,6 b' Authors:'
18 18 #-----------------------------------------------------------------------------
19 19
20 20 import io
21 import itertools
22 21 import os
23 22 import glob
24 23 import shutil
@@ -28,8 +27,9 b' from tornado import web'
28 27 from .nbmanager import NotebookManager
29 28 from IPython.nbformat import current
30 29 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
30 from IPython.utils.py3compat import getcwd
31 31 from IPython.utils import tz
32 from IPython.html.utils import is_hidden
32 from IPython.html.utils import is_hidden, to_os_path
33 33
34 34 #-----------------------------------------------------------------------------
35 35 # Classes
@@ -46,7 +46,17 b' class FileNotebookManager(NotebookManager):'
46 46 short `--script` flag.
47 47 """
48 48 )
49 notebook_dir = Unicode(getcwd(), config=True)
49 50
51 def _notebook_dir_changed(self, name, old, new):
52 """Do a bit of validation of the notebook dir."""
53 if not os.path.isabs(new):
54 # If we receive a non-absolute path, make it absolute.
55 self.notebook_dir = os.path.abspath(new)
56 return
57 if not os.path.exists(new) or not os.path.isdir(new):
58 raise TraitError("notebook dir %r is not a directory" % new)
59
50 60 checkpoint_dir = Unicode(config=True,
51 61 help="""The location in which to keep notebook checkpoints
52 62
@@ -75,9 +85,9 b' class FileNotebookManager(NotebookManager):'
75 85 def get_notebook_names(self, path=''):
76 86 """List all notebook names in the notebook dir and path."""
77 87 path = path.strip('/')
78 if not os.path.isdir(self.get_os_path(path=path)):
88 if not os.path.isdir(self._get_os_path(path=path)):
79 89 raise web.HTTPError(404, 'Directory not found: ' + path)
80 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
90 names = glob.glob(self._get_os_path('*'+self.filename_ext, path))
81 91 names = [os.path.basename(name)
82 92 for name in names]
83 93 return names
@@ -97,7 +107,7 b' class FileNotebookManager(NotebookManager):'
97 107 Whether the path is indeed a directory.
98 108 """
99 109 path = path.strip('/')
100 os_path = self.get_os_path(path=path)
110 os_path = self._get_os_path(path=path)
101 111 return os.path.isdir(os_path)
102 112
103 113 def is_hidden(self, path):
@@ -116,10 +126,10 b' class FileNotebookManager(NotebookManager):'
116 126
117 127 """
118 128 path = path.strip('/')
119 os_path = self.get_os_path(path=path)
129 os_path = self._get_os_path(path=path)
120 130 return is_hidden(os_path, self.notebook_dir)
121 131
122 def get_os_path(self, name=None, path=''):
132 def _get_os_path(self, name=None, path=''):
123 133 """Given a notebook name and a URL path, return its file system
124 134 path.
125 135
@@ -138,12 +148,9 b' class FileNotebookManager(NotebookManager):'
138 148 server started), the relative path, and the filename with the
139 149 current operating system's url.
140 150 """
141 parts = path.strip('/').split('/')
142 parts = [p for p in parts if p != ''] # remove duplicate splits
143 151 if name is not None:
144 parts.append(name)
145 path = os.path.join(self.notebook_dir, *parts)
146 return path
152 path = path + '/' + name
153 return to_os_path(path, self.notebook_dir)
147 154
148 155 def notebook_exists(self, name, path=''):
149 156 """Returns a True if the notebook exists. Else, returns False.
@@ -160,7 +167,7 b' class FileNotebookManager(NotebookManager):'
160 167 bool
161 168 """
162 169 path = path.strip('/')
163 nbpath = self.get_os_path(name, path=path)
170 nbpath = self._get_os_path(name, path=path)
164 171 return os.path.isfile(nbpath)
165 172
166 173 # TODO: Remove this after we create the contents web service and directories are
@@ -168,13 +175,13 b' class FileNotebookManager(NotebookManager):'
168 175 def list_dirs(self, path):
169 176 """List the directories for a given API style path."""
170 177 path = path.strip('/')
171 os_path = self.get_os_path('', path)
178 os_path = self._get_os_path('', path)
172 179 if not os.path.isdir(os_path) or is_hidden(os_path, self.notebook_dir):
173 180 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
174 181 dir_names = os.listdir(os_path)
175 182 dirs = []
176 183 for name in dir_names:
177 os_path = self.get_os_path(name, path)
184 os_path = self._get_os_path(name, path)
178 185 if os.path.isdir(os_path) and not is_hidden(os_path, self.notebook_dir):
179 186 try:
180 187 model = self.get_dir_model(name, path)
@@ -189,7 +196,7 b' class FileNotebookManager(NotebookManager):'
189 196 def get_dir_model(self, name, path=''):
190 197 """Get the directory model given a directory name and its API style path"""
191 198 path = path.strip('/')
192 os_path = self.get_os_path(name, path)
199 os_path = self._get_os_path(name, path)
193 200 if not os.path.isdir(os_path):
194 201 raise IOError('directory does not exist: %r' % os_path)
195 202 info = os.stat(os_path)
@@ -245,7 +252,7 b' class FileNotebookManager(NotebookManager):'
245 252 path = path.strip('/')
246 253 if not self.notebook_exists(name=name, path=path):
247 254 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
248 os_path = self.get_os_path(name, path)
255 os_path = self._get_os_path(name, path)
249 256 info = os.stat(os_path)
250 257 last_modified = tz.utcfromtimestamp(info.st_mtime)
251 258 created = tz.utcfromtimestamp(info.st_ctime)
@@ -284,7 +291,7 b' class FileNotebookManager(NotebookManager):'
284 291 self.rename_notebook(name, path, new_name, new_path)
285 292
286 293 # Save the notebook file
287 os_path = self.get_os_path(new_name, new_path)
294 os_path = self._get_os_path(new_name, new_path)
288 295 nb = current.to_notebook_json(model['content'])
289 296
290 297 self.check_and_sign(nb, new_path, new_name)
@@ -324,7 +331,7 b' class FileNotebookManager(NotebookManager):'
324 331 def delete_notebook(self, name, path=''):
325 332 """Delete notebook by name and path."""
326 333 path = path.strip('/')
327 os_path = self.get_os_path(name, path)
334 os_path = self._get_os_path(name, path)
328 335 if not os.path.isfile(os_path):
329 336 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
330 337
@@ -346,8 +353,8 b' class FileNotebookManager(NotebookManager):'
346 353 if new_name == old_name and new_path == old_path:
347 354 return
348 355
349 new_os_path = self.get_os_path(new_name, new_path)
350 old_os_path = self.get_os_path(old_name, old_path)
356 new_os_path = self._get_os_path(new_name, new_path)
357 old_os_path = self._get_os_path(old_name, old_path)
351 358
352 359 # Should we proceed with the move?
353 360 if os.path.isfile(new_os_path):
@@ -409,7 +416,7 b' class FileNotebookManager(NotebookManager):'
409 416 def create_checkpoint(self, name, path=''):
410 417 """Create a checkpoint from the current state of a notebook"""
411 418 path = path.strip('/')
412 nb_path = self.get_os_path(name, path)
419 nb_path = self._get_os_path(name, path)
413 420 # only the one checkpoint ID:
414 421 checkpoint_id = u"checkpoint"
415 422 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
@@ -439,7 +446,7 b' class FileNotebookManager(NotebookManager):'
439 446 """restore a notebook to a checkpointed state"""
440 447 path = path.strip('/')
441 448 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
442 nb_path = self.get_os_path(name, path)
449 nb_path = self._get_os_path(name, path)
443 450 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
444 451 if not os.path.isfile(cp_path):
445 452 self.log.debug("checkpoint file does not exist: %s", cp_path)
@@ -22,8 +22,7 b' import os'
22 22
23 23 from IPython.config.configurable import LoggingConfigurable
24 24 from IPython.nbformat import current, sign
25 from IPython.utils import py3compat
26 from IPython.utils.traitlets import Instance, Unicode, TraitError
25 from IPython.utils.traitlets import Instance, Unicode
27 26
28 27 #-----------------------------------------------------------------------------
29 28 # Classes
@@ -31,16 +30,6 b' from IPython.utils.traitlets import Instance, Unicode, TraitError'
31 30
32 31 class NotebookManager(LoggingConfigurable):
33 32
34 # Todo:
35 # The notebook_dir attribute is used to mean a couple of different things:
36 # 1. Where the notebooks are stored if FileNotebookManager is used.
37 # 2. The cwd of the kernel for a project.
38 # Right now we use this attribute in a number of different places and
39 # we are going to have to disentangle all of this.
40 notebook_dir = Unicode(py3compat.getcwd(), config=True, help="""
41 The directory to use for notebooks.
42 """)
43
44 33 filename_ext = Unicode(u'.ipynb')
45 34
46 35 notary = Instance(sign.NotebookNotary)
@@ -251,19 +240,4 b' class NotebookManager(LoggingConfigurable):'
251 240 if not trusted:
252 241 self.log.warn("Notebook %s/%s is not trusted", path, name)
253 242 self.notary.mark_cells(nb, trusted)
254
255 def _notebook_dir_changed(self, name, old, new):
256 """Do a bit of validation of the notebook dir."""
257 if not os.path.isabs(new):
258 # If we receive a non-absolute path, make it absolute.
259 self.notebook_dir = os.path.abspath(new)
260 return
261 if os.path.exists(new) and not os.path.isdir(new):
262 raise TraitError("notebook dir %r is not a directory" % new)
263 if not os.path.exists(new):
264 self.log.info("Creating notebook dir %s", new)
265 try:
266 os.mkdir(new)
267 except:
268 raise TraitError("Couldn't create notebook dir %r" % new)
269 243
@@ -23,12 +23,6 b' class TestFileNotebookManager(TestCase):'
23 23 fm = FileNotebookManager(notebook_dir=td)
24 24 self.assertEqual(fm.notebook_dir, td)
25 25
26 def test_create_nb_dir(self):
27 with TemporaryDirectory() as td:
28 nbdir = os.path.join(td, 'notebooks')
29 fm = FileNotebookManager(notebook_dir=nbdir)
30 self.assertEqual(fm.notebook_dir, nbdir)
31
32 26 def test_missing_nb_dir(self):
33 27 with TemporaryDirectory() as td:
34 28 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
@@ -42,20 +36,20 b' class TestFileNotebookManager(TestCase):'
42 36 # full filesystem path should be returned with correct operating system
43 37 # separators.
44 38 with TemporaryDirectory() as td:
45 nbdir = os.path.join(td, 'notebooks')
39 nbdir = td
46 40 fm = FileNotebookManager(notebook_dir=nbdir)
47 path = fm.get_os_path('test.ipynb', '/path/to/notebook/')
41 path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
48 42 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
49 43 fs_path = os.path.join(fm.notebook_dir, *rel_path_list)
50 44 self.assertEqual(path, fs_path)
51 45
52 46 fm = FileNotebookManager(notebook_dir=nbdir)
53 path = fm.get_os_path('test.ipynb')
47 path = fm._get_os_path('test.ipynb')
54 48 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
55 49 self.assertEqual(path, fs_path)
56 50
57 51 fm = FileNotebookManager(notebook_dir=nbdir)
58 path = fm.get_os_path('test.ipynb', '////')
52 path = fm._get_os_path('test.ipynb', '////')
59 53 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
60 54 self.assertEqual(path, fs_path)
61 55
@@ -62,7 +62,7 b' class SessionRootHandler(IPythonHandler):'
62 62 if sm.session_exists(name=name, path=path):
63 63 model = sm.get_session(name=name, path=path)
64 64 else:
65 kernel_id = km.start_kernel(cwd=nbm.get_os_path(path))
65 kernel_id = km.start_kernel(path=path)
66 66 model = sm.create_session(name=name, path=path, kernel_id=kernel_id)
67 67 location = url_path_join(self.base_url, 'api', 'sessions', model['id'])
68 68 self.set_header('Location', url_escape(location))
@@ -11,10 +11,16 b''
11 11 # Imports
12 12 #-----------------------------------------------------------------------------
13 13
14 import os
15 from tempfile import NamedTemporaryFile
16
14 17 import nose.tools as nt
15 18
19 from IPython.utils.tempdir import TemporaryDirectory
20 from IPython.utils.traitlets import TraitError
16 21 import IPython.testing.tools as tt
17 22 from IPython.html import notebookapp
23 NotebookApp = notebookapp.NotebookApp
18 24
19 25 #-----------------------------------------------------------------------------
20 26 # Test functions
@@ -25,7 +31,7 b' def test_help_output():'
25 31 tt.help_all_output_test('notebook')
26 32
27 33 def test_server_info_file():
28 nbapp = notebookapp.NotebookApp(profile='nbserver_file_test')
34 nbapp = NotebookApp(profile='nbserver_file_test')
29 35 def get_servers():
30 36 return list(notebookapp.list_running_servers(profile='nbserver_file_test'))
31 37 nbapp.initialize(argv=[])
@@ -38,4 +44,30 b' def test_server_info_file():'
38 44 nt.assert_equal(get_servers(), [])
39 45
40 46 # The ENOENT error should be silenced.
41 nbapp.remove_server_info_file() No newline at end of file
47 nbapp.remove_server_info_file()
48
49 def test_nb_dir():
50 with TemporaryDirectory() as td:
51 app = NotebookApp(notebook_dir=td)
52 nt.assert_equal(app.notebook_dir, td)
53
54 def test_no_create_nb_dir():
55 with TemporaryDirectory() as td:
56 nbdir = os.path.join(td, 'notebooks')
57 app = NotebookApp()
58 with nt.assert_raises(TraitError):
59 app.notebook_dir = nbdir
60
61 def test_missing_nb_dir():
62 with TemporaryDirectory() as td:
63 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
64 app = NotebookApp()
65 with nt.assert_raises(TraitError):
66 app.notebook_dir = nbdir
67
68 def test_invalid_nb_dir():
69 with NamedTemporaryFile() as tf:
70 app = NotebookApp()
71 with nt.assert_raises(TraitError):
72 app.notebook_dir = tf
73
@@ -81,7 +81,7 b' def url_unescape(path):'
81 81 ])
82 82
83 83 def is_hidden(abs_path, abs_root=''):
84 """Is a file is hidden or contained in a hidden directory.
84 """Is a file hidden or contained in a hidden directory?
85 85
86 86 This will start with the rightmost path element and work backwards to the
87 87 given root to see if a path is hidden or in a hidden directory. Hidden is
@@ -112,3 +112,14 b" def is_hidden(abs_path, abs_root=''):"
112 112
113 113 return False
114 114
115 def to_os_path(path, root=''):
116 """Convert an API path to a filesystem path
117
118 If given, root will be prepended to the path.
119 root must be a filesystem path already.
120 """
121 parts = path.strip('/').split('/')
122 parts = [p for p in parts if p != ''] # remove duplicate splits
123 path = os.path.join(root, *parts)
124 return path
125
General Comments 0
You need to be logged in to leave comments. Login now