Show More
@@ -23,6 +23,7 b' from zmq.utils import jsonapi' | |||||
23 |
|
23 | |||
24 |
|
24 | |||
25 | from ..base.handlers import IPythonHandler |
|
25 | from ..base.handlers import IPythonHandler | |
|
26 | from ..services.notebooks.handlers import _notebook_path_regex, _path_regex | |||
26 | from ..utils import url_path_join |
|
27 | from ..utils import url_path_join | |
27 | from urllib import quote |
|
28 | from urllib import quote | |
28 |
|
29 | |||
@@ -51,32 +52,31 b' class NotebookHandler(IPythonHandler):' | |||||
51 | class NamedNotebookHandler(IPythonHandler): |
|
52 | class NamedNotebookHandler(IPythonHandler): | |
52 |
|
53 | |||
53 | @web.authenticated |
|
54 | @web.authenticated | |
54 |
def get(self, |
|
55 | def get(self, path='', name=None): | |
55 | """get renders the notebook template if a name is given, or |
|
56 | """get renders the notebook template if a name is given, or | |
56 | redirects to the '/files/' handler if the name is not given.""" |
|
57 | redirects to the '/files/' handler if the name is not given.""" | |
57 | nbm = self.notebook_manager |
|
58 | nbm = self.notebook_manager | |
58 | name, path = nbm.named_notebook_path(notebook_path) |
|
59 | if name is None: | |
59 | if name is not None: |
|
60 | url = url_path_join(self.base_project_url, 'files', path) | |
60 | # a .ipynb filename was given |
|
|||
61 | if not nbm.notebook_exists(name, path): |
|
|||
62 | raise web.HTTPError(404, u'Notebook does not exist: %s' % name) |
|
|||
63 | name = nbm.url_encode(name) |
|
|||
64 | path = nbm.url_encode(path) |
|
|||
65 | self.write(self.render_template('notebook.html', |
|
|||
66 | project=self.project_dir, |
|
|||
67 | notebook_path=path, |
|
|||
68 | notebook_name=name, |
|
|||
69 | kill_kernel=False, |
|
|||
70 | mathjax_url=self.mathjax_url, |
|
|||
71 | ) |
|
|||
72 | ) |
|
|||
73 | else: |
|
|||
74 | url = "/files/" + notebook_path |
|
|||
75 | self.redirect(url) |
|
61 | self.redirect(url) | |
|
62 | return | |||
76 |
|
63 | |||
|
64 | # a .ipynb filename was given | |||
|
65 | if not nbm.notebook_exists(name, path): | |||
|
66 | raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name)) | |||
|
67 | name = nbm.url_encode(name) | |||
|
68 | path = nbm.url_encode(path) | |||
|
69 | self.write(self.render_template('notebook.html', | |||
|
70 | project=self.project_dir, | |||
|
71 | notebook_path=path, | |||
|
72 | notebook_name=name, | |||
|
73 | kill_kernel=False, | |||
|
74 | mathjax_url=self.mathjax_url, | |||
|
75 | ) | |||
|
76 | ) | |||
77 |
|
77 | |||
78 | @web.authenticated |
|
78 | @web.authenticated | |
79 |
def post(self, |
|
79 | def post(self, path='', name=None): | |
80 | """post either creates a new notebook if no json data is |
|
80 | """post either creates a new notebook if no json data is | |
81 | sent to the server, or copies the data and returns a |
|
81 | sent to the server, or copies the data and returns a | |
82 | copied notebook in the location given by 'notebook_path.""" |
|
82 | copied notebook in the location given by 'notebook_path.""" | |
@@ -95,10 +95,9 b' class NamedNotebookHandler(IPythonHandler):' | |||||
95 | #----------------------------------------------------------------------------- |
|
95 | #----------------------------------------------------------------------------- | |
96 |
|
96 | |||
97 |
|
97 | |||
98 | _notebook_path_regex = r"(?P<notebook_path>.+)" |
|
|||
99 |
|
||||
100 | default_handlers = [ |
|
98 | default_handlers = [ | |
101 | (r"/notebooks/%s" % _notebook_path_regex, NamedNotebookHandler), |
|
99 | (r"/notebooks/?%s" % _notebook_path_regex, NamedNotebookHandler), | |
102 | (r"/notebooks/", NotebookHandler), |
|
100 | (r"/notebooks/?%s" % _path_regex, NamedNotebookHandler), | |
|
101 | (r"/notebooks/?", NotebookHandler), | |||
103 | ] |
|
102 | ] | |
104 |
|
103 |
@@ -734,10 +734,9 b' class NotebookApp(BaseIPythonApplication):' | |||||
734 | browser = None |
|
734 | browser = None | |
735 |
|
735 | |||
736 | if self.file_to_run: |
|
736 | if self.file_to_run: | |
737 | name, _ = os.path.splitext(os.path.basename(self.file_to_run)) |
|
737 | url = url_path_join('notebooks', self.entry_path, self.file_to_run) | |
738 | url = 'notebooks/' + self.entry_path + name + _ |
|
|||
739 | else: |
|
738 | else: | |
740 |
url = 'tree |
|
739 | url = url_path_join('tree', self.entry_path) | |
741 | if browser: |
|
740 | if browser: | |
742 | b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip, |
|
741 | b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip, | |
743 | self.port, self.base_project_url, url), new=2) |
|
742 | self.port, self.base_project_url, url), new=2) |
@@ -73,14 +73,14 b' class FileNotebookManager(NotebookManager):' | |||||
73 | except: |
|
73 | except: | |
74 | raise TraitError("Couldn't create checkpoint dir %r" % new) |
|
74 | raise TraitError("Couldn't create checkpoint dir %r" % new) | |
75 |
|
75 | |||
76 |
def get_notebook_names(self, path=' |
|
76 | def get_notebook_names(self, path=''): | |
77 | """List all notebook names in the notebook dir and path.""" |
|
77 | """List all notebook names in the notebook dir and path.""" | |
78 | names = glob.glob(self.get_os_path('*'+self.filename_ext, path)) |
|
78 | names = glob.glob(self.get_os_path('*'+self.filename_ext, path)) | |
79 | names = [os.path.basename(name) |
|
79 | names = [os.path.basename(name) | |
80 | for name in names] |
|
80 | for name in names] | |
81 | return names |
|
81 | return names | |
82 |
|
82 | |||
83 |
def increment_filename(self, basename, path=' |
|
83 | def increment_filename(self, basename, path=''): | |
84 | """Return a non-used filename of the form basename<int>.""" |
|
84 | """Return a non-used filename of the form basename<int>.""" | |
85 | i = 0 |
|
85 | i = 0 | |
86 | while True: |
|
86 | while True: | |
@@ -97,7 +97,7 b' class FileNotebookManager(NotebookManager):' | |||||
97 | if os.path.exists(path) is False: |
|
97 | if os.path.exists(path) is False: | |
98 | raise web.HTTPError(404, "No file or directory found.") |
|
98 | raise web.HTTPError(404, "No file or directory found.") | |
99 |
|
99 | |||
100 |
def notebook_exists(self, name, path=' |
|
100 | def notebook_exists(self, name, path=''): | |
101 | """Returns a True if the notebook exists. Else, returns False. |
|
101 | """Returns a True if the notebook exists. Else, returns False. | |
102 |
|
102 | |||
103 | Parameters |
|
103 | Parameters | |
@@ -111,8 +111,8 b' class FileNotebookManager(NotebookManager):' | |||||
111 | ------- |
|
111 | ------- | |
112 | bool |
|
112 | bool | |
113 | """ |
|
113 | """ | |
114 |
path = self.get_os_path(name, path= |
|
114 | nbpath = self.get_os_path(name, path=path) | |
115 | return os.path.isfile(path) |
|
115 | return os.path.isfile(nbpath) | |
116 |
|
116 | |||
117 | def list_notebooks(self, path): |
|
117 | def list_notebooks(self, path): | |
118 | """Returns a list of dictionaries that are the standard model |
|
118 | """Returns a list of dictionaries that are the standard model | |
@@ -137,7 +137,7 b' class FileNotebookManager(NotebookManager):' | |||||
137 | notebooks = sorted(notebooks, key=lambda item: item['name']) |
|
137 | notebooks = sorted(notebooks, key=lambda item: item['name']) | |
138 | return notebooks |
|
138 | return notebooks | |
139 |
|
139 | |||
140 |
def get_notebook_model(self, name, path=' |
|
140 | def get_notebook_model(self, name, path='', content=True): | |
141 | """ Takes a path and name for a notebook and returns it's model |
|
141 | """ Takes a path and name for a notebook and returns it's model | |
142 |
|
142 | |||
143 | Parameters |
|
143 | Parameters | |
@@ -173,13 +173,13 b' class FileNotebookManager(NotebookManager):' | |||||
173 | model['content'] = nb |
|
173 | model['content'] = nb | |
174 | return model |
|
174 | return model | |
175 |
|
175 | |||
176 |
def save_notebook_model(self, model, name, path=' |
|
176 | def save_notebook_model(self, model, name, path=''): | |
177 | """Save the notebook model and return the model with no content.""" |
|
177 | """Save the notebook model and return the model with no content.""" | |
178 |
|
178 | |||
179 | if 'content' not in model: |
|
179 | if 'content' not in model: | |
180 | raise web.HTTPError(400, u'No notebook JSON data provided') |
|
180 | raise web.HTTPError(400, u'No notebook JSON data provided') | |
181 |
|
181 | |||
182 | new_path = model.get('path', path) |
|
182 | new_path = model.get('path', path).strip('/') | |
183 | new_name = model.get('name', name) |
|
183 | new_name = model.get('name', name) | |
184 |
|
184 | |||
185 | if path != new_path or name != new_name: |
|
185 | if path != new_path or name != new_name: |
@@ -20,10 +20,10 b' import json' | |||||
20 |
|
20 | |||
21 | from tornado import web |
|
21 | from tornado import web | |
22 |
|
22 | |||
23 |
from . |
|
23 | from IPython.html.utils import url_path_join | |
24 | from IPython.utils.jsonutil import date_default |
|
24 | from IPython.utils.jsonutil import date_default | |
25 |
|
25 | |||
26 |
from . |
|
26 | from IPython.html.base.handlers import IPythonHandler, json_errors | |
27 |
|
27 | |||
28 | #----------------------------------------------------------------------------- |
|
28 | #----------------------------------------------------------------------------- | |
29 | # Notebook web service handlers |
|
29 | # Notebook web service handlers | |
@@ -34,31 +34,32 b' class NotebookHandler(IPythonHandler):' | |||||
34 |
|
34 | |||
35 | SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE') |
|
35 | SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE') | |
36 |
|
36 | |||
37 | def notebook_location(self, name, path): |
|
37 | def notebook_location(self, name, path=''): | |
38 | """Return the full URL location of a notebook based. |
|
38 | """Return the full URL location of a notebook based. | |
39 |
|
39 | |||
40 | Parameters |
|
40 | Parameters | |
41 | ---------- |
|
41 | ---------- | |
42 | name : unicode |
|
42 | name : unicode | |
43 |
The name of the notebook |
|
43 | The base name of the notebook, such as "foo.ipynb". | |
44 | path : unicode |
|
44 | path : unicode | |
45 | The URL path of the notebook. |
|
45 | The URL path of the notebook. | |
46 | """ |
|
46 | """ | |
47 |
return url_path_join(self.base_project_url, |
|
47 | return url_path_join(self.base_project_url, 'api', 'notebooks', path, name) | |
48 |
|
48 | |||
49 | @web.authenticated |
|
49 | @web.authenticated | |
50 | @json_errors |
|
50 | @json_errors | |
51 |
def get(self, |
|
51 | def get(self, path='', name=None): | |
52 | """get checks if a notebook is not named, an returns a list of notebooks |
|
52 | """ | |
|
53 | GET with path and no notebook lists notebooks in a directory | |||
|
54 | GET with path and notebook name | |||
|
55 | ||||
|
56 | GET get checks if a notebook is not named, an returns a list of notebooks | |||
53 | in the notebook path given. If a name is given, return |
|
57 | in the notebook path given. If a name is given, return | |
54 | the notebook representation""" |
|
58 | the notebook representation""" | |
55 | nbm = self.notebook_manager |
|
59 | nbm = self.notebook_manager | |
56 | # path will have leading and trailing slashes, such as '/foo/bar/' |
|
|||
57 | name, path = nbm.named_notebook_path(notebook_path) |
|
|||
58 |
|
||||
59 | # Check to see if a notebook name was given |
|
60 | # Check to see if a notebook name was given | |
60 | if name is None: |
|
61 | if name is None: | |
61 |
# List notebooks in ' |
|
62 | # List notebooks in 'path' | |
62 | notebooks = nbm.list_notebooks(path) |
|
63 | notebooks = nbm.list_notebooks(path) | |
63 | self.finish(json.dumps(notebooks, default=date_default)) |
|
64 | self.finish(json.dumps(notebooks, default=date_default)) | |
64 | else: |
|
65 | else: | |
@@ -68,13 +69,11 b' class NotebookHandler(IPythonHandler):' | |||||
68 | self.finish(json.dumps(model, default=date_default)) |
|
69 | self.finish(json.dumps(model, default=date_default)) | |
69 |
|
70 | |||
70 | @web.authenticated |
|
71 | @web.authenticated | |
71 |
|
|
72 | @json_errors | |
72 |
def patch(self, |
|
73 | def patch(self, path='', name=None): | |
73 | """patch is currently used strictly for notebook renaming. |
|
74 | """patch is currently used strictly for notebook renaming. | |
74 | Changes the notebook name to the name given in data.""" |
|
75 | Changes the notebook name to the name given in data.""" | |
75 | nbm = self.notebook_manager |
|
76 | nbm = self.notebook_manager | |
76 | # path will have leading and trailing slashes, such as '/foo/bar/' |
|
|||
77 | name, path = nbm.named_notebook_path(notebook_path) |
|
|||
78 | if name is None: |
|
77 | if name is None: | |
79 | raise web.HTTPError(400, u'Notebook name missing') |
|
78 | raise web.HTTPError(400, u'Notebook name missing') | |
80 | model = self.get_json_body() |
|
79 | model = self.get_json_body() | |
@@ -90,11 +89,9 b' class NotebookHandler(IPythonHandler):' | |||||
90 |
|
89 | |||
91 | @web.authenticated |
|
90 | @web.authenticated | |
92 | @json_errors |
|
91 | @json_errors | |
93 |
def post(self, |
|
92 | def post(self, path='', name=None): | |
94 | """Create a new notebook in the location given by 'notebook_path'.""" |
|
93 | """Create a new notebook in the location given by 'notebook_path'.""" | |
95 | nbm = self.notebook_manager |
|
94 | nbm = self.notebook_manager | |
96 | # path will have leading and trailing slashes, such as '/foo/bar/' |
|
|||
97 | name, path = nbm.named_notebook_path(notebook_path) |
|
|||
98 | model = self.get_json_body() |
|
95 | model = self.get_json_body() | |
99 | if name is not None: |
|
96 | if name is not None: | |
100 | raise web.HTTPError(400, 'No name can be provided when POSTing a new notebook.') |
|
97 | raise web.HTTPError(400, 'No name can be provided when POSTing a new notebook.') | |
@@ -108,11 +105,9 b' class NotebookHandler(IPythonHandler):' | |||||
108 |
|
105 | |||
109 | @web.authenticated |
|
106 | @web.authenticated | |
110 | @json_errors |
|
107 | @json_errors | |
111 |
def put(self, |
|
108 | def put(self, path='', name=None): | |
112 | """saves the notebook in the location given by 'notebook_path'.""" |
|
109 | """saves the notebook in the location given by 'notebook_path'.""" | |
113 | nbm = self.notebook_manager |
|
110 | nbm = self.notebook_manager | |
114 | # path will have leading and trailing slashes, such as '/foo/bar/' |
|
|||
115 | name, path = nbm.named_notebook_path(notebook_path) |
|
|||
116 | model = self.get_json_body() |
|
111 | model = self.get_json_body() | |
117 | if model is None: |
|
112 | if model is None: | |
118 | raise web.HTTPError(400, u'JSON body missing') |
|
113 | raise web.HTTPError(400, u'JSON body missing') | |
@@ -121,11 +116,9 b' class NotebookHandler(IPythonHandler):' | |||||
121 |
|
116 | |||
122 | @web.authenticated |
|
117 | @web.authenticated | |
123 | @json_errors |
|
118 | @json_errors | |
124 |
def delete(self, |
|
119 | def delete(self, path='', name=None): | |
125 | """delete the notebook in the given notebook path""" |
|
120 | """delete the notebook in the given notebook path""" | |
126 | nbm = self.notebook_manager |
|
121 | nbm = self.notebook_manager | |
127 | # path will have leading and trailing slashes, such as '/foo/bar/' |
|
|||
128 | name, path = nbm.named_notebook_path(notebook_path) |
|
|||
129 | nbm.delete_notebook_model(name, path) |
|
122 | nbm.delete_notebook_model(name, path) | |
130 | self.set_status(204) |
|
123 | self.set_status(204) | |
131 | self.finish() |
|
124 | self.finish() | |
@@ -137,26 +130,22 b' class NotebookCheckpointsHandler(IPythonHandler):' | |||||
137 |
|
130 | |||
138 | @web.authenticated |
|
131 | @web.authenticated | |
139 | @json_errors |
|
132 | @json_errors | |
140 |
def get(self, |
|
133 | def get(self, path='', name=None): | |
141 | """get lists checkpoints for a notebook""" |
|
134 | """get lists checkpoints for a notebook""" | |
142 | nbm = self.notebook_manager |
|
135 | nbm = self.notebook_manager | |
143 | # path will have leading and trailing slashes, such as '/foo/bar/' |
|
|||
144 | name, path = nbm.named_notebook_path(notebook_path) |
|
|||
145 | checkpoints = nbm.list_checkpoints(name, path) |
|
136 | checkpoints = nbm.list_checkpoints(name, path) | |
146 | data = json.dumps(checkpoints, default=date_default) |
|
137 | data = json.dumps(checkpoints, default=date_default) | |
147 | self.finish(data) |
|
138 | self.finish(data) | |
148 |
|
139 | |||
149 | @web.authenticated |
|
140 | @web.authenticated | |
150 | @json_errors |
|
141 | @json_errors | |
151 |
def post(self, |
|
142 | def post(self, path='', name=None): | |
152 | """post creates a new checkpoint""" |
|
143 | """post creates a new checkpoint""" | |
153 | nbm = self.notebook_manager |
|
144 | nbm = self.notebook_manager | |
154 | name, path = nbm.named_notebook_path(notebook_path) |
|
|||
155 | # path will have leading and trailing slashes, such as '/foo/bar/' |
|
|||
156 | checkpoint = nbm.create_checkpoint(name, path) |
|
145 | checkpoint = nbm.create_checkpoint(name, path) | |
157 | data = json.dumps(checkpoint, default=date_default) |
|
146 | data = json.dumps(checkpoint, default=date_default) | |
158 | location = url_path_join(self.base_project_url, u'/api/notebooks', |
|
147 | location = url_path_join(self.base_project_url, u'/api/notebooks', | |
159 |
path, name, ' |
|
148 | path, name, 'checkpoints', checkpoint[u'checkpoint_id']) | |
160 | self.set_header(u'Location', location) |
|
149 | self.set_header(u'Location', location) | |
161 | self.finish(data) |
|
150 | self.finish(data) | |
162 |
|
151 | |||
@@ -167,22 +156,18 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):' | |||||
167 |
|
156 | |||
168 | @web.authenticated |
|
157 | @web.authenticated | |
169 | @json_errors |
|
158 | @json_errors | |
170 |
def post(self, |
|
159 | def post(self, path, name, checkpoint_id): | |
171 | """post restores a notebook from a checkpoint""" |
|
160 | """post restores a notebook from a checkpoint""" | |
172 | nbm = self.notebook_manager |
|
161 | nbm = self.notebook_manager | |
173 | # path will have leading and trailing slashes, such as '/foo/bar/' |
|
|||
174 | name, path = nbm.named_notebook_path(notebook_path) |
|
|||
175 | nbm.restore_checkpoint(checkpoint_id, name, path) |
|
162 | nbm.restore_checkpoint(checkpoint_id, name, path) | |
176 | self.set_status(204) |
|
163 | self.set_status(204) | |
177 | self.finish() |
|
164 | self.finish() | |
178 |
|
165 | |||
179 | @web.authenticated |
|
166 | @web.authenticated | |
180 | @json_errors |
|
167 | @json_errors | |
181 |
def delete(self, |
|
168 | def delete(self, path, name, checkpoint_id): | |
182 | """delete clears a checkpoint for a given notebook""" |
|
169 | """delete clears a checkpoint for a given notebook""" | |
183 | nbm = self.notebook_manager |
|
170 | nbm = self.notebook_manager | |
184 | # path will have leading and trailing slashes, such as '/foo/bar/' |
|
|||
185 | name, path = nbm.named_notebook_path(notebook_path) |
|
|||
186 | nbm.delete_checkpoint(checkpoint_id, name, path) |
|
171 | nbm.delete_checkpoint(checkpoint_id, name, path) | |
187 | self.set_status(204) |
|
172 | self.set_status(204) | |
188 | self.finish() |
|
173 | self.finish() | |
@@ -192,14 +177,17 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):' | |||||
192 | #----------------------------------------------------------------------------- |
|
177 | #----------------------------------------------------------------------------- | |
193 |
|
178 | |||
194 |
|
179 | |||
195 |
|
|
180 | _path_regex = r"(?P<path>.*)" | |
196 | _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)" |
|
181 | _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)" | |
|
182 | _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)" | |||
|
183 | _notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex) | |||
197 |
|
184 | |||
198 | default_handlers = [ |
|
185 | default_handlers = [ | |
199 | (r"/api/notebooks/%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler), |
|
186 | (r"/api/notebooks/?%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler), | |
200 | (r"/api/notebooks/%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex), |
|
187 | (r"/api/notebooks/?%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex), | |
201 | ModifyNotebookCheckpointsHandler), |
|
188 | ModifyNotebookCheckpointsHandler), | |
202 | (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler), |
|
189 | (r"/api/notebooks/?%s" % _notebook_path_regex, NotebookHandler), | |
|
190 | (r"/api/notebooks/?%s/?" % _path_regex, NotebookHandler), | |||
203 | ] |
|
191 | ] | |
204 |
|
192 | |||
205 |
|
193 |
@@ -45,45 +45,33 b' class NotebookManager(LoggingConfigurable):' | |||||
45 | """) |
|
45 | """) | |
46 |
|
46 | |||
47 | filename_ext = Unicode(u'.ipynb') |
|
47 | filename_ext = Unicode(u'.ipynb') | |
48 |
|
48 | |||
49 |
def |
|
49 | def path_exists(self, path): | |
50 | """Given notebook_path (*always* a URL path to notebook), returns a |
|
50 | """Does the API-style path (directory) actually exist? | |
51 | (name, path) tuple, where name is a .ipynb file, and path is the |
|
51 | ||
52 | URL path that describes the file system path for the file. |
|
52 | Override this method for non-filesystem-based notebooks. | |
53 | It *always* starts *and* ends with a '/' character. |
|
53 | ||
54 |
|
||||
55 | Parameters |
|
54 | Parameters | |
56 | ---------- |
|
55 | ---------- | |
57 |
|
|
56 | path : string | |
58 | A path that may be a .ipynb name or a directory |
|
57 | The | |
59 |
|
58 | |||
60 | Returns |
|
59 | Returns | |
61 | ------- |
|
60 | ------- | |
62 | name : string or None |
|
61 | exists : bool | |
63 | the filename of the notebook, or None if not a .ipynb extension |
|
62 | Whether the path does indeed exist. | |
64 | path : string |
|
|||
65 | the path to the directory which contains the notebook |
|
|||
66 | """ |
|
63 | """ | |
67 | names = notebook_path.split('/') |
|
64 | os_path = self.get_os_path(name, path) | |
68 | names = [n for n in names if n != ''] # remove duplicate splits |
|
65 | return os.path.exists(os_path) | |
69 |
|
||||
70 | names = [''] + names |
|
|||
71 |
|
||||
72 | if names and names[-1].endswith(".ipynb"): |
|
|||
73 | name = names[-1] |
|
|||
74 | path = "/".join(names[:-1]) + '/' |
|
|||
75 | else: |
|
|||
76 | name = None |
|
|||
77 | path = "/".join(names) + '/' |
|
|||
78 | return name, path |
|
|||
79 |
|
66 | |||
80 | def get_os_path(self, fname=None, path='/'): |
|
67 | ||
|
68 | def get_os_path(self, name=None, path=''): | |||
81 | """Given a notebook name and a URL path, return its file system |
|
69 | """Given a notebook name and a URL path, return its file system | |
82 | path. |
|
70 | path. | |
83 |
|
71 | |||
84 | Parameters |
|
72 | Parameters | |
85 | ---------- |
|
73 | ---------- | |
86 |
|
|
74 | name : string | |
87 | The name of a notebook file with the .ipynb extension |
|
75 | The name of a notebook file with the .ipynb extension | |
88 | path : string |
|
76 | path : string | |
89 | The relative URL path (with '/' as separator) to the named |
|
77 | The relative URL path (with '/' as separator) to the named | |
@@ -96,10 +84,10 b' class NotebookManager(LoggingConfigurable):' | |||||
96 | server started), the relative path, and the filename with the |
|
84 | server started), the relative path, and the filename with the | |
97 | current operating system's url. |
|
85 | current operating system's url. | |
98 | """ |
|
86 | """ | |
99 | parts = path.split('/') |
|
87 | parts = path.strip('/').split('/') | |
100 | parts = [p for p in parts if p != ''] # remove duplicate splits |
|
88 | parts = [p for p in parts if p != ''] # remove duplicate splits | |
101 |
if |
|
89 | if name is not None: | |
102 |
parts |
|
90 | parts.append(name) | |
103 | path = os.path.join(self.notebook_dir, *parts) |
|
91 | path = os.path.join(self.notebook_dir, *parts) | |
104 | return path |
|
92 | return path | |
105 |
|
93 | |||
@@ -132,7 +120,7 b' class NotebookManager(LoggingConfigurable):' | |||||
132 |
|
120 | |||
133 | # Main notebook API |
|
121 | # Main notebook API | |
134 |
|
122 | |||
135 |
def increment_filename(self, basename, path=' |
|
123 | def increment_filename(self, basename, path=''): | |
136 | """Increment a notebook filename without the .ipynb to make it unique. |
|
124 | """Increment a notebook filename without the .ipynb to make it unique. | |
137 |
|
125 | |||
138 | Parameters |
|
126 | Parameters | |
@@ -157,15 +145,15 b' class NotebookManager(LoggingConfigurable):' | |||||
157 | """ |
|
145 | """ | |
158 | raise NotImplementedError('must be implemented in a subclass') |
|
146 | raise NotImplementedError('must be implemented in a subclass') | |
159 |
|
147 | |||
160 |
def get_notebook_model(self, name, path=' |
|
148 | def get_notebook_model(self, name, path='', content=True): | |
161 | """Get the notebook model with or without content.""" |
|
149 | """Get the notebook model with or without content.""" | |
162 | raise NotImplementedError('must be implemented in a subclass') |
|
150 | raise NotImplementedError('must be implemented in a subclass') | |
163 |
|
151 | |||
164 |
def save_notebook_model(self, model, name, path=' |
|
152 | def save_notebook_model(self, model, name, path=''): | |
165 | """Save the notebook model and return the model with no content.""" |
|
153 | """Save the notebook model and return the model with no content.""" | |
166 | raise NotImplementedError('must be implemented in a subclass') |
|
154 | raise NotImplementedError('must be implemented in a subclass') | |
167 |
|
155 | |||
168 |
def update_notebook_model(self, model, name, path=' |
|
156 | def update_notebook_model(self, model, name, path=''): | |
169 | """Update the notebook model and return the model with no content.""" |
|
157 | """Update the notebook model and return the model with no content.""" | |
170 | raise NotImplementedError('must be implemented in a subclass') |
|
158 | raise NotImplementedError('must be implemented in a subclass') | |
171 |
|
159 | |||
@@ -173,7 +161,7 b' class NotebookManager(LoggingConfigurable):' | |||||
173 | """Delete notebook by name and path.""" |
|
161 | """Delete notebook by name and path.""" | |
174 | raise NotImplementedError('must be implemented in a subclass') |
|
162 | raise NotImplementedError('must be implemented in a subclass') | |
175 |
|
163 | |||
176 |
def create_notebook_model(self, model=None, path=' |
|
164 | def create_notebook_model(self, model=None, path=''): | |
177 | """Create a new untitled notebook and return its model with no content.""" |
|
165 | """Create a new untitled notebook and return its model with no content.""" | |
178 | name = self.increment_filename('Untitled', path) |
|
166 | name = self.increment_filename('Untitled', path) | |
179 | if model is None: |
|
167 | if model is None: |
@@ -67,48 +67,6 b' class TestNotebookManager(TestCase):' | |||||
67 | except OSError: |
|
67 | except OSError: | |
68 | print "Directory already exists." |
|
68 | print "Directory already exists." | |
69 |
|
69 | |||
70 | def test_named_notebook_path(self): |
|
|||
71 | """the `named_notebook_path` method takes a URL path to |
|
|||
72 | a notebook and returns a url path split into nb and path""" |
|
|||
73 | nm = NotebookManager() |
|
|||
74 |
|
||||
75 | # doesn't end with ipynb, should just be path |
|
|||
76 | name, path = nm.named_notebook_path('hello') |
|
|||
77 | self.assertEqual(name, None) |
|
|||
78 | self.assertEqual(path, '/hello/') |
|
|||
79 |
|
||||
80 | # Root path returns just the root slash |
|
|||
81 | name, path = nm.named_notebook_path('/') |
|
|||
82 | self.assertEqual(name, None) |
|
|||
83 | self.assertEqual(path, '/') |
|
|||
84 |
|
||||
85 | # get notebook, and return the path as '/' |
|
|||
86 | name, path = nm.named_notebook_path('notebook.ipynb') |
|
|||
87 | self.assertEqual(name, 'notebook.ipynb') |
|
|||
88 | self.assertEqual(path, '/') |
|
|||
89 |
|
||||
90 | # Test a notebook name with leading slash returns |
|
|||
91 | # the same as above |
|
|||
92 | name, path = nm.named_notebook_path('/notebook.ipynb') |
|
|||
93 | self.assertEqual(name, 'notebook.ipynb') |
|
|||
94 | self.assertEqual(path, '/') |
|
|||
95 |
|
||||
96 | # Multiple path arguments splits the notebook name |
|
|||
97 | # and returns path with leading and trailing '/' |
|
|||
98 | name, path = nm.named_notebook_path('/this/is/a/path/notebook.ipynb') |
|
|||
99 | self.assertEqual(name, 'notebook.ipynb') |
|
|||
100 | self.assertEqual(path, '/this/is/a/path/') |
|
|||
101 |
|
||||
102 | # path without leading slash is returned with leading slash |
|
|||
103 | name, path = nm.named_notebook_path('path/without/leading/slash/notebook.ipynb') |
|
|||
104 | self.assertEqual(name, 'notebook.ipynb') |
|
|||
105 | self.assertEqual(path, '/path/without/leading/slash/') |
|
|||
106 |
|
||||
107 | # path with spaces and no leading or trailing '/' |
|
|||
108 | name, path = nm.named_notebook_path('foo / bar% path& to# @/ notebook name.ipynb') |
|
|||
109 | self.assertEqual(name, ' notebook name.ipynb') |
|
|||
110 | self.assertEqual(path, '/foo / bar% path& to# @/') |
|
|||
111 |
|
||||
112 | def test_url_encode(self): |
|
70 | def test_url_encode(self): | |
113 | nm = NotebookManager() |
|
71 | nm = NotebookManager() | |
114 |
|
72 |
@@ -20,6 +20,7 b' import os' | |||||
20 | from tornado import web |
|
20 | from tornado import web | |
21 | from ..base.handlers import IPythonHandler |
|
21 | from ..base.handlers import IPythonHandler | |
22 | from ..utils import url_path_join, path2url, url2path |
|
22 | from ..utils import url_path_join, path2url, url2path | |
|
23 | from ..services.notebooks.handlers import _notebook_path_regex, _path_regex | |||
23 |
|
24 | |||
24 | #----------------------------------------------------------------------------- |
|
25 | #----------------------------------------------------------------------------- | |
25 | # Handlers |
|
26 | # Handlers | |
@@ -30,23 +31,19 b' class TreeHandler(IPythonHandler):' | |||||
30 | """Render the tree view, listing notebooks, clusters, etc.""" |
|
31 | """Render the tree view, listing notebooks, clusters, etc.""" | |
31 |
|
32 | |||
32 | @web.authenticated |
|
33 | @web.authenticated | |
33 |
def get(self, |
|
34 | def get(self, path='', name=None): | |
34 | nbm = self.notebook_manager |
|
35 | nbm = self.notebook_manager | |
35 | name, path = nbm.named_notebook_path(notebook_path) |
|
|||
36 | if name is not None: |
|
36 | if name is not None: | |
37 | # is a notebook, redirect to notebook handler |
|
37 | # is a notebook, redirect to notebook handler | |
38 | url = url_path_join(self.base_project_url, 'notebooks', path, name) |
|
38 | url = url_path_join(self.base_project_url, 'notebooks', path, name) | |
39 | self.redirect(url) |
|
39 | self.redirect(url) | |
40 | else: |
|
40 | else: | |
41 |
|
|
41 | if not nbm.path_exists(path=path): | |
42 |
|
||||
43 | if not os.path.exists(location): |
|
|||
44 | # no such directory, 404 |
|
42 | # no such directory, 404 | |
45 | raise web.HTTPError(404) |
|
43 | raise web.HTTPError(404) | |
46 |
|
||||
47 | self.write(self.render_template('tree.html', |
|
44 | self.write(self.render_template('tree.html', | |
48 | project=self.project_dir, |
|
45 | project=self.project_dir, | |
49 |
tree_url_path=path |
|
46 | tree_url_path=path, | |
50 | notebook_path=path, |
|
47 | notebook_path=path, | |
51 | )) |
|
48 | )) | |
52 |
|
49 | |||
@@ -55,8 +52,8 b' class TreeRedirectHandler(IPythonHandler):' | |||||
55 | """Redirect a request to the corresponding tree URL""" |
|
52 | """Redirect a request to the corresponding tree URL""" | |
56 |
|
53 | |||
57 | @web.authenticated |
|
54 | @web.authenticated | |
58 |
def get(self, |
|
55 | def get(self, path=''): | |
59 |
url = url_path_join(self.base_project_url, 'tree', |
|
56 | url = url_path_join(self.base_project_url, 'tree', path).rstrip('/') | |
60 | self.log.debug("Redirecting %s to %s", self.request.uri, url) |
|
57 | self.log.debug("Redirecting %s to %s", self.request.uri, url) | |
61 | self.redirect(url) |
|
58 | self.redirect(url) | |
62 |
|
59 | |||
@@ -66,11 +63,10 b' class TreeRedirectHandler(IPythonHandler):' | |||||
66 | #----------------------------------------------------------------------------- |
|
63 | #----------------------------------------------------------------------------- | |
67 |
|
64 | |||
68 |
|
65 | |||
69 | _notebook_path_regex = r"(?P<notebook_path>.+)" |
|
|||
70 |
|
||||
71 | default_handlers = [ |
|
66 | default_handlers = [ | |
72 |
(r"/tree/ |
|
67 | (r"/tree/(.*)/", TreeRedirectHandler), | |
73 | (r"/tree/%s" % _notebook_path_regex, TreeHandler), |
|
68 | (r"/tree/?%s" % _notebook_path_regex, TreeHandler), | |
|
69 | (r"/tree/?%s" % _path_regex, TreeHandler), | |||
74 | (r"/tree/", TreeRedirectHandler), |
|
70 | (r"/tree/", TreeRedirectHandler), | |
75 | (r"/tree", TreeHandler), |
|
71 | (r"/tree", TreeHandler), | |
76 | (r"/", TreeRedirectHandler), |
|
72 | (r"/", TreeRedirectHandler), |
General Comments 0
You need to be logged in to leave comments.
Login now