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 |
|
37 | help="""The directory name in which to keep file checkpoints | |
42 |
|
38 | |||
43 |
This is a path relative to the |
|
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 |
|
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 |
|
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 |
|
136 | """Create a new file or directory in the specified path. | |
134 |
|
137 | |||
135 |
POST creates new |
|
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, [ |
|
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 |
|
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 |
|
71 | The name of the file you are checking. | |
69 | path : string |
|
72 | path : string | |
70 |
The relative path to the |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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