##// END OF EJS Templates
contents service review...
MinRK -
Show More
@@ -19,10 +19,6 b' from IPython.utils.py3compat import getcwd'
19 from IPython.utils import tz
19 from IPython.utils import tz
20 from IPython.html.utils import is_hidden, to_os_path
20 from IPython.html.utils import is_hidden, to_os_path
21
21
22 def sort_key(item):
23 """Case-insensitive sorting."""
24 return item['name'].lower()
25
26
22
27 class FileContentsManager(ContentsManager):
23 class FileContentsManager(ContentsManager):
28
24
@@ -38,9 +34,9 b' class FileContentsManager(ContentsManager):'
38 raise TraitError("%r is not a directory" % new)
34 raise TraitError("%r is not a directory" % new)
39
35
40 checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
36 checkpoint_dir = Unicode('.ipynb_checkpoints', config=True,
41 help="""The directory name in which to keep notebook checkpoints
37 help="""The directory name in which to keep file checkpoints
42
38
43 This is a path relative to the notebook's own directory.
39 This is a path relative to the file's own directory.
44
40
45 By default, it is .ipynb_checkpoints
41 By default, it is .ipynb_checkpoints
46 """
42 """
@@ -157,7 +153,7 b' class FileContentsManager(ContentsManager):'
157 info = os.stat(os_path)
153 info = os.stat(os_path)
158 last_modified = tz.utcfromtimestamp(info.st_mtime)
154 last_modified = tz.utcfromtimestamp(info.st_mtime)
159 created = tz.utcfromtimestamp(info.st_ctime)
155 created = tz.utcfromtimestamp(info.st_ctime)
160 # Create the notebook model.
156 # Create the base model.
161 model = {}
157 model = {}
162 model['name'] = name
158 model['name'] = name
163 model['path'] = path
159 model['path'] = path
@@ -189,13 +185,12 b' class FileContentsManager(ContentsManager):'
189 model['type'] = 'directory'
185 model['type'] = 'directory'
190 dir_path = u'{}/{}'.format(path, name)
186 dir_path = u'{}/{}'.format(path, name)
191 if content:
187 if content:
192 contents = []
188 model['content'] = contents = []
193 for os_path in glob.glob(self._get_os_path('*', dir_path)):
189 for os_path in glob.glob(self._get_os_path('*', dir_path)):
194 name = os.path.basename(os_path)
190 name = os.path.basename(os_path)
195 if self.should_list(name) and not is_hidden(os_path, self.root_dir):
191 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))
192 contents.append(self.get_model(name=name, path=dir_path, content=False))
197
193
198 model['content'] = sorted(contents, key=sort_key)
199 model['format'] = 'json'
194 model['format'] = 'json'
200
195
201 return model
196 return model
@@ -204,7 +199,7 b' class FileContentsManager(ContentsManager):'
204 """Build a model for a file
199 """Build a model for a file
205
200
206 if content is requested, include the file contents.
201 if content is requested, include the file contents.
207 Text files will be unicode, binary files will be base64-encoded.
202 UTF-8 text files will be unicode, binary files will be base64-encoded.
208 """
203 """
209 model = self._base_model(name, path)
204 model = self._base_model(name, path)
210 model['type'] = 'file'
205 model['type'] = 'file'
@@ -251,8 +246,7 b' class FileContentsManager(ContentsManager):'
251 name : str
246 name : str
252 the name of the target
247 the name of the target
253 path : str
248 path : str
254 the URL path that describes the relative path for
249 the URL path that describes the relative path for the target
255 the notebook
256
250
257 Returns
251 Returns
258 -------
252 -------
@@ -275,6 +269,7 b' class FileContentsManager(ContentsManager):'
275 return model
269 return model
276
270
277 def _save_notebook(self, os_path, model, name='', path=''):
271 def _save_notebook(self, os_path, model, name='', path=''):
272 """save a notebook file"""
278 # Save the notebook file
273 # Save the notebook file
279 nb = current.to_notebook_json(model['content'])
274 nb = current.to_notebook_json(model['content'])
280
275
@@ -287,6 +282,7 b' class FileContentsManager(ContentsManager):'
287 current.write(nb, f, u'json')
282 current.write(nb, f, u'json')
288
283
289 def _save_file(self, os_path, model, name='', path=''):
284 def _save_file(self, os_path, model, name='', path=''):
285 """save a non-notebook file"""
290 fmt = model.get('format', None)
286 fmt = model.get('format', None)
291 if fmt not in {'text', 'base64'}:
287 if fmt not in {'text', 'base64'}:
292 raise web.HTTPError(400, "Must specify format of file contents as 'text' or 'base64'")
288 raise web.HTTPError(400, "Must specify format of file contents as 'text' or 'base64'")
@@ -303,6 +299,7 b' class FileContentsManager(ContentsManager):'
303 f.write(bcontent)
299 f.write(bcontent)
304
300
305 def _save_directory(self, os_path, model, name='', path=''):
301 def _save_directory(self, os_path, model, name='', path=''):
302 """create a directory"""
306 if not os.path.exists(os_path):
303 if not os.path.exists(os_path):
307 os.mkdir(os_path)
304 os.mkdir(os_path)
308 elif not os.path.isdir(os_path):
305 elif not os.path.isdir(os_path):
@@ -442,7 +439,7 b' class FileContentsManager(ContentsManager):'
442 # only the one checkpoint ID:
439 # only the one checkpoint ID:
443 checkpoint_id = u"checkpoint"
440 checkpoint_id = u"checkpoint"
444 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
441 cp_path = self.get_checkpoint_path(checkpoint_id, name, path)
445 self.log.debug("creating checkpoint for notebook %s", name)
442 self.log.debug("creating checkpoint for %s", name)
446 self._copy(src_path, cp_path)
443 self._copy(src_path, cp_path)
447
444
448 # return the checkpoint info
445 # return the checkpoint info
@@ -15,6 +15,16 b' from IPython.html.base.handlers import (IPythonHandler, json_errors,'
15 file_name_regex)
15 file_name_regex)
16
16
17
17
18 def sort_key(model):
19 """key function for case-insensitive sort by name and type"""
20 iname = model['name'].lower()
21 type_key = {
22 'directory' : '0',
23 'notebook' : '1',
24 'file' : '2',
25 }.get(model['type'], '9')
26 return u'%s%s' % (type_key, iname)
27
18 class ContentsHandler(IPythonHandler):
28 class ContentsHandler(IPythonHandler):
19
29
20 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
30 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
@@ -52,16 +62,9 b' class ContentsHandler(IPythonHandler):'
52 path = path or ''
62 path = path or ''
53 model = self.contents_manager.get_model(name=name, path=path)
63 model = self.contents_manager.get_model(name=name, path=path)
54 if model['type'] == 'directory':
64 if model['type'] == 'directory':
55 # resort listing to group directories at the top
65 # group listing by type, then by name (case-insensitive)
56 dirs = []
66 # FIXME: front-ends shouldn't rely on this sorting
57 files = []
67 model['content'].sort(key=sort_key)
58 for entry in model['content']:
59 if entry['type'] == 'directory':
60 dirs.append(entry)
61 else:
62 # do we also want to group notebooks separate from files?
63 files.append(entry)
64 model['content'] = dirs + files
65 self._finish_model(model, location=False)
68 self._finish_model(model, location=False)
66
69
67 @web.authenticated
70 @web.authenticated
@@ -130,9 +133,9 b' class ContentsHandler(IPythonHandler):'
130 @web.authenticated
133 @web.authenticated
131 @json_errors
134 @json_errors
132 def post(self, path='', name=None):
135 def post(self, path='', name=None):
133 """Create a new notebook in the specified path.
136 """Create a new file or directory in the specified path.
134
137
135 POST creates new notebooks. The server always decides on the notebook name.
138 POST creates new files or directories. The server always decides on the name.
136
139
137 POST /api/contents/path
140 POST /api/contents/path
138 New untitled notebook in path. If content specified, upload a
141 New untitled notebook in path. If content specified, upload a
@@ -18,7 +18,10 b' class ContentsManager(LoggingConfigurable):'
18 def _notary_default(self):
18 def _notary_default(self):
19 return sign.NotebookNotary(parent=self)
19 return sign.NotebookNotary(parent=self)
20
20
21 hide_globs = List(Unicode, [u'__pycache__'], config=True, help="""
21 hide_globs = List(Unicode, [
22 u'__pycache__', '*.pyc', '*.pyo',
23 '.DS_Store', '*.so', '*.dylib', '*~',
24 ], config=True, help="""
22 Glob patterns to hide in file and directory listings.
25 Glob patterns to hide in file and directory listings.
23 """)
26 """)
24
27
@@ -60,14 +63,14 b' class ContentsManager(LoggingConfigurable):'
60 raise NotImplementedError
63 raise NotImplementedError
61
64
62 def file_exists(self, name, path=''):
65 def file_exists(self, name, path=''):
63 """Returns a True if the notebook exists. Else, returns False.
66 """Returns a True if the file exists. Else, returns False.
64
67
65 Parameters
68 Parameters
66 ----------
69 ----------
67 name : string
70 name : string
68 The name of the notebook you are checking.
71 The name of the file you are checking.
69 path : string
72 path : string
70 The relative path to the notebook (with '/' as separator)
73 The relative path to the file's directory (with '/' as separator)
71
74
72 Returns
75 Returns
73 -------
76 -------
@@ -87,38 +90,38 b' class ContentsManager(LoggingConfigurable):'
87 raise NotImplementedError('must be implemented in a subclass')
90 raise NotImplementedError('must be implemented in a subclass')
88
91
89 def get_model(self, name, path='', content=True):
92 def get_model(self, name, path='', content=True):
90 """Get the notebook model with or without content."""
93 """Get the model of a file or directory with or without content."""
91 raise NotImplementedError('must be implemented in a subclass')
94 raise NotImplementedError('must be implemented in a subclass')
92
95
93 def save(self, model, name, path=''):
96 def save(self, model, name, path=''):
94 """Save the notebook and return the model with no content."""
97 """Save the file or directory and return the model with no content."""
95 raise NotImplementedError('must be implemented in a subclass')
98 raise NotImplementedError('must be implemented in a subclass')
96
99
97 def update(self, model, name, path=''):
100 def update(self, model, name, path=''):
98 """Update the notebook and return the model with no content."""
101 """Update the file or directory and return the model with no content."""
99 raise NotImplementedError('must be implemented in a subclass')
102 raise NotImplementedError('must be implemented in a subclass')
100
103
101 def delete(self, name, path=''):
104 def delete(self, name, path=''):
102 """Delete notebook by name and path."""
105 """Delete file or directory by name and path."""
103 raise NotImplementedError('must be implemented in a subclass')
106 raise NotImplementedError('must be implemented in a subclass')
104
107
105 def create_checkpoint(self, name, path=''):
108 def create_checkpoint(self, name, path=''):
106 """Create a checkpoint of the current state of a notebook
109 """Create a checkpoint of the current state of a file
107
110
108 Returns a checkpoint_id for the new checkpoint.
111 Returns a checkpoint_id for the new checkpoint.
109 """
112 """
110 raise NotImplementedError("must be implemented in a subclass")
113 raise NotImplementedError("must be implemented in a subclass")
111
114
112 def list_checkpoints(self, name, path=''):
115 def list_checkpoints(self, name, path=''):
113 """Return a list of checkpoints for a given notebook"""
116 """Return a list of checkpoints for a given file"""
114 return []
117 return []
115
118
116 def restore_checkpoint(self, checkpoint_id, name, path=''):
119 def restore_checkpoint(self, checkpoint_id, name, path=''):
117 """Restore a notebook from one of its checkpoints"""
120 """Restore a file from one of its checkpoints"""
118 raise NotImplementedError("must be implemented in a subclass")
121 raise NotImplementedError("must be implemented in a subclass")
119
122
120 def delete_checkpoint(self, checkpoint_id, name, path=''):
123 def delete_checkpoint(self, checkpoint_id, name, path=''):
121 """delete a checkpoint for a notebook"""
124 """delete a checkpoint for a file"""
122 raise NotImplementedError("must be implemented in a subclass")
125 raise NotImplementedError("must be implemented in a subclass")
123
126
124 def info_string(self):
127 def info_string(self):
@@ -139,7 +142,7 b' class ContentsManager(LoggingConfigurable):'
139 filename : unicode
142 filename : unicode
140 The name of a file, including extension
143 The name of a file, including extension
141 path : unicode
144 path : unicode
142 The URL path of the notebooks directory
145 The URL path of the target's directory
143
146
144 Returns
147 Returns
145 -------
148 -------
@@ -156,7 +159,7 b' class ContentsManager(LoggingConfigurable):'
156 return name
159 return name
157
160
158 def create_file(self, model=None, path='', ext='.ipynb'):
161 def create_file(self, model=None, path='', ext='.ipynb'):
159 """Create a new notebook and return its model with no content."""
162 """Create a new file or directory and return its model with no content."""
160 path = path.strip('/')
163 path = path.strip('/')
161 if model is None:
164 if model is None:
162 model = {}
165 model = {}
General Comments 0
You need to be logged in to leave comments. Login now