##// END OF EJS Templates
teach contents service about non-notebook files
MinRK -
Show More
@@ -416,6 +416,8 b' class TrailingSlashHandler(web.RequestHandler):'
416 path_regex = r"(?P<path>(?:/.*)*)"
416 path_regex = r"(?P<path>(?:/.*)*)"
417 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
417 notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
418 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
418 notebook_path_regex = "%s/%s" % (path_regex, notebook_name_regex)
419 file_name_regex = r"(?P<name>[^/]+)"
420 file_path_regex = "%s/%s" % (path_regex, file_name_regex)
419
421
420 #-----------------------------------------------------------------------------
422 #-----------------------------------------------------------------------------
421 # URL to handler mappings
423 # URL to handler mappings
@@ -1,3 +1,8 b''
1 """Tornado handlers for nbconvert."""
2
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
5
1 import io
6 import io
2 import os
7 import os
3 import zipfile
8 import zipfile
@@ -73,7 +78,7 b' class NbconvertFileHandler(IPythonHandler):'
73 exporter = get_exporter(format, config=self.config, log=self.log)
78 exporter = get_exporter(format, config=self.config, log=self.log)
74
79
75 path = path.strip('/')
80 path = path.strip('/')
76 model = self.contents_manager.get(name=name, path=path)
81 model = self.contents_manager.get_model(name=name, path=path)
77
82
78 self.set_header('Last-Modified', model['last_modified'])
83 self.set_header('Last-Modified', model['last_modified'])
79
84
@@ -3,6 +3,7 b''
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import base64
6 import io
7 import io
7 import os
8 import os
8 import glob
9 import glob
@@ -56,17 +57,29 b' class FileContentsManager(ContentsManager):'
56 except OSError as e:
57 except OSError as e:
57 self.log.debug("copystat on %s failed", dest, exc_info=True)
58 self.log.debug("copystat on %s failed", dest, exc_info=True)
58
59
59 def get_names(self, path=''):
60 def _get_os_path(self, name=None, path=''):
60 """List all filenames in the path (relative to root_dir)."""
61 """Given a filename and a URL path, return its file system
61 path = path.strip('/')
62 path.
62 if not os.path.isdir(self._get_os_path(path=path)):
63
63 raise web.HTTPError(404, 'Directory not found: ' + path)
64 Parameters
64 names = glob.glob(self._get_os_path('*', path))
65 ----------
65 names = [ os.path.basename(name) for name in names if os.path.isfile(name)]
66 name : string
66 return names
67 A filename
68 path : string
69 The relative URL path (with '/' as separator) to the named
70 file.
71
72 Returns
73 -------
74 path : string
75 API path to be evaluated relative to root_dir.
76 """
77 if name is not None:
78 path = path + '/' + name
79 return to_os_path(path, self.root_dir)
67
80
68 def path_exists(self, path):
81 def path_exists(self, path):
69 """Does the API-style path (directory) actually exist?
82 """Does the API-style path refer to an extant directory?
70
83
71 Parameters
84 Parameters
72 ----------
85 ----------
@@ -102,29 +115,26 b' class FileContentsManager(ContentsManager):'
102 os_path = self._get_os_path(path=path)
115 os_path = self._get_os_path(path=path)
103 return is_hidden(os_path, self.root_dir)
116 return is_hidden(os_path, self.root_dir)
104
117
105 def _get_os_path(self, name=None, path=''):
118 def file_exists(self, name, path=''):
106 """Given a filename and a URL path, return its file system
119 """Returns True if the file exists, else returns False.
107 path.
108
120
109 Parameters
121 Parameters
110 ----------
122 ----------
111 name : string
123 name : string
112 A filename
124 The name of the file you are checking.
113 path : string
125 path : string
114 The relative URL path (with '/' as separator) to the named
126 The relative path to the file's directory (with '/' as separator)
115 file.
116
127
117 Returns
128 Returns
118 -------
129 -------
119 path : string
130 bool
120 API path to be evaluated relative to root_dir.
121 """
131 """
122 if name is not None:
132 path = path.strip('/')
123 path = path + '/' + name
133 nbpath = self._get_os_path(name, path=path)
124 return to_os_path(path, self.root_dir)
134 return os.path.isfile(nbpath)
125
135
126 def file_exists(self, name, path=''):
136 def exists(self, name=None, path=''):
127 """Returns a True if the file exists, else returns False.
137 """Returns True if the path [and name] exists, else returns False.
128
138
129 Parameters
139 Parameters
130 ----------
140 ----------
@@ -138,83 +148,107 b' class FileContentsManager(ContentsManager):'
138 bool
148 bool
139 """
149 """
140 path = path.strip('/')
150 path = path.strip('/')
141 nbpath = self._get_os_path(name, path=path)
151 os_path = self._get_os_path(name, path=path)
142 return os.path.isfile(nbpath)
152 return os.path.exists(os_path)
143
153
144 # TODO: Remove this after we create the contents web service and directories are
154 def _base_model(self, name, path=''):
145 # no longer listed by the notebook web service.
155 """Build the common base of a contents model"""
146 def list_dirs(self, path):
147 """List the directories for a given API style path."""
148 path = path.strip('/')
149 os_path = self._get_os_path('', path)
150 if not os.path.isdir(os_path):
151 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
152 elif is_hidden(os_path, self.root_dir):
153 self.log.info("Refusing to serve hidden directory, via 404 Error")
154 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
155 dir_names = os.listdir(os_path)
156 dirs = []
157 for name in dir_names:
158 os_path = self._get_os_path(name, path)
159 if os.path.isdir(os_path) and not is_hidden(os_path, self.root_dir)\
160 and self.should_list(name):
161 try:
162 model = self.get_dir_model(name, path)
163 except IOError:
164 pass
165 dirs.append(model)
166 dirs = sorted(dirs, key=sort_key)
167 return dirs
168
169 # TODO: Remove this after we create the contents web service and directories are
170 # no longer listed by the notebook web service.
171 def get_dir_model(self, name, path=''):
172 """Get the directory model given a directory name and its API style path"""
173 path = path.strip('/')
174 os_path = self._get_os_path(name, path)
156 os_path = self._get_os_path(name, path)
175 if not os.path.isdir(os_path):
176 raise IOError('directory does not exist: %r' % os_path)
177 info = os.stat(os_path)
157 info = os.stat(os_path)
178 last_modified = tz.utcfromtimestamp(info.st_mtime)
158 last_modified = tz.utcfromtimestamp(info.st_mtime)
179 created = tz.utcfromtimestamp(info.st_ctime)
159 created = tz.utcfromtimestamp(info.st_ctime)
180 # Create the notebook model.
160 # Create the notebook model.
181 model ={}
161 model = {}
182 model['name'] = name
162 model['name'] = name
183 model['path'] = path
163 model['path'] = path
184 model['last_modified'] = last_modified
164 model['last_modified'] = last_modified
185 model['created'] = created
165 model['created'] = created
166 model['content'] = None
167 model['format'] = None
168 return model
169
170 def _dir_model(self, name, path='', content=True):
171 """Build a model for a directory
172
173 if content is requested, will include a listing of the directory
174 """
175 os_path = self._get_os_path(name, path)
176
177 if not os.path.isdir(os_path):
178 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
179 elif is_hidden(os_path, self.root_dir):
180 self.log.info("Refusing to serve hidden directory, via 404 Error")
181 raise web.HTTPError(404, u'directory does not exist: %r' % os_path)
182
183 if name is None:
184 if '/' in path:
185 path, name = path.rsplit('/', 1)
186 else:
187 name = ''
188 model = self._base_model(name, path)
186 model['type'] = 'directory'
189 model['type'] = 'directory'
190 dir_path = u'{}/{}'.format(path, name)
191 if content:
192 contents = []
193 for os_path in glob.glob(self._get_os_path('*', dir_path)):
194 name = os.path.basename(os_path)
195 if self.should_list(name) and not is_hidden(os_path, self.root_dir):
196 contents.append(self.get_model(name=name, path=dir_path, content=False))
197
198 model['content'] = sorted(contents, key=sort_key)
199
187 return model
200 return model
188
201
189 def list_files(self, path):
202 def _file_model(self, name, path='', content=True):
190 """Returns a list of dictionaries that are the standard model
203 """Build a model for a file
191 for all notebooks in the relative 'path'.
192
204
193 Parameters
205 if content is requested, include the file contents.
194 ----------
206 Text files will be unicode, binary files will be base64-encoded.
195 path : str
207 """
196 the URL path that describes the relative path for the
208 model = self._base_model(name, path)
197 listed notebooks
209 model['type'] = 'file'
210 if content:
211 os_path = self._get_os_path(name, path)
212 try:
213 with io.open(os_path, 'r', encoding='utf-8') as f:
214 model['content'] = f.read()
215 except UnicodeError as e:
216 with io.open(os_path, 'rb') as f:
217 bcontent = f.read()
218 model['content'] = base64.encodestring(bcontent).decode('ascii')
219 model['format'] = 'base64'
220 else:
221 model['format'] = 'text'
222 return model
198
223
199 Returns
224
200 -------
225 def _notebook_model(self, name, path='', content=True):
201 notebooks : list of dicts
226 """Build a notebook model
202 a list of the notebook models without 'content'
227
228 if content is requested, the notebook content will be populated
229 as a JSON structure (not double-serialized)
203 """
230 """
204 path = path.strip('/')
231 model = self._base_model(name, path)
205 names = self.get_names(path)
232 model['type'] = 'notebook'
206 notebooks = [self.get(name, path, content=False)
233 if content:
207 for name in names if self.should_list(name)]
234 os_path = self._get_os_path(name, path)
208 notebooks = sorted(notebooks, key=sort_key)
235 with io.open(os_path, 'r', encoding='utf-8') as f:
209 return notebooks
236 try:
237 nb = current.read(f, u'json')
238 except Exception as e:
239 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
240 self.mark_trusted_cells(nb, name, path)
241 model['content'] = nb
242 model['format'] = 'json'
243 return model
210
244
211 def get(self, name, path='', content=True):
245 def get_model(self, name, path='', content=True):
212 """ Takes a path and name for a notebook and returns its model
246 """ Takes a path and name for an entity and returns its model
213
247
214 Parameters
248 Parameters
215 ----------
249 ----------
216 name : str
250 name : str
217 the name of the notebook
251 the name of the target
218 path : str
252 path : str
219 the URL path that describes the relative path for
253 the URL path that describes the relative path for
220 the notebook
254 the notebook
@@ -222,31 +256,21 b' class FileContentsManager(ContentsManager):'
222 Returns
256 Returns
223 -------
257 -------
224 model : dict
258 model : dict
225 the notebook model. If contents=True, returns the 'contents'
259 the contents model. If content=True, returns the contents
226 dict in the model as well.
260 of the file or directory as well.
227 """
261 """
228 path = path.strip('/')
262 path = path.strip('/')
229 if not self.file_exists(name=name, path=path):
263
230 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
264 if not self.exists(name=name, path=path):
265 raise web.HTTPError(404, u'No such file or directory: %s/%s' % (path, name))
266
231 os_path = self._get_os_path(name, path)
267 os_path = self._get_os_path(name, path)
232 info = os.stat(os_path)
268 if os.path.isdir(os_path):
233 last_modified = tz.utcfromtimestamp(info.st_mtime)
269 model = self._dir_model(name, path, content)
234 created = tz.utcfromtimestamp(info.st_ctime)
270 elif name.endswith('.ipynb'):
235 # Create the notebook model.
271 model = self._notebook_model(name, path, content)
236 model ={}
272 else:
237 model['name'] = name
273 model = self._file_model(name, path, content)
238 model['path'] = path
239 model['last_modified'] = last_modified
240 model['created'] = created
241 model['type'] = 'notebook'
242 if content:
243 with io.open(os_path, 'r', encoding='utf-8') as f:
244 try:
245 nb = current.read(f, u'json')
246 except Exception as e:
247 raise web.HTTPError(400, u"Unreadable Notebook: %s %s" % (os_path, e))
248 self.mark_trusted_cells(nb, name, path)
249 model['content'] = nb
250 return model
274 return model
251
275
252 def save(self, model, name='', path=''):
276 def save(self, model, name='', path=''):
@@ -281,7 +305,7 b' class FileContentsManager(ContentsManager):'
281 except Exception as e:
305 except Exception as e:
282 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
306 raise web.HTTPError(400, u'Unexpected error while autosaving notebook: %s %s' % (os_path, e))
283
307
284 model = self.get(new_name, new_path, content=False)
308 model = self.get_model(new_name, new_path, content=False)
285 return model
309 return model
286
310
287 def update(self, model, name, path=''):
311 def update(self, model, name, path=''):
@@ -291,7 +315,7 b' class FileContentsManager(ContentsManager):'
291 new_path = model.get('path', path).strip('/')
315 new_path = model.get('path', path).strip('/')
292 if path != new_path or name != new_name:
316 if path != new_path or name != new_name:
293 self.rename(name, path, new_name, new_path)
317 self.rename(name, path, new_name, new_path)
294 model = self.get(new_name, new_path, content=False)
318 model = self.get_model(new_name, new_path, content=False)
295 return model
319 return model
296
320
297 def delete(self, name, path=''):
321 def delete(self, name, path=''):
@@ -11,15 +11,15 b' from IPython.html.utils import url_path_join, url_escape'
11 from IPython.utils.jsonutil import date_default
11 from IPython.utils.jsonutil import date_default
12
12
13 from IPython.html.base.handlers import (IPythonHandler, json_errors,
13 from IPython.html.base.handlers import (IPythonHandler, json_errors,
14 notebook_path_regex, path_regex,
14 file_path_regex, path_regex,
15 notebook_name_regex)
15 file_name_regex)
16
16
17
17
18 class ContentsHandler(IPythonHandler):
18 class ContentsHandler(IPythonHandler):
19
19
20 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')
21
21
22 def location_url(self, name, path=''):
22 def location_url(self, name, path):
23 """Return the full URL location of a file.
23 """Return the full URL location of a file.
24
24
25 Parameters
25 Parameters
@@ -49,25 +49,19 b' class ContentsHandler(IPythonHandler):'
49 * GET with path and no filename lists files in a directory
49 * GET with path and no filename lists files in a directory
50 * GET with path and filename returns file contents model
50 * GET with path and filename returns file contents model
51 """
51 """
52 cm = self.contents_manager
52 path = path or ''
53 # Check to see if a filename was given
53 model = self.contents_manager.get_model(name=name, path=path)
54 if name is None:
54 if model['type'] == 'directory':
55 # TODO: Remove this after we create the contents web service and directories are
55 # resort listing to group directories at the top
56 # no longer listed by the notebook web service. This should only handle notebooks
56 dirs = []
57 # and not directories.
58 dirs = cm.list_dirs(path)
59 files = []
57 files = []
60 index = []
58 for entry in model['content']:
61 for nb in cm.list_files(path):
59 if entry['type'] == 'directory':
62 if nb['name'].lower() == 'index.ipynb':
60 dirs.append(entry)
63 index.append(nb)
64 else:
61 else:
65 files.append(nb)
62 # do we also want to group notebooks separate from files?
66 files = index + dirs + files
63 files.append(entry)
67 self.finish(json.dumps(files, default=date_default))
64 model['content'] = dirs + files
68 return
69 # get and return notebook representation
70 model = cm.get(name, path)
71 self._finish_model(model, location=False)
65 self._finish_model(model, location=False)
72
66
73 @web.authenticated
67 @web.authenticated
@@ -148,8 +142,16 b' class ContentsHandler(IPythonHandler):'
148 """
142 """
149
143
150 if name is not None:
144 if name is not None:
145 path = u'{}/{}'.format(path, name)
146
147 cm = self.contents_manager
148
149 if cm.file_exists(path):
151 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
150 raise web.HTTPError(400, "Only POST to directories. Use PUT for full names.")
152
151
152 if not cm.path_exists(path):
153 raise web.HTTPError(404, "No such directory: %s" % path)
154
153 model = self.get_json_body()
155 model = self.get_json_body()
154
156
155 if model is not None:
157 if model is not None:
@@ -200,6 +202,7 b' class ContentsHandler(IPythonHandler):'
200 def delete(self, path='', name=None):
202 def delete(self, path='', name=None):
201 """delete a file in the given path"""
203 """delete a file in the given path"""
202 cm = self.contents_manager
204 cm = self.contents_manager
205 self.log.warn('delete %s:%s', path, name)
203 cm.delete(name, path)
206 cm.delete(name, path)
204 self.set_status(204)
207 self.set_status(204)
205 self.finish()
208 self.finish()
@@ -262,9 +265,9 b' class ModifyCheckpointsHandler(IPythonHandler):'
262 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
265 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
263
266
264 default_handlers = [
267 default_handlers = [
265 (r"/api/contents%s/checkpoints" % notebook_path_regex, CheckpointsHandler),
268 (r"/api/contents%s/checkpoints" % file_path_regex, CheckpointsHandler),
266 (r"/api/contents%s/checkpoints/%s" % (notebook_path_regex, _checkpoint_id_regex),
269 (r"/api/contents%s/checkpoints/%s" % (file_path_regex, _checkpoint_id_regex),
267 ModifyCheckpointsHandler),
270 ModifyCheckpointsHandler),
268 (r"/api/contents%s" % notebook_path_regex, ContentsHandler),
271 (r"/api/contents%s" % file_path_regex, ContentsHandler),
269 (r"/api/contents%s" % path_regex, ContentsHandler),
272 (r"/api/contents%s" % path_regex, ContentsHandler),
270 ]
273 ]
@@ -75,27 +75,7 b' class ContentsManager(LoggingConfigurable):'
75 """
75 """
76 raise NotImplementedError('must be implemented in a subclass')
76 raise NotImplementedError('must be implemented in a subclass')
77
77
78 # TODO: Remove this after we create the contents web service and directories are
78 def list(self, path=''):
79 # no longer listed by the notebook web service.
80 def list_dirs(self, path):
81 """List the directory models for a given API style path."""
82 raise NotImplementedError('must be implemented in a subclass')
83
84 # TODO: Remove this after we create the contents web service and directories are
85 # no longer listed by the notebook web service.
86 def get_dir_model(self, name, path=''):
87 """Get the directory model given a directory name and its API style path.
88
89 The keys in the model should be:
90 * name
91 * path
92 * last_modified
93 * created
94 * type='directory'
95 """
96 raise NotImplementedError('must be implemented in a subclass')
97
98 def list_files(self, path=''):
99 """Return a list of contents dicts without content.
79 """Return a list of contents dicts without content.
100
80
101 This returns a list of dicts
81 This returns a list of dicts
@@ -196,7 +176,7 b' class ContentsManager(LoggingConfigurable):'
196 If to_name not specified, increment `from_name-Copy#.ipynb`.
176 If to_name not specified, increment `from_name-Copy#.ipynb`.
197 """
177 """
198 path = path.strip('/')
178 path = path.strip('/')
199 model = self.get(from_name, path)
179 model = self.get_model(from_name, path)
200 if not to_name:
180 if not to_name:
201 base, ext = os.path.splitext(from_name)
181 base, ext = os.path.splitext(from_name)
202 copy_name = u'{0}-Copy{1}'.format(base, ext)
182 copy_name = u'{0}-Copy{1}'.format(base, ext)
@@ -218,7 +198,7 b' class ContentsManager(LoggingConfigurable):'
218 path : string
198 path : string
219 The notebook's directory
199 The notebook's directory
220 """
200 """
221 model = self.get(name, path)
201 model = self.get_model(name, path)
222 nb = model['content']
202 nb = model['content']
223 self.log.warn("Trusting notebook %s/%s", path, name)
203 self.log.warn("Trusting notebook %s/%s", path, name)
224 self.notary.mark_cells(nb, True)
204 self.notary.mark_cells(nb, True)
@@ -1,6 +1,7 b''
1 # coding: utf-8
1 # coding: utf-8
2 """Test the contents webservice API."""
2 """Test the contents webservice API."""
3
3
4 import base64
4 import io
5 import io
5 import json
6 import json
6 import os
7 import os
@@ -23,11 +24,11 b' from IPython.utils.data import uniq_stable'
23
24
24 # TODO: Remove this after we create the contents web service and directories are
25 # TODO: Remove this after we create the contents web service and directories are
25 # no longer listed by the notebook web service.
26 # no longer listed by the notebook web service.
26 def notebooks_only(nb_list):
27 def notebooks_only(dir_model):
27 return [nb for nb in nb_list if nb['type']=='notebook']
28 return [nb for nb in dir_model['content'] if nb['type']=='notebook']
28
29
29 def dirs_only(nb_list):
30 def dirs_only(dir_model):
30 return [x for x in nb_list if x['type']=='directory']
31 return [x for x in dir_model['content'] if x['type']=='directory']
31
32
32
33
33 class API(object):
34 class API(object):
@@ -112,8 +113,20 b' class APITest(NotebookTestBase):'
112 del dirs[0] # remove ''
113 del dirs[0] # remove ''
113 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
114 top_level_dirs = {normalize('NFC', d.split('/')[0]) for d in dirs}
114
115
116 @staticmethod
117 def _blob_for_name(name):
118 return name.encode('utf-8') + b'\xFF'
119
120 @staticmethod
121 def _txt_for_name(name):
122 return u'%s text file' % name
123
115 def setUp(self):
124 def setUp(self):
116 nbdir = self.notebook_dir.name
125 nbdir = self.notebook_dir.name
126 self.blob = os.urandom(100)
127 self.b64_blob = base64.encodestring(self.blob).decode('ascii')
128
129
117
130
118 for d in (self.dirs + self.hidden_dirs):
131 for d in (self.dirs + self.hidden_dirs):
119 d.replace('/', os.sep)
132 d.replace('/', os.sep)
@@ -122,11 +135,21 b' class APITest(NotebookTestBase):'
122
135
123 for d, name in self.dirs_nbs:
136 for d, name in self.dirs_nbs:
124 d = d.replace('/', os.sep)
137 d = d.replace('/', os.sep)
138 # create a notebook
125 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
139 with io.open(pjoin(nbdir, d, '%s.ipynb' % name), 'w',
126 encoding='utf-8') as f:
140 encoding='utf-8') as f:
127 nb = new_notebook(name=name)
141 nb = new_notebook(name=name)
128 write(nb, f, format='ipynb')
142 write(nb, f, format='ipynb')
129
143
144 # create a text file
145 with io.open(pjoin(nbdir, d, '%s.txt' % name), 'w',
146 encoding='utf-8') as f:
147 f.write(self._txt_for_name(name))
148
149 # create a binary file
150 with io.open(pjoin(nbdir, d, '%s.blob' % name), 'wb') as f:
151 f.write(self._blob_for_name(name))
152
130 self.api = API(self.base_url())
153 self.api = API(self.base_url())
131
154
132 def tearDown(self):
155 def tearDown(self):
@@ -178,18 +201,49 b' class APITest(NotebookTestBase):'
178 with assert_http_error(404):
201 with assert_http_error(404):
179 self.api.list('nonexistant')
202 self.api.list('nonexistant')
180
203
181 def test_get_contents(self):
204 def test_get_nb_contents(self):
182 for d, name in self.dirs_nbs:
205 for d, name in self.dirs_nbs:
183 nb = self.api.read('%s.ipynb' % name, d+'/').json()
206 nb = self.api.read('%s.ipynb' % name, d+'/').json()
184 self.assertEqual(nb['name'], u'%s.ipynb' % name)
207 self.assertEqual(nb['name'], u'%s.ipynb' % name)
208 self.assertEqual(nb['type'], 'notebook')
209 self.assertIn('content', nb)
210 self.assertEqual(nb['format'], 'json')
185 self.assertIn('content', nb)
211 self.assertIn('content', nb)
186 self.assertIn('metadata', nb['content'])
212 self.assertIn('metadata', nb['content'])
187 self.assertIsInstance(nb['content']['metadata'], dict)
213 self.assertIsInstance(nb['content']['metadata'], dict)
188
214
215 def test_get_contents_no_such_file(self):
189 # Name that doesn't exist - should be a 404
216 # Name that doesn't exist - should be a 404
190 with assert_http_error(404):
217 with assert_http_error(404):
191 self.api.read('q.ipynb', 'foo')
218 self.api.read('q.ipynb', 'foo')
192
219
220 def test_get_text_file_contents(self):
221 for d, name in self.dirs_nbs:
222 model = self.api.read(u'%s.txt' % name, d+'/').json()
223 self.assertEqual(model['name'], u'%s.txt' % name)
224 self.assertIn('content', model)
225 self.assertEqual(model['format'], 'text')
226 self.assertEqual(model['type'], 'file')
227 self.assertEqual(model['content'], self._txt_for_name(name))
228
229 # Name that doesn't exist - should be a 404
230 with assert_http_error(404):
231 self.api.read('q.txt', 'foo')
232
233 def test_get_binary_file_contents(self):
234 for d, name in self.dirs_nbs:
235 model = self.api.read(u'%s.blob' % name, d+'/').json()
236 self.assertEqual(model['name'], u'%s.blob' % name)
237 self.assertIn('content', model)
238 self.assertEqual(model['format'], 'base64')
239 self.assertEqual(model['type'], 'file')
240 b64_data = base64.encodestring(self._blob_for_name(name)).decode('ascii')
241 self.assertEqual(model['content'], b64_data)
242
243 # Name that doesn't exist - should be a 404
244 with assert_http_error(404):
245 self.api.read('q.txt', 'foo')
246
193 def _check_nb_created(self, resp, name, path):
247 def _check_nb_created(self, resp, name, path):
194 self.assertEqual(resp.status_code, 201)
248 self.assertEqual(resp.status_code, 201)
195 location_header = py3compat.str_to_unicode(resp.headers['Location'])
249 location_header = py3compat.str_to_unicode(resp.headers['Location'])
@@ -70,7 +70,7 b' class TestFileContentsManager(TestCase):'
70 self.assertEqual(cp_subdir, os.path.join(root, 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 TestContentsManager(TestCase):
74
74
75 def setUp(self):
75 def setUp(self):
76 self._temp_dir = TemporaryDirectory()
76 self._temp_dir = TemporaryDirectory()
@@ -105,7 +105,7 b' class TestNotebookManager(TestCase):'
105 name = model['name']
105 name = model['name']
106 path = model['path']
106 path = model['path']
107
107
108 full_model = cm.get(name, path)
108 full_model = cm.get_model(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
@@ -140,7 +140,7 b' class TestNotebookManager(TestCase):'
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 = cm.get(name, path)
143 model2 = cm.get_model(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)
@@ -151,7 +151,7 b' class TestNotebookManager(TestCase):'
151 sub_dir = '/foo/'
151 sub_dir = '/foo/'
152 self.make_dir(cm.root_dir, 'foo')
152 self.make_dir(cm.root_dir, 'foo')
153 model = cm.create_notebook(None, sub_dir)
153 model = cm.create_notebook(None, sub_dir)
154 model2 = cm.get(name, sub_dir)
154 model2 = cm.get_model(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)
@@ -175,7 +175,7 b' class TestNotebookManager(TestCase):'
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, cm.get, name, path)
178 self.assertRaises(HTTPError, cm.get_model, 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
@@ -195,7 +195,7 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, cm.get, name, path)
198 self.assertRaises(HTTPError, cm.get_model, name, path)
199
199
200 def test_save(self):
200 def test_save(self):
201 cm = self.contents_manager
201 cm = self.contents_manager
@@ -205,7 +205,7 b' class TestNotebookManager(TestCase):'
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 = cm.get(name, path)
208 full_model = cm.get_model(name, path)
209
209
210 # Save the notebook
210 # Save the notebook
211 model = cm.save(full_model, name, path)
211 model = cm.save(full_model, name, path)
@@ -222,7 +222,7 b' class TestNotebookManager(TestCase):'
222 model = cm.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 = cm.get(name, path)
225 model = cm.get_model(name, path)
226
226
227 # Change the name in the model for rename
227 # Change the name in the model for rename
228 model = cm.save(model, name, path)
228 model = cm.save(model, name, path)
@@ -241,7 +241,7 b' class TestNotebookManager(TestCase):'
241 cm.delete(name, path)
241 cm.delete(name, path)
242
242
243 # Check that a 'get' on the deleted notebook raises and error
243 # Check that a 'get' on the deleted notebook raises and error
244 self.assertRaises(HTTPError, cm.get, name, path)
244 self.assertRaises(HTTPError, cm.get_model, name, path)
245
245
246 def test_copy(self):
246 def test_copy(self):
247 cm = self.contents_manager
247 cm = self.contents_manager
@@ -262,12 +262,12 b' class TestNotebookManager(TestCase):'
262 cm = self.contents_manager
262 cm = self.contents_manager
263 nb, name, path = self.new_notebook()
263 nb, name, path = self.new_notebook()
264
264
265 untrusted = cm.get(name, path)['content']
265 untrusted = cm.get_model(name, path)['content']
266 assert not cm.notary.check_cells(untrusted)
266 assert not cm.notary.check_cells(untrusted)
267
267
268 # print(untrusted)
268 # print(untrusted)
269 cm.trust_notebook(name, path)
269 cm.trust_notebook(name, path)
270 trusted = cm.get(name, path)['content']
270 trusted = cm.get_model(name, path)['content']
271 # print(trusted)
271 # print(trusted)
272 assert cm.notary.check_cells(trusted)
272 assert cm.notary.check_cells(trusted)
273
273
@@ -281,7 +281,7 b' class TestNotebookManager(TestCase):'
281 assert not cell.trusted
281 assert not cell.trusted
282
282
283 cm.trust_notebook(name, path)
283 cm.trust_notebook(name, path)
284 nb = cm.get(name, path)['content']
284 nb = cm.get_model(name, path)['content']
285 for cell in nb.worksheets[0].cells:
285 for cell in nb.worksheets[0].cells:
286 if cell.cell_type == 'code':
286 if cell.cell_type == 'code':
287 assert cell.trusted
287 assert cell.trusted
@@ -295,7 +295,7 b' class TestNotebookManager(TestCase):'
295 assert not cm.notary.check_signature(nb)
295 assert not cm.notary.check_signature(nb)
296
296
297 cm.trust_notebook(name, path)
297 cm.trust_notebook(name, path)
298 nb = cm.get(name, path)['content']
298 nb = cm.get_model(name, path)['content']
299 cm.mark_trusted_cells(nb, name, path)
299 cm.mark_trusted_cells(nb, name, path)
300 cm.check_and_sign(nb, name, path)
300 cm.check_and_sign(nb, name, path)
301 assert cm.notary.check_signature(nb)
301 assert cm.notary.check_signature(nb)
@@ -161,7 +161,8 b' define(['
161 message = param.msg;
161 message = param.msg;
162 }
162 }
163 var item = null;
163 var item = null;
164 var len = data.length;
164 var content = data.content;
165 var len = content.length;
165 this.clear_list();
166 this.clear_list();
166 if (len === 0) {
167 if (len === 0) {
167 item = this.new_notebook_item(0);
168 item = this.new_notebook_item(0);
@@ -177,12 +178,12 b' define(['
177 offset = 1;
178 offset = 1;
178 }
179 }
179 for (var i=0; i<len; i++) {
180 for (var i=0; i<len; i++) {
180 if (data[i].type === 'directory') {
181 if (content[i].type === 'directory') {
181 var name = data[i].name;
182 var name = content[i].name;
182 item = this.new_notebook_item(i+offset);
183 item = this.new_notebook_item(i+offset);
183 this.add_dir(path, name, item);
184 this.add_dir(path, name, item);
184 } else {
185 } else {
185 var name = data[i].name;
186 var name = content[i].name;
186 item = this.new_notebook_item(i+offset);
187 item = this.new_notebook_item(i+offset);
187 this.add_link(path, name, item);
188 this.add_link(path, name, item);
188 name = utils.url_path_join(path, name);
189 name = utils.url_path_join(path, name);
@@ -1,28 +1,12 b''
1 """Tornado handlers for the tree view.
1 """Tornado handlers for the tree view."""
2
2
3 Authors:
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4
5
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 from tornado import web
6 from tornado import web
19 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
7 from ..base.handlers import IPythonHandler, notebook_path_regex, path_regex
20 from ..utils import url_path_join, url_escape
8 from ..utils import url_path_join, url_escape
21
9
22 #-----------------------------------------------------------------------------
23 # Handlers
24 #-----------------------------------------------------------------------------
25
26
10
27 class TreeHandler(IPythonHandler):
11 class TreeHandler(IPythonHandler):
28 """Render the tree view, listing notebooks, clusters, etc."""
12 """Render the tree view, listing notebooks, clusters, etc."""
General Comments 0
You need to be logged in to leave comments. Login now