##// END OF EJS Templates
rename notebooks service to contents service...
MinRK -
Show More
@@ -1,4 +1,4 b''
1 """Base Tornado handlers for the notebook."""
1 """Base Tornado handlers for the notebook server."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
@@ -141,8 +141,8 b' class IPythonHandler(AuthenticatedHandler):'
141 141 return self.settings['kernel_manager']
142 142
143 143 @property
144 def notebook_manager(self):
145 return self.settings['notebook_manager']
144 def contents_manager(self):
145 return self.settings['contents_manager']
146 146
147 147 @property
148 148 def cluster_manager(self):
@@ -158,7 +158,7 b' class IPythonHandler(AuthenticatedHandler):'
158 158
159 159 @property
160 160 def project_dir(self):
161 return self.notebook_manager.notebook_dir
161 return getattr(self.contents_manager, 'root_dir', '/')
162 162
163 163 #---------------------------------------------------------------
164 164 # CORS
@@ -73,7 +73,7 b' class NbconvertFileHandler(IPythonHandler):'
73 73 exporter = get_exporter(format, config=self.config, log=self.log)
74 74
75 75 path = path.strip('/')
76 model = self.notebook_manager.get_notebook(name=name, path=path)
76 model = self.contents_manager.get(name=name, path=path)
77 77
78 78 self.set_header('Last-Modified', model['last_modified'])
79 79
@@ -106,7 +106,7 b' class APITest(NotebookTestBase):'
106 106
107 107 @onlyif_cmds_exist('pandoc')
108 108 def test_from_post(self):
109 nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
109 nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
110 110 nbmodel = requests.get(nbmodel_url).json()
111 111
112 112 r = self.nbconvert_api.from_post(format='html', nbmodel=nbmodel)
@@ -121,7 +121,7 b' class APITest(NotebookTestBase):'
121 121
122 122 @onlyif_cmds_exist('pandoc')
123 123 def test_from_post_zip(self):
124 nbmodel_url = url_path_join(self.base_url(), 'api/notebooks/foo/testnb.ipynb')
124 nbmodel_url = url_path_join(self.base_url(), 'api/contents/foo/testnb.ipynb')
125 125 nbmodel = requests.get(nbmodel_url).json()
126 126
127 127 r = self.nbconvert_api.from_post(format='latex', nbmodel=nbmodel)
@@ -35,12 +35,12 b' class NotebookHandler(IPythonHandler):'
35 35 """get renders the notebook template if a name is given, or
36 36 redirects to the '/files/' handler if the name is not given."""
37 37 path = path.strip('/')
38 nbm = self.notebook_manager
38 cm = self.contents_manager
39 39 if name is None:
40 40 raise web.HTTPError(500, "This shouldn't be accessible: %s" % self.request.uri)
41 41
42 42 # a .ipynb filename was given
43 if not nbm.notebook_exists(name, path):
43 if not cm.file_exists(name, path):
44 44 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
45 45 name = url_escape(name)
46 46 path = url_escape(path)
@@ -55,8 +55,8 b' class NotebookHandler(IPythonHandler):'
55 55
56 56 class NotebookRedirectHandler(IPythonHandler):
57 57 def get(self, path=''):
58 nbm = self.notebook_manager
59 if nbm.path_exists(path):
58 cm = self.contents_manager
59 if cm.path_exists(path):
60 60 # it's a *directory*, redirect to /tree
61 61 url = url_path_join(self.base_url, 'tree', path)
62 62 else:
@@ -68,7 +68,7 b' class NotebookRedirectHandler(IPythonHandler):'
68 68 # but so is the files handler itself,
69 69 # so it should work until both are cleaned up.
70 70 parts = path.split('/')
71 files_path = os.path.join(nbm.notebook_dir, *parts)
71 files_path = os.path.join(cm.root_dir, *parts)
72 72 if not os.path.exists(files_path):
73 73 self.log.warn("Deprecated files/ URL: %s", path)
74 74 path = path.replace('/files/', '/', 1)
@@ -55,8 +55,8 b' from IPython.html import DEFAULT_STATIC_FILES_PATH'
55 55 from .base.handlers import Template404
56 56 from .log import log_request
57 57 from .services.kernels.kernelmanager import MappingKernelManager
58 from .services.notebooks.nbmanager import NotebookManager
59 from .services.notebooks.filenbmanager import FileNotebookManager
58 from .services.contents.manager import ContentsManager
59 from .services.contents.filemanager import FileContentsManager
60 60 from .services.clusters.clustermanager import ClusterManager
61 61 from .services.sessions.sessionmanager import SessionManager
62 62
@@ -121,19 +121,19 b' def load_handlers(name):'
121 121
122 122 class NotebookWebApplication(web.Application):
123 123
124 def __init__(self, ipython_app, kernel_manager, notebook_manager,
124 def __init__(self, ipython_app, kernel_manager, contents_manager,
125 125 cluster_manager, session_manager, kernel_spec_manager, log,
126 126 base_url, settings_overrides, jinja_env_options):
127 127
128 128 settings = self.init_settings(
129 ipython_app, kernel_manager, notebook_manager, cluster_manager,
129 ipython_app, kernel_manager, contents_manager, cluster_manager,
130 130 session_manager, kernel_spec_manager, log, base_url,
131 131 settings_overrides, jinja_env_options)
132 132 handlers = self.init_handlers(settings)
133 133
134 134 super(NotebookWebApplication, self).__init__(handlers, **settings)
135 135
136 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
136 def init_settings(self, ipython_app, kernel_manager, contents_manager,
137 137 cluster_manager, session_manager, kernel_spec_manager,
138 138 log, base_url, settings_overrides,
139 139 jinja_env_options=None):
@@ -165,7 +165,7 b' class NotebookWebApplication(web.Application):'
165 165
166 166 # managers
167 167 kernel_manager=kernel_manager,
168 notebook_manager=notebook_manager,
168 contents_manager=contents_manager,
169 169 cluster_manager=cluster_manager,
170 170 session_manager=session_manager,
171 171 kernel_spec_manager=kernel_spec_manager,
@@ -193,18 +193,20 b' class NotebookWebApplication(web.Application):'
193 193 handlers.extend(load_handlers('nbconvert.handlers'))
194 194 handlers.extend(load_handlers('kernelspecs.handlers'))
195 195 handlers.extend(load_handlers('services.kernels.handlers'))
196 handlers.extend(load_handlers('services.notebooks.handlers'))
196 handlers.extend(load_handlers('services.contents.handlers'))
197 197 handlers.extend(load_handlers('services.clusters.handlers'))
198 198 handlers.extend(load_handlers('services.sessions.handlers'))
199 199 handlers.extend(load_handlers('services.nbconvert.handlers'))
200 200 handlers.extend(load_handlers('services.kernelspecs.handlers'))
201 201 # FIXME: /files/ should be handled by the Contents service when it exists
202 nbm = settings['notebook_manager']
203 if hasattr(nbm, 'notebook_dir'):
204 handlers.extend([
205 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : nbm.notebook_dir}),
202 cm = settings['contents_manager']
203 if hasattr(cm, 'root_dir'):
204 handlers.append(
205 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : cm.root_dir}),
206 )
207 handlers.append(
206 208 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
207 ])
209 )
208 210 # prepend base_url onto the patterns that we match
209 211 new_handlers = []
210 212 for handler in handlers:
@@ -263,11 +265,6 b" flags['no-mathjax']=("
263 265 """
264 266 )
265 267
266 # Add notebook manager flags
267 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
268 'Auto-save a .py script everytime the .ipynb notebook is saved',
269 'Do not auto-save .py scripts for every notebook'))
270
271 268 aliases = dict(base_aliases)
272 269
273 270 aliases.update({
@@ -302,7 +299,7 b' class NotebookApp(BaseIPythonApplication):'
302 299
303 300 classes = [
304 301 KernelManager, ProfileDir, Session, MappingKernelManager,
305 NotebookManager, FileNotebookManager, NotebookNotary,
302 ContentsManager, FileContentsManager, NotebookNotary,
306 303 ]
307 304 flags = Dict(flags)
308 305 aliases = Dict(aliases)
@@ -557,7 +554,7 b' class NotebookApp(BaseIPythonApplication):'
557 554 else:
558 555 self.log.info("Using MathJax: %s", new)
559 556
560 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
557 contents_manager_class = DottedObjectName('IPython.html.services.contents.filemanager.FileContentsManager',
561 558 config=True,
562 559 help='The notebook manager class to use.'
563 560 )
@@ -621,7 +618,7 b' class NotebookApp(BaseIPythonApplication):'
621 618 raise TraitError("No such notebook dir: %r" % new)
622 619
623 620 # setting App.notebook_dir implies setting notebook and kernel dirs as well
624 self.config.FileNotebookManager.notebook_dir = new
621 self.config.FileContentsManager.root_dir = new
625 622 self.config.MappingKernelManager.root_dir = new
626 623
627 624
@@ -658,12 +655,12 b' class NotebookApp(BaseIPythonApplication):'
658 655 parent=self, log=self.log, kernel_argv=self.kernel_argv,
659 656 connection_dir = self.profile_dir.security_dir,
660 657 )
661 kls = import_item(self.notebook_manager_class)
662 self.notebook_manager = kls(parent=self, log=self.log)
658 kls = import_item(self.contents_manager_class)
659 self.contents_manager = kls(parent=self, log=self.log)
663 660 kls = import_item(self.session_manager_class)
664 661 self.session_manager = kls(parent=self, log=self.log,
665 662 kernel_manager=self.kernel_manager,
666 notebook_manager=self.notebook_manager)
663 contents_manager=self.contents_manager)
667 664 kls = import_item(self.cluster_manager_class)
668 665 self.cluster_manager = kls(parent=self, log=self.log)
669 666 self.cluster_manager.update_profiles()
@@ -688,7 +685,7 b' class NotebookApp(BaseIPythonApplication):'
688 685 self.webapp_settings['allow_credentials'] = self.allow_credentials
689 686
690 687 self.web_app = NotebookWebApplication(
691 self, self.kernel_manager, self.notebook_manager,
688 self, self.kernel_manager, self.contents_manager,
692 689 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
693 690 self.log, self.base_url, self.webapp_settings,
694 691 self.jinja_environment_options
@@ -838,7 +835,7 b' class NotebookApp(BaseIPythonApplication):'
838 835
839 836 def notebook_info(self):
840 837 "Return the current working directory and the server url information"
841 info = self.notebook_manager.info_string() + "\n"
838 info = self.contents_manager.info_string() + "\n"
842 839 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
843 840 return info + "The IPython Notebook is running at: %s" % self.display_url
844 841
@@ -1,4 +1,4 b''
1 """A notebook manager that uses the local file system for storage."""
1 """A contents manager that uses the local file system for storage."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
@@ -10,7 +10,7 b' import shutil'
10 10
11 11 from tornado import web
12 12
13 from .nbmanager import NotebookManager
13 from .manager import ContentsManager
14 14 from IPython.nbformat import current
15 15 from IPython.utils.path import ensure_dir_exists
16 16 from IPython.utils.traitlets import Unicode, Bool, TraitError
@@ -22,31 +22,19 b' def sort_key(item):'
22 22 """Case-insensitive sorting."""
23 23 return item['name'].lower()
24 24
25 #-----------------------------------------------------------------------------
26 # Classes
27 #-----------------------------------------------------------------------------
28 25
29 class FileNotebookManager(NotebookManager):
26 class FileContentsManager(ContentsManager):
30 27
31 save_script = Bool(False, config=True,
32 help="""Automatically create a Python script when saving the notebook.
28 root_dir = Unicode(getcwd(), config=True)
33 29
34 For easier use of import, %run and %load across notebooks, a
35 <notebook-name>.py script will be created next to any
36 <notebook-name>.ipynb on each save. This can also be set with the
37 short `--script` flag.
38 """
39 )
40 notebook_dir = Unicode(getcwd(), config=True)
41
42 def _notebook_dir_changed(self, name, old, new):
43 """Do a bit of validation of the notebook dir."""
30 def _root_dir_changed(self, name, old, new):
31 """Do a bit of validation of the root_dir."""
44 32 if not os.path.isabs(new):
45 33 # If we receive a non-absolute path, make it absolute.
46 self.notebook_dir = os.path.abspath(new)
34 self.root_dir = os.path.abspath(new)
47 35 return
48 36 if not os.path.exists(new) or not os.path.isdir(new):
49 raise TraitError("notebook dir %r is not a directory" % new)
37 raise TraitError("%r is not a directory" % new)
50 38
51 39 checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
52 40 help="""The directory name in which to keep notebook checkpoints
@@ -68,14 +56,13 b' class FileNotebookManager(NotebookManager):'
68 56 except OSError as e:
69 57 self.log.debug("copystat on %s failed", dest, exc_info=True)
70 58
71 def get_notebook_names(self, path=''):
72 """List all notebook names in the notebook dir and path."""
59 def get_names(self, path=''):
60 """List all filenames in the path (relative to root_dir)."""
73 61 path = path.strip('/')
74 62 if not os.path.isdir(self._get_os_path(path=path)):
75 63 raise web.HTTPError(404, 'Directory not found: ' + path)
76 names = glob.glob(self._get_os_path('*'+self.filename_ext, path))
77 names = [os.path.basename(name)
78 for name in names]
64 names = glob.glob(self._get_os_path('*', path))
65 names = [ os.path.basename(name) for name in names if os.path.isfile(name)]
79 66 return names
80 67
81 68 def path_exists(self, path):
@@ -85,7 +72,7 b' class FileNotebookManager(NotebookManager):'
85 72 ----------
86 73 path : string
87 74 The path to check. This is an API path (`/` separated,
88 relative to base notebook-dir).
75 relative to root_dir).
89 76
90 77 Returns
91 78 -------
@@ -103,7 +90,7 b' class FileNotebookManager(NotebookManager):'
103 90 ----------
104 91 path : string
105 92 The path to check. This is an API path (`/` separated,
106 relative to base notebook-dir).
93 relative to root_dir).
107 94
108 95 Returns
109 96 -------
@@ -113,40 +100,38 b' class FileNotebookManager(NotebookManager):'
113 100 """
114 101 path = path.strip('/')
115 102 os_path = self._get_os_path(path=path)
116 return is_hidden(os_path, self.notebook_dir)
103 return is_hidden(os_path, self.root_dir)
117 104
118 105 def _get_os_path(self, name=None, path=''):
119 """Given a notebook name and a URL path, return its file system
106 """Given a filename and a URL path, return its file system
120 107 path.
121 108
122 109 Parameters
123 110 ----------
124 111 name : string
125 The name of a notebook file with the .ipynb extension
112 A filename
126 113 path : string
127 114 The relative URL path (with '/' as separator) to the named
128 notebook.
115 file.
129 116
130 117 Returns
131 118 -------
132 119 path : string
133 A file system path that combines notebook_dir (location where
134 server started), the relative path, and the filename with the
135 current operating system's url.
120 API path to be evaluated relative to root_dir.
136 121 """
137 122 if name is not None:
138 123 path = path + '/' + name
139 return to_os_path(path, self.notebook_dir)
124 return to_os_path(path, self.root_dir)
140 125
141 def notebook_exists(self, name, path=''):
142 """Returns a True if the notebook exists. Else, returns False.
126 def file_exists(self, name, path=''):
127 """Returns a True if the file exists, else returns False.
143 128
144 129 Parameters
145 130 ----------
146 131 name : string
147 The name of the notebook you are checking.
132 The name of the file you are checking.
148 133 path : string
149 The relative path to the notebook (with '/' as separator)
134 The relative path to the file's directory (with '/' as separator)
150 135
151 136 Returns
152 137 -------
@@ -164,14 +149,14 b' class FileNotebookManager(NotebookManager):'
164 149 os_path = self._get_os_path('', path)
165 150 if not os.path.isdir(os_path):
166 151 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
167 elif is_hidden(os_path, self.notebook_dir):
152 elif is_hidden(os_path, self.root_dir):
168 153 self.log.info("Refusing to serve hidden directory, via 404 Error")
169 154 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
170 155 dir_names = os.listdir(os_path)
171 156 dirs = []
172 157 for name in dir_names:
173 158 os_path = self._get_os_path(name, path)
174 if os.path.isdir(os_path) and not is_hidden(os_path, self.notebook_dir)\
159 if os.path.isdir(os_path) and not is_hidden(os_path, self.root_dir)\
175 160 and self.should_list(name):
176 161 try:
177 162 model = self.get_dir_model(name, path)
@@ -201,7 +186,7 b' class FileNotebookManager(NotebookManager):'
201 186 model['type'] = 'directory'
202 187 return model
203 188
204 def list_notebooks(self, path):
189 def list_files(self, path):
205 190 """Returns a list of dictionaries that are the standard model
206 191 for all notebooks in the relative 'path'.
207 192
@@ -217,13 +202,13 b' class FileNotebookManager(NotebookManager):'
217 202 a list of the notebook models without 'content'
218 203 """
219 204 path = path.strip('/')
220 notebook_names = self.get_notebook_names(path)
221 notebooks = [self.get_notebook(name, path, content=False)
222 for name in notebook_names if self.should_list(name)]
205 names = self.get_names(path)
206 notebooks = [self.get(name, path, content=False)
207 for name in names if self.should_list(name)]
223 208 notebooks = sorted(notebooks, key=sort_key)
224 209 return notebooks
225 210
226 def get_notebook(self, name, path='', content=True):
211 def get(self, name, path='', content=True):
227 212 """ Takes a path and name for a notebook and returns its model
228 213
229 214 Parameters
@@ -241,7 +226,7 b' class FileNotebookManager(NotebookManager):'
241 226 dict in the model as well.
242 227 """
243 228 path = path.strip('/')
244 if not self.notebook_exists(name=name, path=path):
229 if not self.file_exists(name=name, path=path):
245 230 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
246 231 os_path = self._get_os_path(name, path)
247 232 info = os.stat(os_path)
@@ -264,7 +249,7 b' class FileNotebookManager(NotebookManager):'
264 249 model['content'] = nb
265 250 return model
266 251
267 def save_notebook(self, model, name='', path=''):
252 def save(self, model, name='', path=''):
268 253 """Save the notebook model and return the model with no content."""
269 254 path = path.strip('/')
270 255
@@ -272,14 +257,14 b' class FileNotebookManager(NotebookManager):'
272 257 raise web.HTTPError(400, u'No notebook JSON data provided')
273 258
274 259 # One checkpoint should always exist
275 if self.notebook_exists(name, path) and not self.list_checkpoints(name, path):
260 if self.file_exists(name, path) and not self.list_checkpoints(name, path):
276 261 self.create_checkpoint(name, path)
277 262
278 263 new_path = model.get('path', path).strip('/')
279 264 new_name = model.get('name', name)
280 265
281 266 if path != new_path or name != new_name:
282 self.rename_notebook(name, path, new_name, new_path)
267 self.rename(name, path, new_name, new_path)
283 268
284 269 # Save the notebook file
285 270 os_path = self._get_os_path(new_name, new_path)
@@ -296,35 +281,25 b' class FileNotebookManager(NotebookManager):'
296 281 except Exception as e:
297 282 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
298 283
299 # Save .py script as well
300 if self.save_script:
301 py_path = os.path.splitext(os_path)[0] + '.py'
302 self.log.debug("Writing script %s", py_path)
303 try:
304 with io.open(py_path, 'w', encoding='utf-8') as f:
305 current.write(nb, f, u'py')
306 except Exception as e:
307 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s %s' % (py_path, e))
308
309 model = self.get_notebook(new_name, new_path, content=False)
284 model = self.get(new_name, new_path, content=False)
310 285 return model
311 286
312 def update_notebook(self, model, name, path=''):
313 """Update the notebook's path and/or name"""
287 def update(self, model, name, path=''):
288 """Update the file's path and/or name"""
314 289 path = path.strip('/')
315 290 new_name = model.get('name', name)
316 291 new_path = model.get('path', path).strip('/')
317 292 if path != new_path or name != new_name:
318 self.rename_notebook(name, path, new_name, new_path)
319 model = self.get_notebook(new_name, new_path, content=False)
293 self.rename(name, path, new_name, new_path)
294 model = self.get(new_name, new_path, content=False)
320 295 return model
321 296
322 def delete_notebook(self, name, path=''):
323 """Delete notebook by name and path."""
297 def delete(self, name, path=''):
298 """Delete file by name and path."""
324 299 path = path.strip('/')
325 300 os_path = self._get_os_path(name, path)
326 301 if not os.path.isfile(os_path):
327 raise web.HTTPError(404, u'Notebook does not exist: %s' % os_path)
302 raise web.HTTPError(404, u'File does not exist: %s' % os_path)
328 303
329 304 # clear checkpoints
330 305 for checkpoint in self.list_checkpoints(name, path):
@@ -334,11 +309,11 b' class FileNotebookManager(NotebookManager):'
334 309 self.log.debug("Unlinking checkpoint %s", cp_path)
335 310 os.unlink(cp_path)
336 311
337 self.log.debug("Unlinking notebook %s", os_path)
312 self.log.debug("Unlinking file %s", os_path)
338 313 os.unlink(os_path)
339 314
340 def rename_notebook(self, old_name, old_path, new_name, new_path):
341 """Rename a notebook."""
315 def rename(self, old_name, old_path, new_name, new_path):
316 """Rename a file."""
342 317 old_path = old_path.strip('/')
343 318 new_path = new_path.strip('/')
344 319 if new_name == old_name and new_path == old_path:
@@ -350,17 +325,12 b' class FileNotebookManager(NotebookManager):'
350 325 # Should we proceed with the move?
351 326 if os.path.isfile(new_os_path):
352 327 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
353 if self.save_script:
354 old_py_path = os.path.splitext(old_os_path)[0] + '.py'
355 new_py_path = os.path.splitext(new_os_path)[0] + '.py'
356 if os.path.isfile(new_py_path):
357 raise web.HTTPError(409, u'Python script with name already exists: %s' % new_py_path)
358 328
359 # Move the notebook file
329 # Move the file
360 330 try:
361 331 shutil.move(old_os_path, new_os_path)
362 332 except Exception as e:
363 raise web.HTTPError(500, u'Unknown error renaming notebook: %s %s' % (old_os_path, e))
333 raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_os_path, e))
364 334
365 335 # Move the checkpoints
366 336 old_checkpoints = self.list_checkpoints(old_name, old_path)
@@ -372,20 +342,16 b' class FileNotebookManager(NotebookManager):'
372 342 self.log.debug("Renaming checkpoint %s -> %s", old_cp_path, new_cp_path)
373 343 shutil.move(old_cp_path, new_cp_path)
374 344
375 # Move the .py script
376 if self.save_script:
377 shutil.move(old_py_path, new_py_path)
378
379 345 # Checkpoint-related utilities
380 346
381 347 def get_checkpoint_path(self, checkpoint_id, name, path=''):
382 348 """find the path to a checkpoint"""
383 349 path = path.strip('/')
384 basename, _ = os.path.splitext(name)
350 basename, ext = os.path.splitext(name)
385 351 filename = u"{name}-{checkpoint_id}{ext}".format(
386 352 name=basename,
387 353 checkpoint_id=checkpoint_id,
388 ext=self.filename_ext,
354 ext=ext,
389 355 )
390 356 os_path = self._get_os_path(path=path)
391 357 cp_dir = os.path.join(os_path, self.checkpoint_dir)
@@ -408,22 +374,22 b' class FileNotebookManager(NotebookManager):'
408 374 # public checkpoint API
409 375
410 376 def create_checkpoint(self, name, path=''):
411 """Create a checkpoint from the current state of a notebook"""
377 """Create a checkpoint from the current state of a file"""
412 378 path = path.strip('/')
413 nb_path = self._get_os_path(name, path)
379 src_path = self._get_os_path(name, path)
414 380 # only the one checkpoint ID:
415 381 checkpoint_id = u"checkpoint"
416 382 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
417 383 self.log.debug("creating checkpoint for notebook %s", name)
418 self._copy(nb_path, cp_path)
384 self._copy(src_path, cp_path)
419 385
420 386 # return the checkpoint info
421 387 return self.get_checkpoint_model(checkpoint_id, name, path)
422 388
423 389 def list_checkpoints(self, name, path=''):
424 """list the checkpoints for a given notebook
390 """list the checkpoints for a given file
425 391
426 This notebook manager currently only supports one checkpoint per notebook.
392 This contents manager currently only supports one checkpoint per file.
427 393 """
428 394 path = path.strip('/')
429 395 checkpoint_id = "checkpoint"
@@ -435,36 +401,37 b' class FileNotebookManager(NotebookManager):'
435 401
436 402
437 403 def restore_checkpoint(self, checkpoint_id, name, path=''):
438 """restore a notebook to a checkpointed state"""
404 """restore a file to a checkpointed state"""
439 405 path = path.strip('/')
440 self.log.info("restoring Notebook %s from checkpoint %s", name, checkpoint_id)
406 self.log.info("restoring %s from checkpoint %s", name, checkpoint_id)
441 407 nb_path = self._get_os_path(name, path)
442 408 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
443 409 if not os.path.isfile(cp_path):
444 410 self.log.debug("checkpoint file does not exist: %s", cp_path)
445 411 raise web.HTTPError(404,
446 u'Notebook checkpoint does not exist: %s-%s' % (name, checkpoint_id)
412 u'checkpoint does not exist: %s-%s' % (name, checkpoint_id)
447 413 )
448 414 # ensure notebook is readable (never restore from an unreadable notebook)
449 with io.open(cp_path, 'r', encoding='utf-8') as f:
450 current.read(f, u'json')
415 if cp_path.endswith('.ipynb'):
416 with io.open(cp_path, 'r', encoding='utf-8') as f:
417 current.read(f, u'json')
451 418 self._copy(cp_path, nb_path)
452 419 self.log.debug("copying %s -> %s", cp_path, nb_path)
453 420
454 421 def delete_checkpoint(self, checkpoint_id, name, path=''):
455 """delete a notebook's checkpoint"""
422 """delete a file's checkpoint"""
456 423 path = path.strip('/')
457 424 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
458 425 if not os.path.isfile(cp_path):
459 426 raise web.HTTPError(404,
460 u'Notebook checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
427 u'Checkpoint does not exist: %s%s-%s' % (path, name, checkpoint_id)
461 428 )
462 429 self.log.debug("unlinking %s", cp_path)
463 430 os.unlink(cp_path)
464 431
465 432 def info_string(self):
466 return "Serving notebooks from local directory: %s" % self.notebook_dir
433 return "Serving notebooks from local directory: %s" % self.root_dir
467 434
468 435 def get_kernel_path(self, name, path='', model=None):
469 """ Return the path to start kernel in """
470 return os.path.join(self.notebook_dir, path)
436 """Return the initial working dir a kernel associated with a given notebook"""
437 return os.path.join(self.root_dir, path)
@@ -1,20 +1,7 b''
1 """Tornado handlers for the notebooks web service.
1 """Tornado handlers for the contents web service."""
2 2
3 Authors:
4
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
18 5
19 6 import json
20 7
@@ -27,33 +14,29 b' from IPython.html.base.handlers import (IPythonHandler, json_errors,'
27 14 notebook_path_regex, path_regex,
28 15 notebook_name_regex)
29 16
30 #-----------------------------------------------------------------------------
31 # Notebook web service handlers
32 #-----------------------------------------------------------------------------
33
34 17
35 class NotebookHandler(IPythonHandler):
18 class ContentsHandler(IPythonHandler):
36 19
37 20 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
38 21
39 def notebook_location(self, name, path=''):
40 """Return the full URL location of a notebook based.
22 def location_url(self, name, path=''):
23 """Return the full URL location of a file.
41 24
42 25 Parameters
43 26 ----------
44 27 name : unicode
45 The base name of the notebook, such as "foo.ipynb".
28 The base name of the file, such as "foo.ipynb".
46 29 path : unicode
47 The URL path of the notebook.
30 The API path of the file, such as "foo/bar".
48 31 """
49 32 return url_escape(url_path_join(
50 self.base_url, 'api', 'notebooks', path, name
33 self.base_url, 'api', 'contents', path, name
51 34 ))
52 35
53 36 def _finish_model(self, model, location=True):
54 37 """Finish a JSON request with a model, setting relevant headers, etc."""
55 38 if location:
56 location = self.notebook_location(model['name'], model['path'])
39 location = self.location_url(model['name'], model['path'])
57 40 self.set_header('Location', location)
58 41 self.set_header('Last-Modified', model['last_modified'])
59 42 self.finish(json.dumps(model, default=date_default))
@@ -61,68 +44,68 b' class NotebookHandler(IPythonHandler):'
61 44 @web.authenticated
62 45 @json_errors
63 46 def get(self, path='', name=None):
64 """Return a Notebook or list of notebooks.
47 """Return a file or list of files.
65 48
66 * GET with path and no notebook name lists notebooks in a directory
67 * GET with path and notebook name returns notebook JSON
49 * GET with path and no filename lists files in a directory
50 * GET with path and filename returns file contents model
68 51 """
69 nbm = self.notebook_manager
70 # Check to see if a notebook name was given
52 cm = self.contents_manager
53 # Check to see if a filename was given
71 54 if name is None:
72 55 # TODO: Remove this after we create the contents web service and directories are
73 56 # no longer listed by the notebook web service. This should only handle notebooks
74 57 # and not directories.
75 dirs = nbm.list_dirs(path)
76 notebooks = []
58 dirs = cm.list_dirs(path)
59 files = []
77 60 index = []
78 for nb in nbm.list_notebooks(path):
61 for nb in cm.list_files(path):
79 62 if nb['name'].lower() == 'index.ipynb':
80 63 index.append(nb)
81 64 else:
82 notebooks.append(nb)
83 notebooks = index + dirs + notebooks
84 self.finish(json.dumps(notebooks, default=date_default))
65 files.append(nb)
66 files = index + dirs + files
67 self.finish(json.dumps(files, default=date_default))
85 68 return
86 69 # get and return notebook representation
87 model = nbm.get_notebook(name, path)
70 model = cm.get(name, path)
88 71 self._finish_model(model, location=False)
89 72
90 73 @web.authenticated
91 74 @json_errors
92 75 def patch(self, path='', name=None):
93 76 """PATCH renames a notebook without re-uploading content."""
94 nbm = self.notebook_manager
77 cm = self.contents_manager
95 78 if name is None:
96 raise web.HTTPError(400, u'Notebook name missing')
79 raise web.HTTPError(400, u'Filename missing')
97 80 model = self.get_json_body()
98 81 if model is None:
99 82 raise web.HTTPError(400, u'JSON body missing')
100 model = nbm.update_notebook(model, name, path)
83 model = cm.update(model, name, path)
101 84 self._finish_model(model)
102 85
103 def _copy_notebook(self, copy_from, path, copy_to=None):
104 """Copy a notebook in path, optionally specifying the new name.
86 def _copy(self, copy_from, path, copy_to=None):
87 """Copy a file in path, optionally specifying the new name.
105 88
106 89 Only support copying within the same directory.
107 90 """
108 self.log.info(u"Copying notebook from %s/%s to %s/%s",
91 self.log.info(u"Copying from %s/%s to %s/%s",
109 92 path, copy_from,
110 93 path, copy_to or '',
111 94 )
112 model = self.notebook_manager.copy_notebook(copy_from, copy_to, path)
95 model = self.contents_manager.copy(copy_from, copy_to, path)
113 96 self.set_status(201)
114 97 self._finish_model(model)
115 98
116 def _upload_notebook(self, model, path, name=None):
117 """Upload a notebook
99 def _upload(self, model, path, name=None):
100 """Upload a file
118 101
119 102 If name specified, create it in path/name.
120 103 """
121 self.log.info(u"Uploading notebook to %s/%s", path, name or '')
104 self.log.info(u"Uploading file to %s/%s", path, name or '')
122 105 if name:
123 106 model['name'] = name
124 107
125 model = self.notebook_manager.create_notebook(model, path)
108 model = self.contents_manager.create_notebook(model, path)
126 109 self.set_status(201)
127 110 self._finish_model(model)
128 111
@@ -135,14 +118,14 b' class NotebookHandler(IPythonHandler):'
135 118 model = {}
136 119 if name:
137 120 model['name'] = name
138 model = self.notebook_manager.create_notebook(model, path=path)
121 model = self.contents_manager.create_notebook(model, path=path)
139 122 self.set_status(201)
140 123 self._finish_model(model)
141 124
142 def _save_notebook(self, model, path, name):
143 """Save an existing notebook."""
144 self.log.info(u"Saving notebook at %s/%s", path, name)
145 model = self.notebook_manager.save_notebook(model, name, path)
125 def _save(self, model, path, name):
126 """Save an existing file."""
127 self.log.info(u"Saving file at %s/%s", path, name)
128 model = self.contents_manager.save(model, name, path)
146 129 if model['path'] != path.strip('/') or model['name'] != name:
147 130 # a rename happened, set Location header
148 131 location = True
@@ -157,10 +140,10 b' class NotebookHandler(IPythonHandler):'
157 140
158 141 POST creates new notebooks. The server always decides on the notebook name.
159 142
160 POST /api/notebooks/path
143 POST /api/contents/path
161 144 New untitled notebook in path. If content specified, upload a
162 145 notebook, otherwise start empty.
163 POST /api/notebooks/path?copy=OtherNotebook.ipynb
146 POST /api/contents/path?copy=OtherNotebook.ipynb
164 147 New copy of OtherNotebook in path
165 148 """
166 149
@@ -174,25 +157,25 b' class NotebookHandler(IPythonHandler):'
174 157 if copy_from:
175 158 if model.get('content'):
176 159 raise web.HTTPError(400, "Can't upload and copy at the same time.")
177 self._copy_notebook(copy_from, path)
160 self._copy(copy_from, path)
178 161 else:
179 self._upload_notebook(model, path)
162 self._upload(model, path)
180 163 else:
181 164 self._create_empty_notebook(path)
182 165
183 166 @web.authenticated
184 167 @json_errors
185 168 def put(self, path='', name=None):
186 """Saves the notebook in the location specified by name and path.
169 """Saves the file in the location specified by name and path.
187 170
188 171 PUT is very similar to POST, but the requester specifies the name,
189 172 whereas with POST, the server picks the name.
190 173
191 PUT /api/notebooks/path/Name.ipynb
174 PUT /api/contents/path/Name.ipynb
192 175 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
193 176 in `content` key of JSON request body. If content is not specified,
194 177 create a new empty notebook.
195 PUT /api/notebooks/path/Name.ipynb?copy=OtherNotebook.ipynb
178 PUT /api/contents/path/Name.ipynb?copy=OtherNotebook.ipynb
196 179 Copy OtherNotebook to Name
197 180 """
198 181 if name is None:
@@ -204,34 +187,34 b' class NotebookHandler(IPythonHandler):'
204 187 if copy_from:
205 188 if model.get('content'):
206 189 raise web.HTTPError(400, "Can't upload and copy at the same time.")
207 self._copy_notebook(copy_from, path, name)
208 elif self.notebook_manager.notebook_exists(name, path):
209 self._save_notebook(model, path, name)
190 self._copy(copy_from, path, name)
191 elif self.contents_manager.file_exists(name, path):
192 self._save(model, path, name)
210 193 else:
211 self._upload_notebook(model, path, name)
194 self._upload(model, path, name)
212 195 else:
213 196 self._create_empty_notebook(path, name)
214 197
215 198 @web.authenticated
216 199 @json_errors
217 200 def delete(self, path='', name=None):
218 """delete the notebook in the given notebook path"""
219 nbm = self.notebook_manager
220 nbm.delete_notebook(name, path)
201 """delete a file in the given path"""
202 cm = self.contents_manager
203 cm.delete(name, path)
221 204 self.set_status(204)
222 205 self.finish()
223 206
224 207
225 class NotebookCheckpointsHandler(IPythonHandler):
208 class CheckpointsHandler(IPythonHandler):
226 209
227 210 SUPPORTED_METHODS = ('GET', 'POST')
228 211
229 212 @web.authenticated
230 213 @json_errors
231 214 def get(self, path='', name=None):
232 """get lists checkpoints for a notebook"""
233 nbm = self.notebook_manager
234 checkpoints = nbm.list_checkpoints(name, path)
215 """get lists checkpoints for a file"""
216 cm = self.contents_manager
217 checkpoints = cm.list_checkpoints(name, path)
235 218 data = json.dumps(checkpoints, default=date_default)
236 219 self.finish(data)
237 220
@@ -239,35 +222,35 b' class NotebookCheckpointsHandler(IPythonHandler):'
239 222 @json_errors
240 223 def post(self, path='', name=None):
241 224 """post creates a new checkpoint"""
242 nbm = self.notebook_manager
243 checkpoint = nbm.create_checkpoint(name, path)
225 cm = self.contents_manager
226 checkpoint = cm.create_checkpoint(name, path)
244 227 data = json.dumps(checkpoint, default=date_default)
245 location = url_path_join(self.base_url, 'api/notebooks',
228 location = url_path_join(self.base_url, 'api/contents',
246 229 path, name, 'checkpoints', checkpoint['id'])
247 230 self.set_header('Location', url_escape(location))
248 231 self.set_status(201)
249 232 self.finish(data)
250 233
251 234
252 class ModifyNotebookCheckpointsHandler(IPythonHandler):
235 class ModifyCheckpointsHandler(IPythonHandler):
253 236
254 237 SUPPORTED_METHODS = ('POST', 'DELETE')
255 238
256 239 @web.authenticated
257 240 @json_errors
258 241 def post(self, path, name, checkpoint_id):
259 """post restores a notebook from a checkpoint"""
260 nbm = self.notebook_manager
261 nbm.restore_checkpoint(checkpoint_id, name, path)
242 """post restores a file from a checkpoint"""
243 cm = self.contents_manager
244 cm.restore_checkpoint(checkpoint_id, name, path)
262 245 self.set_status(204)
263 246 self.finish()
264 247
265 248 @web.authenticated
266 249 @json_errors
267 250 def delete(self, path, name, checkpoint_id):
268 """delete clears a checkpoint for a given notebook"""
269 nbm = self.notebook_manager
270 nbm.delete_checkpoint(checkpoint_id, name, path)
251 """delete clears a checkpoint for a given file"""
252 cm = self.contents_manager
253 cm.delete_checkpoint(checkpoint_id, name, path)
271 254 self.set_status(204)
272 255 self.finish()
273 256
@@ -279,9 +262,9 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):'
279 262 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
280 263
281 264 default_handlers = [
282 (r"/api/notebooks%s/checkpoints" % notebook_path_regex, NotebookCheckpointsHandler),
283 (r"/api/notebooks%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
284 ModifyNotebookCheckpointsHandler),
285 (r"/api/notebooks%s" % notebook_path_regex, NotebookHandler),
286 (r"/api/notebooks%s" % path_regex, NotebookHandler),
265 (r"/api/contents%s/checkpoints" % notebook_path_regex, CheckpointsHandler),
266 (r"/api/contents%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
267 ModifyCheckpointsHandler),
268 (r"/api/contents%s" % notebook_path_regex, ContentsHandler),
269 (r"/api/contents%s" % path_regex, ContentsHandler),
287 270 ]
@@ -1,21 +1,7 b''
1 """A base class notebook manager.
1 """A base class for contents managers."""
2 2
3 Authors:
4
5 * Brian Granger
6 * Zach Sailer
7 """
8
9 #-----------------------------------------------------------------------------
10 # Copyright (C) 2011 The IPython Development Team
11 #
12 # Distributed under the terms of the BSD License. The full license is in
13 # the file COPYING, distributed as part of this software.
14 #-----------------------------------------------------------------------------
15
16 #-----------------------------------------------------------------------------
17 # Imports
18 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
19 5
20 6 from fnmatch import fnmatch
21 7 import itertools
@@ -25,13 +11,8 b' from IPython.config.configurable import LoggingConfigurable'
25 11 from IPython.nbformat import current, sign
26 12 from IPython.utils.traitlets import Instance, Unicode, List
27 13
28 #-----------------------------------------------------------------------------
29 # Classes
30 #-----------------------------------------------------------------------------
31 14
32 class NotebookManager(LoggingConfigurable):
33
34 filename_ext = Unicode(u'.ipynb')
15 class ContentsManager(LoggingConfigurable):
35 16
36 17 notary = Instance(sign.NotebookNotary)
37 18 def _notary_default(self):
@@ -41,7 +22,7 b' class NotebookManager(LoggingConfigurable):'
41 22 Glob patterns to hide in file and directory listings.
42 23 """)
43 24
44 # NotebookManager API part 1: methods that must be
25 # ContentsManager API part 1: methods that must be
45 26 # implemented in subclasses.
46 27
47 28 def path_exists(self, path):
@@ -68,7 +49,7 b' class NotebookManager(LoggingConfigurable):'
68 49 ----------
69 50 path : string
70 51 The path to check. This is an API path (`/` separated,
71 relative to base notebook-dir).
52 relative to root dir).
72 53
73 54 Returns
74 55 -------
@@ -78,7 +59,7 b' class NotebookManager(LoggingConfigurable):'
78 59 """
79 60 raise NotImplementedError
80 61
81 def notebook_exists(self, name, path=''):
62 def file_exists(self, name, path=''):
82 63 """Returns a True if the notebook exists. Else, returns False.
83 64
84 65 Parameters
@@ -114,12 +95,10 b' class NotebookManager(LoggingConfigurable):'
114 95 """
115 96 raise NotImplementedError('must be implemented in a subclass')
116 97
117 def list_notebooks(self, path=''):
118 """Return a list of notebook dicts without content.
119
120 This returns a list of dicts, each of the form::
98 def list_files(self, path=''):
99 """Return a list of contents dicts without content.
121 100
122 dict(notebook_id=notebook,name=name)
101 This returns a list of dicts
123 102
124 103 This list of dicts should be sorted by name::
125 104
@@ -127,19 +106,19 b' class NotebookManager(LoggingConfigurable):'
127 106 """
128 107 raise NotImplementedError('must be implemented in a subclass')
129 108
130 def get_notebook(self, name, path='', content=True):
109 def get_model(self, name, path='', content=True):
131 110 """Get the notebook model with or without content."""
132 111 raise NotImplementedError('must be implemented in a subclass')
133 112
134 def save_notebook(self, model, name, path=''):
113 def save(self, model, name, path=''):
135 114 """Save the notebook and return the model with no content."""
136 115 raise NotImplementedError('must be implemented in a subclass')
137 116
138 def update_notebook(self, model, name, path=''):
117 def update(self, model, name, path=''):
139 118 """Update the notebook and return the model with no content."""
140 119 raise NotImplementedError('must be implemented in a subclass')
141 120
142 def delete_notebook(self, name, path=''):
121 def delete(self, name, path=''):
143 122 """Delete notebook by name and path."""
144 123 raise NotImplementedError('must be implemented in a subclass')
145 124
@@ -165,34 +144,34 b' class NotebookManager(LoggingConfigurable):'
165 144 def info_string(self):
166 145 return "Serving notebooks"
167 146
168 # NotebookManager API part 2: methods that have useable default
147 # ContentsManager API part 2: methods that have useable default
169 148 # implementations, but can be overridden in subclasses.
170 149
171 150 def get_kernel_path(self, name, path='', model=None):
172 151 """ Return the path to start kernel in """
173 152 return path
174 153
175 def increment_filename(self, basename, path=''):
176 """Increment a notebook filename without the .ipynb to make it unique.
154 def increment_filename(self, filename, path=''):
155 """Increment a filename until it is unique.
177 156
178 157 Parameters
179 158 ----------
180 basename : unicode
181 The name of a notebook without the ``.ipynb`` file extension.
159 filename : unicode
160 The name of a file, including extension
182 161 path : unicode
183 162 The URL path of the notebooks directory
184 163
185 164 Returns
186 165 -------
187 166 name : unicode
188 A notebook name (with the .ipynb extension) that starts
189 with basename and does not refer to any existing notebook.
167 A filename that is unique, based on the input filename.
190 168 """
191 169 path = path.strip('/')
170 basename, ext = os.path.splitext(filename)
192 171 for i in itertools.count():
193 172 name = u'{basename}{i}{ext}'.format(basename=basename, i=i,
194 ext=self.filename_ext)
195 if not self.notebook_exists(name, path):
173 ext=ext)
174 if not self.file_exists(name, path):
196 175 break
197 176 return name
198 177
@@ -205,24 +184,25 b' class NotebookManager(LoggingConfigurable):'
205 184 metadata = current.new_metadata(name=u'')
206 185 model['content'] = current.new_notebook(metadata=metadata)
207 186 if 'name' not in model:
208 model['name'] = self.increment_filename('Untitled', path)
187 model['name'] = self.increment_filename('Untitled.ipynb', path)
209 188
210 189 model['path'] = path
211 model = self.save_notebook(model, model['name'], model['path'])
190 model = self.save(model, model['name'], model['path'])
212 191 return model
213 192
214 def copy_notebook(self, from_name, to_name=None, path=''):
215 """Copy an existing notebook and return its new model.
193 def copy(self, from_name, to_name=None, path=''):
194 """Copy an existing file and return its new model.
216 195
217 196 If to_name not specified, increment `from_name-Copy#.ipynb`.
218 197 """
219 198 path = path.strip('/')
220 model = self.get_notebook(from_name, path)
199 model = self.get(from_name, path)
221 200 if not to_name:
222 base = os.path.splitext(from_name)[0] + '-Copy'
223 to_name = self.increment_filename(base, path)
201 base, ext = os.path.splitext(from_name)
202 copy_name = u'{0}-Copy{1}'.format(base, ext)
203 to_name = self.increment_filename(copy_name, path)
224 204 model['name'] = to_name
225 model = self.save_notebook(model, to_name, path)
205 model = self.save(model, to_name, path)
226 206 return model
227 207
228 208 def log_info(self):
@@ -238,11 +218,11 b' class NotebookManager(LoggingConfigurable):'
238 218 path : string
239 219 The notebook's directory
240 220 """
241 model = self.get_notebook(name, path)
221 model = self.get(name, path)
242 222 nb = model['content']
243 223 self.log.warn("Trusting notebook %s/%s", path, name)
244 224 self.notary.mark_cells(nb, True)
245 self.save_notebook(model, name, path)
225 self.save(model, name, path)
246 226
247 227 def check_and_sign(self, nb, name, path=''):
248 228 """Check for trusted cells, and sign the notebook.
@@ -1,5 +1,5 b''
1 1 # coding: utf-8
2 """Test the notebooks webservice API."""
2 """Test the contents webservice API."""
3 3
4 4 import io
5 5 import json
@@ -30,14 +30,14 b' def dirs_only(nb_list):'
30 30 return [x for x in nb_list if x['type']=='directory']
31 31
32 32
33 class NBAPI(object):
34 """Wrapper for notebook API calls."""
33 class API(object):
34 """Wrapper for contents API calls."""
35 35 def __init__(self, base_url):
36 36 self.base_url = base_url
37 37
38 38 def _req(self, verb, path, body=None):
39 39 response = requests.request(verb,
40 url_path_join(self.base_url, 'api/notebooks', path),
40 url_path_join(self.base_url, 'api/contents', path),
41 41 data=body,
42 42 )
43 43 response.raise_for_status()
@@ -127,7 +127,7 b' class APITest(NotebookTestBase):'
127 127 nb = new_notebook(name=name)
128 128 write(nb, f, format='ipynb')
129 129
130 self.nb_api = NBAPI(self.base_url())
130 self.api = API(self.base_url())
131 131
132 132 def tearDown(self):
133 133 nbdir = self.notebook_dir.name
@@ -139,48 +139,48 b' class APITest(NotebookTestBase):'
139 139 os.unlink(pjoin(nbdir, 'inroot.ipynb'))
140 140
141 141 def test_list_notebooks(self):
142 nbs = notebooks_only(self.nb_api.list().json())
142 nbs = notebooks_only(self.api.list().json())
143 143 self.assertEqual(len(nbs), 1)
144 144 self.assertEqual(nbs[0]['name'], 'inroot.ipynb')
145 145
146 nbs = notebooks_only(self.nb_api.list('/Directory with spaces in/').json())
146 nbs = notebooks_only(self.api.list('/Directory with spaces in/').json())
147 147 self.assertEqual(len(nbs), 1)
148 148 self.assertEqual(nbs[0]['name'], 'inspace.ipynb')
149 149
150 nbs = notebooks_only(self.nb_api.list(u'/unicodé/').json())
150 nbs = notebooks_only(self.api.list(u'/unicodé/').json())
151 151 self.assertEqual(len(nbs), 1)
152 152 self.assertEqual(nbs[0]['name'], 'innonascii.ipynb')
153 153 self.assertEqual(nbs[0]['path'], u'unicodé')
154 154
155 nbs = notebooks_only(self.nb_api.list('/foo/bar/').json())
155 nbs = notebooks_only(self.api.list('/foo/bar/').json())
156 156 self.assertEqual(len(nbs), 1)
157 157 self.assertEqual(nbs[0]['name'], 'baz.ipynb')
158 158 self.assertEqual(nbs[0]['path'], 'foo/bar')
159 159
160 nbs = notebooks_only(self.nb_api.list('foo').json())
160 nbs = notebooks_only(self.api.list('foo').json())
161 161 self.assertEqual(len(nbs), 4)
162 162 nbnames = { normalize('NFC', n['name']) for n in nbs }
163 163 expected = [ u'a.ipynb', u'b.ipynb', u'name with spaces.ipynb', u'unicodé.ipynb']
164 164 expected = { normalize('NFC', name) for name in expected }
165 165 self.assertEqual(nbnames, expected)
166 166
167 nbs = notebooks_only(self.nb_api.list('ordering').json())
167 nbs = notebooks_only(self.api.list('ordering').json())
168 168 nbnames = [n['name'] for n in nbs]
169 169 expected = ['A.ipynb', 'b.ipynb', 'C.ipynb']
170 170 self.assertEqual(nbnames, expected)
171 171
172 172 def test_list_dirs(self):
173 dirs = dirs_only(self.nb_api.list().json())
173 dirs = dirs_only(self.api.list().json())
174 174 dir_names = {normalize('NFC', d['name']) for d in dirs}
175 175 self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs
176 176
177 177 def test_list_nonexistant_dir(self):
178 178 with assert_http_error(404):
179 self.nb_api.list('nonexistant')
179 self.api.list('nonexistant')
180 180
181 181 def test_get_contents(self):
182 182 for d, name in self.dirs_nbs:
183 nb = self.nb_api.read('%s.ipynb' % name, d+'/').json()
183 nb = self.api.read('%s.ipynb' % name, d+'/').json()
184 184 self.assertEqual(nb['name'], u'%s.ipynb' % name)
185 185 self.assertIn('content', nb)
186 186 self.assertIn('metadata', nb['content'])
@@ -188,12 +188,12 b' class APITest(NotebookTestBase):'
188 188
189 189 # Name that doesn't exist - should be a 404
190 190 with assert_http_error(404):
191 self.nb_api.read('q.ipynb', 'foo')
191 self.api.read('q.ipynb', 'foo')
192 192
193 193 def _check_nb_created(self, resp, name, path):
194 194 self.assertEqual(resp.status_code, 201)
195 195 location_header = py3compat.str_to_unicode(resp.headers['Location'])
196 self.assertEqual(location_header, url_escape(url_path_join(u'/api/notebooks', path, name)))
196 self.assertEqual(location_header, url_escape(url_path_join(u'/api/contents', path, name)))
197 197 self.assertEqual(resp.json()['name'], name)
198 198 assert os.path.isfile(pjoin(
199 199 self.notebook_dir.name,
@@ -202,28 +202,28 b' class APITest(NotebookTestBase):'
202 202 ))
203 203
204 204 def test_create_untitled(self):
205 resp = self.nb_api.create_untitled(path=u'å b')
205 resp = self.api.create_untitled(path=u'å b')
206 206 self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
207 207
208 208 # Second time
209 resp = self.nb_api.create_untitled(path=u'å b')
209 resp = self.api.create_untitled(path=u'å b')
210 210 self._check_nb_created(resp, 'Untitled1.ipynb', u'å b')
211 211
212 212 # And two directories down
213 resp = self.nb_api.create_untitled(path='foo/bar')
213 resp = self.api.create_untitled(path='foo/bar')
214 214 self._check_nb_created(resp, 'Untitled0.ipynb', 'foo/bar')
215 215
216 216 def test_upload_untitled(self):
217 217 nb = new_notebook(name='Upload test')
218 218 nbmodel = {'content': nb}
219 resp = self.nb_api.upload_untitled(path=u'å b',
219 resp = self.api.upload_untitled(path=u'å b',
220 220 body=json.dumps(nbmodel))
221 221 self._check_nb_created(resp, 'Untitled0.ipynb', u'å b')
222 222
223 223 def test_upload(self):
224 224 nb = new_notebook(name=u'ignored')
225 225 nbmodel = {'content': nb}
226 resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
226 resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
227 227 body=json.dumps(nbmodel))
228 228 self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
229 229
@@ -233,48 +233,48 b' class APITest(NotebookTestBase):'
233 233 nb.worksheets.append(ws)
234 234 ws.cells.append(v2.new_code_cell(input='print("hi")'))
235 235 nbmodel = {'content': nb}
236 resp = self.nb_api.upload(u'Upload tést.ipynb', path=u'å b',
236 resp = self.api.upload(u'Upload tést.ipynb', path=u'å b',
237 237 body=json.dumps(nbmodel))
238 238 self._check_nb_created(resp, u'Upload tést.ipynb', u'å b')
239 resp = self.nb_api.read(u'Upload tést.ipynb', u'å b')
239 resp = self.api.read(u'Upload tést.ipynb', u'å b')
240 240 data = resp.json()
241 241 self.assertEqual(data['content']['nbformat'], current.nbformat)
242 242 self.assertEqual(data['content']['orig_nbformat'], 2)
243 243
244 244 def test_copy_untitled(self):
245 resp = self.nb_api.copy_untitled(u'ç d.ipynb', path=u'å b')
245 resp = self.api.copy_untitled(u'ç d.ipynb', path=u'å b')
246 246 self._check_nb_created(resp, u'ç d-Copy0.ipynb', u'å b')
247 247
248 248 def test_copy(self):
249 resp = self.nb_api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
249 resp = self.api.copy(u'ç d.ipynb', u'cøpy.ipynb', path=u'å b')
250 250 self._check_nb_created(resp, u'cøpy.ipynb', u'å b')
251 251
252 252 def test_delete(self):
253 253 for d, name in self.dirs_nbs:
254 resp = self.nb_api.delete('%s.ipynb' % name, d)
254 resp = self.api.delete('%s.ipynb' % name, d)
255 255 self.assertEqual(resp.status_code, 204)
256 256
257 257 for d in self.dirs + ['/']:
258 nbs = notebooks_only(self.nb_api.list(d).json())
258 nbs = notebooks_only(self.api.list(d).json())
259 259 self.assertEqual(len(nbs), 0)
260 260
261 261 def test_rename(self):
262 resp = self.nb_api.rename('a.ipynb', 'foo', 'z.ipynb')
262 resp = self.api.rename('a.ipynb', 'foo', 'z.ipynb')
263 263 self.assertEqual(resp.headers['Location'].split('/')[-1], 'z.ipynb')
264 264 self.assertEqual(resp.json()['name'], 'z.ipynb')
265 265 assert os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'z.ipynb'))
266 266
267 nbs = notebooks_only(self.nb_api.list('foo').json())
267 nbs = notebooks_only(self.api.list('foo').json())
268 268 nbnames = set(n['name'] for n in nbs)
269 269 self.assertIn('z.ipynb', nbnames)
270 270 self.assertNotIn('a.ipynb', nbnames)
271 271
272 272 def test_rename_existing(self):
273 273 with assert_http_error(409):
274 self.nb_api.rename('a.ipynb', 'foo', 'b.ipynb')
274 self.api.rename('a.ipynb', 'foo', 'b.ipynb')
275 275
276 276 def test_save(self):
277 resp = self.nb_api.read('a.ipynb', 'foo')
277 resp = self.api.read('a.ipynb', 'foo')
278 278 nbcontent = json.loads(resp.text)['content']
279 279 nb = to_notebook_json(nbcontent)
280 280 ws = new_worksheet()
@@ -282,32 +282,32 b' class APITest(NotebookTestBase):'
282 282 ws.cells.append(new_heading_cell(u'Created by test ³'))
283 283
284 284 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
285 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
285 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
286 286
287 287 nbfile = pjoin(self.notebook_dir.name, 'foo', 'a.ipynb')
288 288 with io.open(nbfile, 'r', encoding='utf-8') as f:
289 289 newnb = read(f, format='ipynb')
290 290 self.assertEqual(newnb.worksheets[0].cells[0].source,
291 291 u'Created by test ³')
292 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
292 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
293 293 newnb = to_notebook_json(nbcontent)
294 294 self.assertEqual(newnb.worksheets[0].cells[0].source,
295 295 u'Created by test ³')
296 296
297 297 # Save and rename
298 298 nbmodel= {'name': 'a2.ipynb', 'path':'foo/bar', 'content': nb}
299 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
299 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
300 300 saved = resp.json()
301 301 self.assertEqual(saved['name'], 'a2.ipynb')
302 302 self.assertEqual(saved['path'], 'foo/bar')
303 303 assert os.path.isfile(pjoin(self.notebook_dir.name,'foo','bar','a2.ipynb'))
304 304 assert not os.path.isfile(pjoin(self.notebook_dir.name, 'foo', 'a.ipynb'))
305 305 with assert_http_error(404):
306 self.nb_api.read('a.ipynb', 'foo')
306 self.api.read('a.ipynb', 'foo')
307 307
308 308 def test_checkpoints(self):
309 resp = self.nb_api.read('a.ipynb', 'foo')
310 r = self.nb_api.new_checkpoint('a.ipynb', 'foo')
309 resp = self.api.read('a.ipynb', 'foo')
310 r = self.api.new_checkpoint('a.ipynb', 'foo')
311 311 self.assertEqual(r.status_code, 201)
312 312 cp1 = r.json()
313 313 self.assertEqual(set(cp1), {'id', 'last_modified'})
@@ -322,25 +322,25 b' class APITest(NotebookTestBase):'
322 322 ws.cells.append(hcell)
323 323 # Save
324 324 nbmodel= {'name': 'a.ipynb', 'path':'foo', 'content': nb}
325 resp = self.nb_api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
325 resp = self.api.save('a.ipynb', path='foo', body=json.dumps(nbmodel))
326 326
327 327 # List checkpoints
328 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
328 cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
329 329 self.assertEqual(cps, [cp1])
330 330
331 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
331 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
332 332 nb = to_notebook_json(nbcontent)
333 333 self.assertEqual(nb.worksheets[0].cells[0].source, 'Created by test')
334 334
335 335 # Restore cp1
336 r = self.nb_api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
336 r = self.api.restore_checkpoint('a.ipynb', 'foo', cp1['id'])
337 337 self.assertEqual(r.status_code, 204)
338 nbcontent = self.nb_api.read('a.ipynb', 'foo').json()['content']
338 nbcontent = self.api.read('a.ipynb', 'foo').json()['content']
339 339 nb = to_notebook_json(nbcontent)
340 340 self.assertEqual(nb.worksheets, [])
341 341
342 342 # Delete cp1
343 r = self.nb_api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
343 r = self.api.delete_checkpoint('a.ipynb', 'foo', cp1['id'])
344 344 self.assertEqual(r.status_code, 204)
345 cps = self.nb_api.get_checkpoints('a.ipynb', 'foo').json()
345 cps = self.api.get_checkpoints('a.ipynb', 'foo').json()
346 346 self.assertEqual(cps, [])
@@ -15,59 +15,59 b' from IPython.utils.tempdir import TemporaryDirectory'
15 15 from IPython.utils.traitlets import TraitError
16 16 from IPython.html.utils import url_path_join
17 17
18 from ..filenbmanager import FileNotebookManager
19 from ..nbmanager import NotebookManager
18 from ..filemanager import FileContentsManager
19 from ..manager import ContentsManager
20 20
21 21
22 class TestFileNotebookManager(TestCase):
22 class TestFileContentsManager(TestCase):
23 23
24 def test_nb_dir(self):
24 def test_root_dir(self):
25 25 with TemporaryDirectory() as td:
26 fm = FileNotebookManager(notebook_dir=td)
27 self.assertEqual(fm.notebook_dir, td)
26 fm = FileContentsManager(root_dir=td)
27 self.assertEqual(fm.root_dir, td)
28 28
29 def test_missing_nb_dir(self):
29 def test_missing_root_dir(self):
30 30 with TemporaryDirectory() as td:
31 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
32 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir)
31 root = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
32 self.assertRaises(TraitError, FileContentsManager, root_dir=root)
33 33
34 def test_invalid_nb_dir(self):
34 def test_invalid_root_dir(self):
35 35 with NamedTemporaryFile() as tf:
36 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name)
36 self.assertRaises(TraitError, FileContentsManager, root_dir=tf.name)
37 37
38 38 def test_get_os_path(self):
39 39 # full filesystem path should be returned with correct operating system
40 40 # separators.
41 41 with TemporaryDirectory() as td:
42 nbdir = td
43 fm = FileNotebookManager(notebook_dir=nbdir)
42 root = td
43 fm = FileContentsManager(root_dir=root)
44 44 path = fm._get_os_path('test.ipynb', '/path/to/notebook/')
45 45 rel_path_list = '/path/to/notebook/test.ipynb'.split('/')
46 fs_path = os.path.join(fm.notebook_dir, *rel_path_list)
46 fs_path = os.path.join(fm.root_dir, *rel_path_list)
47 47 self.assertEqual(path, fs_path)
48 48
49 fm = FileNotebookManager(notebook_dir=nbdir)
49 fm = FileContentsManager(root_dir=root)
50 50 path = fm._get_os_path('test.ipynb')
51 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
51 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
52 52 self.assertEqual(path, fs_path)
53 53
54 fm = FileNotebookManager(notebook_dir=nbdir)
54 fm = FileContentsManager(root_dir=root)
55 55 path = fm._get_os_path('test.ipynb', '////')
56 fs_path = os.path.join(fm.notebook_dir, 'test.ipynb')
56 fs_path = os.path.join(fm.root_dir, 'test.ipynb')
57 57 self.assertEqual(path, fs_path)
58 58
59 59 def test_checkpoint_subdir(self):
60 60 subd = u'sub ∂ir'
61 61 cp_name = 'test-cp.ipynb'
62 62 with TemporaryDirectory() as td:
63 nbdir = td
63 root = td
64 64 os.mkdir(os.path.join(td, subd))
65 fm = FileNotebookManager(notebook_dir=nbdir)
65 fm = FileContentsManager(root_dir=root)
66 66 cp_dir = fm.get_checkpoint_path('cp', 'test.ipynb', '/')
67 67 cp_subdir = fm.get_checkpoint_path('cp', 'test.ipynb', '/%s/' % subd)
68 68 self.assertNotEqual(cp_dir, cp_subdir)
69 self.assertEqual(cp_dir, os.path.join(nbdir, fm.checkpoint_dir, cp_name))
70 self.assertEqual(cp_subdir, os.path.join(nbdir, subd, fm.checkpoint_dir, cp_name))
69 self.assertEqual(cp_dir, os.path.join(root, fm.checkpoint_dir, cp_name))
70 self.assertEqual(cp_subdir, os.path.join(root, subd, fm.checkpoint_dir, cp_name))
71 71
72 72
73 73 class TestNotebookManager(TestCase):
@@ -75,8 +75,8 b' class TestNotebookManager(TestCase):'
75 75 def setUp(self):
76 76 self._temp_dir = TemporaryDirectory()
77 77 self.td = self._temp_dir.name
78 self.notebook_manager = FileNotebookManager(
79 notebook_dir=self.td,
78 self.contents_manager = FileContentsManager(
79 root_dir=self.td,
80 80 log=logging.getLogger()
81 81 )
82 82
@@ -100,22 +100,22 b' class TestNotebookManager(TestCase):'
100 100 nb.worksheets[0].cells.append(cell)
101 101
102 102 def new_notebook(self):
103 nbm = self.notebook_manager
104 model = nbm.create_notebook()
103 cm = self.contents_manager
104 model = cm.create_notebook()
105 105 name = model['name']
106 106 path = model['path']
107 107
108 full_model = nbm.get_notebook(name, path)
108 full_model = cm.get(name, path)
109 109 nb = full_model['content']
110 110 self.add_code_cell(nb)
111 111
112 nbm.save_notebook(full_model, name, path)
112 cm.save(full_model, name, path)
113 113 return nb, name, path
114 114
115 115 def test_create_notebook(self):
116 nm = self.notebook_manager
116 cm = self.contents_manager
117 117 # Test in root directory
118 model = nm.create_notebook()
118 model = cm.create_notebook()
119 119 assert isinstance(model, dict)
120 120 self.assertIn('name', model)
121 121 self.assertIn('path', model)
@@ -124,23 +124,23 b' class TestNotebookManager(TestCase):'
124 124
125 125 # Test in sub-directory
126 126 sub_dir = '/foo/'
127 self.make_dir(nm.notebook_dir, 'foo')
128 model = nm.create_notebook(None, sub_dir)
127 self.make_dir(cm.root_dir, 'foo')
128 model = cm.create_notebook(None, sub_dir)
129 129 assert isinstance(model, dict)
130 130 self.assertIn('name', model)
131 131 self.assertIn('path', model)
132 132 self.assertEqual(model['name'], 'Untitled0.ipynb')
133 133 self.assertEqual(model['path'], sub_dir.strip('/'))
134 134
135 def test_get_notebook(self):
136 nm = self.notebook_manager
135 def test_get(self):
136 cm = self.contents_manager
137 137 # Create a notebook
138 model = nm.create_notebook()
138 model = cm.create_notebook()
139 139 name = model['name']
140 140 path = model['path']
141 141
142 142 # Check that we 'get' on the notebook we just created
143 model2 = nm.get_notebook(name, path)
143 model2 = cm.get(name, path)
144 144 assert isinstance(model2, dict)
145 145 self.assertIn('name', model2)
146 146 self.assertIn('path', model2)
@@ -149,9 +149,9 b' class TestNotebookManager(TestCase):'
149 149
150 150 # Test in sub-directory
151 151 sub_dir = '/foo/'
152 self.make_dir(nm.notebook_dir, 'foo')
153 model = nm.create_notebook(None, sub_dir)
154 model2 = nm.get_notebook(name, sub_dir)
152 self.make_dir(cm.root_dir, 'foo')
153 model = cm.create_notebook(None, sub_dir)
154 model2 = cm.get(name, sub_dir)
155 155 assert isinstance(model2, dict)
156 156 self.assertIn('name', model2)
157 157 self.assertIn('path', model2)
@@ -159,35 +159,35 b' class TestNotebookManager(TestCase):'
159 159 self.assertEqual(model2['name'], 'Untitled0.ipynb')
160 160 self.assertEqual(model2['path'], sub_dir.strip('/'))
161 161
162 def test_update_notebook(self):
163 nm = self.notebook_manager
162 def test_update(self):
163 cm = self.contents_manager
164 164 # Create a notebook
165 model = nm.create_notebook()
165 model = cm.create_notebook()
166 166 name = model['name']
167 167 path = model['path']
168 168
169 169 # Change the name in the model for rename
170 170 model['name'] = 'test.ipynb'
171 model = nm.update_notebook(model, name, path)
171 model = cm.update(model, name, path)
172 172 assert isinstance(model, dict)
173 173 self.assertIn('name', model)
174 174 self.assertIn('path', model)
175 175 self.assertEqual(model['name'], 'test.ipynb')
176 176
177 177 # Make sure the old name is gone
178 self.assertRaises(HTTPError, nm.get_notebook, name, path)
178 self.assertRaises(HTTPError, cm.get, name, path)
179 179
180 180 # Test in sub-directory
181 181 # Create a directory and notebook in that directory
182 182 sub_dir = '/foo/'
183 self.make_dir(nm.notebook_dir, 'foo')
184 model = nm.create_notebook(None, sub_dir)
183 self.make_dir(cm.root_dir, 'foo')
184 model = cm.create_notebook(None, sub_dir)
185 185 name = model['name']
186 186 path = model['path']
187 187
188 188 # Change the name in the model for rename
189 189 model['name'] = 'test_in_sub.ipynb'
190 model = nm.update_notebook(model, name, path)
190 model = cm.update(model, name, path)
191 191 assert isinstance(model, dict)
192 192 self.assertIn('name', model)
193 193 self.assertIn('path', model)
@@ -195,20 +195,20 b' class TestNotebookManager(TestCase):'
195 195 self.assertEqual(model['path'], sub_dir.strip('/'))
196 196
197 197 # Make sure the old name is gone
198 self.assertRaises(HTTPError, nm.get_notebook, name, path)
198 self.assertRaises(HTTPError, cm.get, name, path)
199 199
200 def test_save_notebook(self):
201 nm = self.notebook_manager
200 def test_save(self):
201 cm = self.contents_manager
202 202 # Create a notebook
203 model = nm.create_notebook()
203 model = cm.create_notebook()
204 204 name = model['name']
205 205 path = model['path']
206 206
207 207 # Get the model with 'content'
208 full_model = nm.get_notebook(name, path)
208 full_model = cm.get(name, path)
209 209
210 210 # Save the notebook
211 model = nm.save_notebook(full_model, name, path)
211 model = cm.save(full_model, name, path)
212 212 assert isinstance(model, dict)
213 213 self.assertIn('name', model)
214 214 self.assertIn('path', model)
@@ -218,103 +218,84 b' class TestNotebookManager(TestCase):'
218 218 # Test in sub-directory
219 219 # Create a directory and notebook in that directory
220 220 sub_dir = '/foo/'
221 self.make_dir(nm.notebook_dir, 'foo')
222 model = nm.create_notebook(None, sub_dir)
221 self.make_dir(cm.root_dir, 'foo')
222 model = cm.create_notebook(None, sub_dir)
223 223 name = model['name']
224 224 path = model['path']
225 model = nm.get_notebook(name, path)
225 model = cm.get(name, path)
226 226
227 227 # Change the name in the model for rename
228 model = nm.save_notebook(model, name, path)
228 model = cm.save(model, name, path)
229 229 assert isinstance(model, dict)
230 230 self.assertIn('name', model)
231 231 self.assertIn('path', model)
232 232 self.assertEqual(model['name'], 'Untitled0.ipynb')
233 233 self.assertEqual(model['path'], sub_dir.strip('/'))
234 234
235 def test_save_notebook_with_script(self):
236 nm = self.notebook_manager
237 # Create a notebook
238 model = nm.create_notebook()
239 nm.save_script = True
240 model = nm.create_notebook()
241 name = model['name']
242 path = model['path']
243
244 # Get the model with 'content'
245 full_model = nm.get_notebook(name, path)
246
247 # Save the notebook
248 model = nm.save_notebook(full_model, name, path)
249
250 # Check that the script was created
251 py_path = os.path.join(nm.notebook_dir, os.path.splitext(name)[0]+'.py')
252 assert os.path.exists(py_path), py_path
253
254 def test_delete_notebook(self):
255 nm = self.notebook_manager
235 def test_delete(self):
236 cm = self.contents_manager
256 237 # Create a notebook
257 238 nb, name, path = self.new_notebook()
258 239
259 240 # Delete the notebook
260 nm.delete_notebook(name, path)
241 cm.delete(name, path)
261 242
262 243 # Check that a 'get' on the deleted notebook raises and error
263 self.assertRaises(HTTPError, nm.get_notebook, name, path)
244 self.assertRaises(HTTPError, cm.get, name, path)
264 245
265 def test_copy_notebook(self):
266 nm = self.notebook_manager
246 def test_copy(self):
247 cm = self.contents_manager
267 248 path = u'å b'
268 249 name = u'nb √.ipynb'
269 os.mkdir(os.path.join(nm.notebook_dir, path))
270 orig = nm.create_notebook({'name' : name}, path=path)
250 os.mkdir(os.path.join(cm.root_dir, path))
251 orig = cm.create_notebook({'name' : name}, path=path)
271 252
272 253 # copy with unspecified name
273 copy = nm.copy_notebook(name, path=path)
254 copy = cm.copy(name, path=path)
274 255 self.assertEqual(copy['name'], orig['name'].replace('.ipynb', '-Copy0.ipynb'))
275 256
276 257 # copy with specified name
277 copy2 = nm.copy_notebook(name, u'copy 2.ipynb', path=path)
258 copy2 = cm.copy(name, u'copy 2.ipynb', path=path)
278 259 self.assertEqual(copy2['name'], u'copy 2.ipynb')
279 260
280 261 def test_trust_notebook(self):
281 nbm = self.notebook_manager
262 cm = self.contents_manager
282 263 nb, name, path = self.new_notebook()
283 264
284 untrusted = nbm.get_notebook(name, path)['content']
285 assert not nbm.notary.check_cells(untrusted)
265 untrusted = cm.get(name, path)['content']
266 assert not cm.notary.check_cells(untrusted)
286 267
287 268 # print(untrusted)
288 nbm.trust_notebook(name, path)
289 trusted = nbm.get_notebook(name, path)['content']
269 cm.trust_notebook(name, path)
270 trusted = cm.get(name, path)['content']
290 271 # print(trusted)
291 assert nbm.notary.check_cells(trusted)
272 assert cm.notary.check_cells(trusted)
292 273
293 274 def test_mark_trusted_cells(self):
294 nbm = self.notebook_manager
275 cm = self.contents_manager
295 276 nb, name, path = self.new_notebook()
296 277
297 nbm.mark_trusted_cells(nb, name, path)
278 cm.mark_trusted_cells(nb, name, path)
298 279 for cell in nb.worksheets[0].cells:
299 280 if cell.cell_type == 'code':
300 281 assert not cell.trusted
301 282
302 nbm.trust_notebook(name, path)
303 nb = nbm.get_notebook(name, path)['content']
283 cm.trust_notebook(name, path)
284 nb = cm.get(name, path)['content']
304 285 for cell in nb.worksheets[0].cells:
305 286 if cell.cell_type == 'code':
306 287 assert cell.trusted
307 288
308 289 def test_check_and_sign(self):
309 nbm = self.notebook_manager
290 cm = self.contents_manager
310 291 nb, name, path = self.new_notebook()
311 292
312 nbm.mark_trusted_cells(nb, name, path)
313 nbm.check_and_sign(nb, name, path)
314 assert not nbm.notary.check_signature(nb)
293 cm.mark_trusted_cells(nb, name, path)
294 cm.check_and_sign(nb, name, path)
295 assert not cm.notary.check_signature(nb)
315 296
316 nbm.trust_notebook(name, path)
317 nb = nbm.get_notebook(name, path)['content']
318 nbm.mark_trusted_cells(nb, name, path)
319 nbm.check_and_sign(nb, name, path)
320 assert nbm.notary.check_signature(nb)
297 cm.trust_notebook(name, path)
298 nb = cm.get(name, path)['content']
299 cm.mark_trusted_cells(nb, name, path)
300 cm.check_and_sign(nb, name, path)
301 assert cm.notary.check_signature(nb)
@@ -1,20 +1,7 b''
1 """Tornado handlers for the sessions web service.
1 """Tornado handlers for the sessions web service."""
2 2
3 Authors:
4
5 * Zach Sailer
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2013 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
18 5
19 6 import json
20 7
@@ -24,10 +11,6 b' from ...base.handlers import IPythonHandler, json_errors'
24 11 from IPython.utils.jsonutil import date_default
25 12 from IPython.html.utils import url_path_join, url_escape
26 13
27 #-----------------------------------------------------------------------------
28 # Session web service handlers
29 #-----------------------------------------------------------------------------
30
31 14
32 15 class SessionRootHandler(IPythonHandler):
33 16
@@ -45,6 +28,8 b' class SessionRootHandler(IPythonHandler):'
45 28 # Creates a new session
46 29 #(unless a session already exists for the named nb)
47 30 sm = self.session_manager
31 cm = self.contents_manager
32 km = self.kernel_manager
48 33
49 34 model = self.get_json_body()
50 35 if model is None:
@@ -32,7 +32,7 b' from IPython.utils.traitlets import Instance'
32 32 class SessionManager(LoggingConfigurable):
33 33
34 34 kernel_manager = Instance('IPython.html.services.kernels.kernelmanager.MappingKernelManager')
35 notebook_manager = Instance('IPython.html.services.notebooks.nbmanager.NotebookManager', args=())
35 contents_manager = Instance('IPython.html.services.contents.manager.ContentsManager', args=())
36 36
37 37 # Session database initialized below
38 38 _cursor = None
@@ -77,7 +77,7 b' class SessionManager(LoggingConfigurable):'
77 77 """Creates a session and returns its model"""
78 78 session_id = self.new_session_id()
79 79 # allow nbm to specify kernels cwd
80 kernel_path = self.notebook_manager.get_kernel_path(name=name, path=path)
80 kernel_path = self.contents_manager.get_kernel_path(name=name, path=path)
81 81 kernel_id = self.kernel_manager.start_kernel(path=kernel_path,
82 82 kernel_name=kernel_name)
83 83 return self.save_session(session_id, name=name, path=path,
@@ -1908,7 +1908,7 b' define(['
1908 1908 this.events.trigger('notebook_saving.Notebook');
1909 1909 var url = utils.url_join_encode(
1910 1910 this.base_url,
1911 'api/notebooks',
1911 'api/contents',
1912 1912 this.notebook_path,
1913 1913 this.notebook_name
1914 1914 );
@@ -2041,7 +2041,7 b' define(['
2041 2041 };
2042 2042 var url = utils.url_join_encode(
2043 2043 base_url,
2044 'api/notebooks',
2044 'api/contents',
2045 2045 path
2046 2046 );
2047 2047 $.ajax(url,settings);
@@ -2070,7 +2070,7 b' define(['
2070 2070 };
2071 2071 var url = utils.url_join_encode(
2072 2072 base_url,
2073 'api/notebooks',
2073 'api/contents',
2074 2074 path
2075 2075 );
2076 2076 $.ajax(url,settings);
@@ -2095,7 +2095,7 b' define(['
2095 2095 this.events.trigger('rename_notebook.Notebook', data);
2096 2096 var url = utils.url_join_encode(
2097 2097 this.base_url,
2098 'api/notebooks',
2098 'api/contents',
2099 2099 this.notebook_path,
2100 2100 this.notebook_name
2101 2101 );
@@ -2113,7 +2113,7 b' define(['
2113 2113 };
2114 2114 var url = utils.url_join_encode(
2115 2115 this.base_url,
2116 'api/notebooks',
2116 'api/contents',
2117 2117 this.notebook_path,
2118 2118 this.notebook_name
2119 2119 );
@@ -2182,7 +2182,7 b' define(['
2182 2182 this.events.trigger('notebook_loading.Notebook');
2183 2183 var url = utils.url_join_encode(
2184 2184 this.base_url,
2185 'api/notebooks',
2185 'api/contents',
2186 2186 this.notebook_path,
2187 2187 this.notebook_name
2188 2188 );
@@ -2345,7 +2345,7 b' define(['
2345 2345 Notebook.prototype.list_checkpoints = function () {
2346 2346 var url = utils.url_join_encode(
2347 2347 this.base_url,
2348 'api/notebooks',
2348 'api/contents',
2349 2349 this.notebook_path,
2350 2350 this.notebook_name,
2351 2351 'checkpoints'
@@ -2396,7 +2396,7 b' define(['
2396 2396 Notebook.prototype.create_checkpoint = function () {
2397 2397 var url = utils.url_join_encode(
2398 2398 this.base_url,
2399 'api/notebooks',
2399 'api/contents',
2400 2400 this.notebook_path,
2401 2401 this.notebook_name,
2402 2402 'checkpoints'
@@ -2485,7 +2485,7 b' define(['
2485 2485 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2486 2486 var url = utils.url_join_encode(
2487 2487 this.base_url,
2488 'api/notebooks',
2488 'api/contents',
2489 2489 this.notebook_path,
2490 2490 this.notebook_name,
2491 2491 'checkpoints',
@@ -2533,7 +2533,7 b' define(['
2533 2533 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2534 2534 var url = utils.url_join_encode(
2535 2535 this.base_url,
2536 'api/notebooks',
2536 'api/contents',
2537 2537 this.notebook_path,
2538 2538 this.notebook_name,
2539 2539 'checkpoints',
@@ -148,7 +148,7 b' define(['
148 148 var url = utils.url_join_encode(
149 149 this.base_url,
150 150 'api',
151 'notebooks',
151 'contents',
152 152 this.notebook_path
153 153 );
154 154 $.ajax(url, settings);
@@ -328,7 +328,7 b' define(['
328 328 };
329 329 var url = utils.url_join_encode(
330 330 notebooklist.base_url,
331 'api/notebooks',
331 'api/contents',
332 332 notebooklist.notebook_path,
333 333 nbname
334 334 );
@@ -375,7 +375,7 b' define(['
375 375
376 376 var url = utils.url_join_encode(
377 377 that.base_url,
378 'api/notebooks',
378 'api/contents',
379 379 that.notebook_path,
380 380 nbname
381 381 );
@@ -419,7 +419,7 b' define(['
419 419 };
420 420 var url = utils.url_join_encode(
421 421 base_url,
422 'api/notebooks',
422 'api/contents',
423 423 path
424 424 );
425 425 $.ajax(url, settings);
@@ -33,7 +33,7 b' class NotebookTestBase(TestCase):'
33 33 @classmethod
34 34 def wait_until_alive(cls):
35 35 """Wait for the server to be alive"""
36 url = 'http://localhost:%i/api/notebooks' % cls.port
36 url = 'http://localhost:%i/api/contents' % cls.port
37 37 for _ in range(int(MAX_WAITTIME/POLL_INTERVAL)):
38 38 try:
39 39 requests.get(url)
@@ -51,7 +51,7 b' class TreeHandler(IPythonHandler):'
51 51 @web.authenticated
52 52 def get(self, path='', name=None):
53 53 path = path.strip('/')
54 nbm = self.notebook_manager
54 cm = self.contents_manager
55 55 if name is not None:
56 56 # is a notebook, redirect to notebook handler
57 57 url = url_escape(url_path_join(
@@ -60,10 +60,10 b' class TreeHandler(IPythonHandler):'
60 60 self.log.debug("Redirecting %s to %s", self.request.path, url)
61 61 self.redirect(url)
62 62 else:
63 if not nbm.path_exists(path=path):
63 if not cm.path_exists(path=path):
64 64 # Directory is hidden or does not exist.
65 65 raise web.HTTPError(404)
66 elif nbm.is_hidden(path):
66 elif cm.is_hidden(path):
67 67 self.log.info("Refusing to serve hidden directory, via 404 Error")
68 68 raise web.HTTPError(404)
69 69 breadcrumbs = self.generate_breadcrumbs(path)
General Comments 0
You need to be logged in to leave comments. Login now