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