Show More
@@ -0,0 +1,61 b'' | |||||
|
1 | """Test HTML utils""" | |||
|
2 | ||||
|
3 | #----------------------------------------------------------------------------- | |||
|
4 | # Copyright (C) 2013 The IPython Development Team | |||
|
5 | # | |||
|
6 | # Distributed under the terms of the BSD License. The full license is in | |||
|
7 | # the file COPYING, distributed as part of this software. | |||
|
8 | #----------------------------------------------------------------------------- | |||
|
9 | ||||
|
10 | #----------------------------------------------------------------------------- | |||
|
11 | # Imports | |||
|
12 | #----------------------------------------------------------------------------- | |||
|
13 | ||||
|
14 | import nose.tools as nt | |||
|
15 | ||||
|
16 | import IPython.testing.tools as tt | |||
|
17 | from IPython.html.utils import url_escape, url_unescape | |||
|
18 | ||||
|
19 | #----------------------------------------------------------------------------- | |||
|
20 | # Test functions | |||
|
21 | #----------------------------------------------------------------------------- | |||
|
22 | ||||
|
23 | def test_help_output(): | |||
|
24 | """ipython notebook --help-all works""" | |||
|
25 | tt.help_all_output_test('notebook') | |||
|
26 | ||||
|
27 | ||||
|
28 | def test_url_escape(): | |||
|
29 | ||||
|
30 | # changes path or notebook name with special characters to url encoding | |||
|
31 | # these tests specifically encode paths with spaces | |||
|
32 | path = url_escape('/this is a test/for spaces/') | |||
|
33 | nt.assert_equal(path, '/this%20is%20a%20test/for%20spaces/') | |||
|
34 | ||||
|
35 | path = url_escape('notebook with space.ipynb') | |||
|
36 | nt.assert_equal(path, 'notebook%20with%20space.ipynb') | |||
|
37 | ||||
|
38 | path = url_escape('/path with a/notebook and space.ipynb') | |||
|
39 | nt.assert_equal(path, '/path%20with%20a/notebook%20and%20space.ipynb') | |||
|
40 | ||||
|
41 | path = url_escape('/ !@$#%^&* / test %^ notebook @#$ name.ipynb') | |||
|
42 | nt.assert_equal(path, | |||
|
43 | '/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb') | |||
|
44 | ||||
|
45 | def test_url_unescape(): | |||
|
46 | ||||
|
47 | # decodes a url string to a plain string | |||
|
48 | # these tests decode paths with spaces | |||
|
49 | path = url_unescape('/this%20is%20a%20test/for%20spaces/') | |||
|
50 | nt.assert_equal(path, '/this is a test/for spaces/') | |||
|
51 | ||||
|
52 | path = url_unescape('notebook%20with%20space.ipynb') | |||
|
53 | nt.assert_equal(path, 'notebook with space.ipynb') | |||
|
54 | ||||
|
55 | path = url_unescape('/path%20with%20a/notebook%20and%20space.ipynb') | |||
|
56 | nt.assert_equal(path, '/path with a/notebook and space.ipynb') | |||
|
57 | ||||
|
58 | path = url_unescape( | |||
|
59 | '/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb') | |||
|
60 | nt.assert_equal(path, '/ !@$#%^&* / test %^ notebook @#$ name.ipynb') | |||
|
61 |
@@ -1,103 +1,103 b'' | |||||
1 | """Tornado handlers for the live notebook view. |
|
1 | """Tornado handlers for the live notebook view. | |
2 |
|
2 | |||
3 | Authors: |
|
3 | Authors: | |
4 |
|
4 | |||
5 | * Brian Granger |
|
5 | * Brian Granger | |
6 | """ |
|
6 | """ | |
7 |
|
7 | |||
8 | #----------------------------------------------------------------------------- |
|
8 | #----------------------------------------------------------------------------- | |
9 | # Copyright (C) 2011 The IPython Development Team |
|
9 | # Copyright (C) 2011 The IPython Development Team | |
10 | # |
|
10 | # | |
11 | # Distributed under the terms of the BSD License. The full license is in |
|
11 | # Distributed under the terms of the BSD License. The full license is in | |
12 | # the file COPYING, distributed as part of this software. |
|
12 | # the file COPYING, distributed as part of this software. | |
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 |
|
14 | |||
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | # Imports |
|
16 | # Imports | |
17 | #----------------------------------------------------------------------------- |
|
17 | #----------------------------------------------------------------------------- | |
18 |
|
18 | |||
19 | import os |
|
19 | import os | |
20 | from tornado import web |
|
20 | from tornado import web | |
21 | HTTPError = web.HTTPError |
|
21 | HTTPError = web.HTTPError | |
22 | from zmq.utils import jsonapi |
|
22 | 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 ..services.notebooks.handlers import _notebook_path_regex, _path_regex | |
27 | from ..utils import url_path_join |
|
27 | from ..utils import url_path_join, url_escape, url_unescape | |
28 | from urllib import quote |
|
28 | from urllib import quote | |
29 |
|
29 | |||
30 | #----------------------------------------------------------------------------- |
|
30 | #----------------------------------------------------------------------------- | |
31 | # Handlers |
|
31 | # Handlers | |
32 | #----------------------------------------------------------------------------- |
|
32 | #----------------------------------------------------------------------------- | |
33 |
|
33 | |||
34 |
|
34 | |||
35 | class NotebookHandler(IPythonHandler): |
|
35 | class NotebookHandler(IPythonHandler): | |
36 |
|
36 | |||
37 | @web.authenticated |
|
37 | @web.authenticated | |
38 | def post(self): |
|
38 | def post(self): | |
39 | """post either creates a new notebook if no json data is |
|
39 | """post either creates a new notebook if no json data is | |
40 | sent to the server, or copies the data and returns a |
|
40 | sent to the server, or copies the data and returns a | |
41 | copied notebook.""" |
|
41 | copied notebook.""" | |
42 | nbm = self.notebook_manager |
|
42 | nbm = self.notebook_manager | |
43 | data=self.request.body |
|
43 | data=self.request.body | |
44 | if data: |
|
44 | if data: | |
45 | data = jsonapi.loads(data) |
|
45 | data = jsonapi.loads(data) | |
46 | notebook_name = nbm.copy_notebook(data['name']) |
|
46 | notebook_name = nbm.copy_notebook(data['name']) | |
47 | else: |
|
47 | else: | |
48 | notebook_name = nbm.new_notebook() |
|
48 | notebook_name = nbm.new_notebook() | |
49 | self.finish(jsonapi.dumps({"name": notebook_name})) |
|
49 | self.finish(jsonapi.dumps({"name": notebook_name})) | |
50 |
|
50 | |||
51 |
|
51 | |||
52 | class NamedNotebookHandler(IPythonHandler): |
|
52 | class NamedNotebookHandler(IPythonHandler): | |
53 |
|
53 | |||
54 | @web.authenticated |
|
54 | @web.authenticated | |
55 | def get(self, path='', name=None): |
|
55 | def get(self, path='', name=None): | |
56 | """get renders the notebook template if a name is given, or |
|
56 | """get renders the notebook template if a name is given, or | |
57 | redirects to the '/files/' handler if the name is not given.""" |
|
57 | redirects to the '/files/' handler if the name is not given.""" | |
58 | nbm = self.notebook_manager |
|
58 | nbm = self.notebook_manager | |
59 | if name is None: |
|
59 | if name is None: | |
60 | url = url_path_join(self.base_project_url, 'files', path) |
|
60 | url = url_path_join(self.base_project_url, 'files', path) | |
61 | self.redirect(url) |
|
61 | self.redirect(url) | |
62 | return |
|
62 | return | |
63 |
|
63 | |||
64 | # a .ipynb filename was given |
|
64 | # a .ipynb filename was given | |
65 | if not nbm.notebook_exists(name, path): |
|
65 | if not nbm.notebook_exists(name, path): | |
66 | raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name)) |
|
66 | raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name)) | |
67 |
name = |
|
67 | name = url_escape(name) | |
68 |
path = |
|
68 | path = url_escape(path) | |
69 | self.write(self.render_template('notebook.html', |
|
69 | self.write(self.render_template('notebook.html', | |
70 | project=self.project_dir, |
|
70 | project=self.project_dir, | |
71 | notebook_path=path, |
|
71 | notebook_path=path, | |
72 | notebook_name=name, |
|
72 | notebook_name=name, | |
73 | kill_kernel=False, |
|
73 | kill_kernel=False, | |
74 | mathjax_url=self.mathjax_url, |
|
74 | mathjax_url=self.mathjax_url, | |
75 | ) |
|
75 | ) | |
76 | ) |
|
76 | ) | |
77 |
|
77 | |||
78 | @web.authenticated |
|
78 | @web.authenticated | |
79 | def post(self, path='', name=None): |
|
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.""" | |
83 | nbm = self.notebook_manager |
|
83 | nbm = self.notebook_manager | |
84 | data = self.request.body |
|
84 | data = self.request.body | |
85 | if data: |
|
85 | if data: | |
86 | data = jsonapi.loads(data) |
|
86 | data = jsonapi.loads(data) | |
87 | notebook_name = nbm.copy_notebook(data['name'], notebook_path) |
|
87 | notebook_name = nbm.copy_notebook(data['name'], notebook_path) | |
88 | else: |
|
88 | else: | |
89 | notebook_name = nbm.new_notebook(notebook_path) |
|
89 | notebook_name = nbm.new_notebook(notebook_path) | |
90 | self.finish(jsonapi.dumps({"name": notebook_name})) |
|
90 | self.finish(jsonapi.dumps({"name": notebook_name})) | |
91 |
|
91 | |||
92 |
|
92 | |||
93 | #----------------------------------------------------------------------------- |
|
93 | #----------------------------------------------------------------------------- | |
94 | # URL to handler mappings |
|
94 | # URL to handler mappings | |
95 | #----------------------------------------------------------------------------- |
|
95 | #----------------------------------------------------------------------------- | |
96 |
|
96 | |||
97 |
|
97 | |||
98 | default_handlers = [ |
|
98 | default_handlers = [ | |
99 | (r"/notebooks/?%s" % _notebook_path_regex, NamedNotebookHandler), |
|
99 | (r"/notebooks/?%s" % _notebook_path_regex, NamedNotebookHandler), | |
100 | (r"/notebooks/?%s" % _path_regex, NamedNotebookHandler), |
|
100 | (r"/notebooks/?%s" % _path_regex, NamedNotebookHandler), | |
101 | (r"/notebooks/?", NotebookHandler), |
|
101 | (r"/notebooks/?", NotebookHandler), | |
102 | ] |
|
102 | ] | |
103 |
|
103 |
@@ -1,211 +1,199 b'' | |||||
1 | """A base class notebook manager. |
|
1 | """A base class notebook manager. | |
2 |
|
2 | |||
3 | Authors: |
|
3 | Authors: | |
4 |
|
4 | |||
5 | * Brian Granger |
|
5 | * Brian Granger | |
6 | * Zach Sailer |
|
6 | * Zach Sailer | |
7 | """ |
|
7 | """ | |
8 |
|
8 | |||
9 | #----------------------------------------------------------------------------- |
|
9 | #----------------------------------------------------------------------------- | |
10 | # Copyright (C) 2011 The IPython Development Team |
|
10 | # Copyright (C) 2011 The IPython Development Team | |
11 | # |
|
11 | # | |
12 | # Distributed under the terms of the BSD License. The full license is in |
|
12 | # Distributed under the terms of the BSD License. The full license is in | |
13 | # the file COPYING, distributed as part of this software. |
|
13 | # the file COPYING, distributed as part of this software. | |
14 | #----------------------------------------------------------------------------- |
|
14 | #----------------------------------------------------------------------------- | |
15 |
|
15 | |||
16 | #----------------------------------------------------------------------------- |
|
16 | #----------------------------------------------------------------------------- | |
17 | # Imports |
|
17 | # Imports | |
18 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
19 |
|
19 | |||
20 | import os |
|
20 | import os | |
21 | import uuid |
|
21 | import uuid | |
22 | from urllib import quote, unquote |
|
22 | from urllib import quote, unquote | |
23 |
|
23 | |||
24 | from tornado import web |
|
24 | from tornado import web | |
25 |
|
25 | |||
26 | from IPython.html.utils import url_path_join |
|
26 | from IPython.html.utils import url_path_join | |
27 | from IPython.config.configurable import LoggingConfigurable |
|
27 | from IPython.config.configurable import LoggingConfigurable | |
28 | from IPython.nbformat import current |
|
28 | from IPython.nbformat import current | |
29 | from IPython.utils.traitlets import List, Dict, Unicode, TraitError |
|
29 | from IPython.utils.traitlets import List, Dict, Unicode, TraitError | |
30 |
|
30 | |||
31 | #----------------------------------------------------------------------------- |
|
31 | #----------------------------------------------------------------------------- | |
32 | # Classes |
|
32 | # Classes | |
33 | #----------------------------------------------------------------------------- |
|
33 | #----------------------------------------------------------------------------- | |
34 |
|
34 | |||
35 | class NotebookManager(LoggingConfigurable): |
|
35 | class NotebookManager(LoggingConfigurable): | |
36 |
|
36 | |||
37 | # Todo: |
|
37 | # Todo: | |
38 | # The notebook_dir attribute is used to mean a couple of different things: |
|
38 | # The notebook_dir attribute is used to mean a couple of different things: | |
39 | # 1. Where the notebooks are stored if FileNotebookManager is used. |
|
39 | # 1. Where the notebooks are stored if FileNotebookManager is used. | |
40 | # 2. The cwd of the kernel for a project. |
|
40 | # 2. The cwd of the kernel for a project. | |
41 | # Right now we use this attribute in a number of different places and |
|
41 | # Right now we use this attribute in a number of different places and | |
42 | # we are going to have to disentangle all of this. |
|
42 | # we are going to have to disentangle all of this. | |
43 | notebook_dir = Unicode(os.getcwdu(), config=True, help=""" |
|
43 | notebook_dir = Unicode(os.getcwdu(), config=True, help=""" | |
44 | The directory to use for notebooks. |
|
44 | The directory to use for notebooks. | |
45 | """) |
|
45 | """) | |
46 |
|
46 | |||
47 | filename_ext = Unicode(u'.ipynb') |
|
47 | filename_ext = Unicode(u'.ipynb') | |
48 |
|
48 | |||
49 | def path_exists(self, path): |
|
49 | def path_exists(self, path): | |
50 | """Does the API-style path (directory) actually exist? |
|
50 | """Does the API-style path (directory) actually exist? | |
51 |
|
51 | |||
52 | Override this method for non-filesystem-based notebooks. |
|
52 | Override this method for non-filesystem-based notebooks. | |
53 |
|
53 | |||
54 | Parameters |
|
54 | Parameters | |
55 | ---------- |
|
55 | ---------- | |
56 | path : string |
|
56 | path : string | |
57 | The |
|
57 | The | |
58 |
|
58 | |||
59 | Returns |
|
59 | Returns | |
60 | ------- |
|
60 | ------- | |
61 | exists : bool |
|
61 | exists : bool | |
62 | Whether the path does indeed exist. |
|
62 | Whether the path does indeed exist. | |
63 | """ |
|
63 | """ | |
64 | os_path = self.get_os_path(name, path) |
|
64 | os_path = self.get_os_path(name, path) | |
65 | return os.path.exists(os_path) |
|
65 | return os.path.exists(os_path) | |
66 |
|
66 | |||
67 |
|
67 | |||
68 | def get_os_path(self, name=None, path=''): |
|
68 | def get_os_path(self, name=None, path=''): | |
69 | """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 | |
70 | path. |
|
70 | path. | |
71 |
|
71 | |||
72 | Parameters |
|
72 | Parameters | |
73 | ---------- |
|
73 | ---------- | |
74 | name : string |
|
74 | name : string | |
75 | The name of a notebook file with the .ipynb extension |
|
75 | The name of a notebook file with the .ipynb extension | |
76 | path : string |
|
76 | path : string | |
77 | The relative URL path (with '/' as separator) to the named |
|
77 | The relative URL path (with '/' as separator) to the named | |
78 | notebook. |
|
78 | notebook. | |
79 |
|
79 | |||
80 | Returns |
|
80 | Returns | |
81 | ------- |
|
81 | ------- | |
82 | path : string |
|
82 | path : string | |
83 | A file system path that combines notebook_dir (location where |
|
83 | A file system path that combines notebook_dir (location where | |
84 | server started), the relative path, and the filename with the |
|
84 | server started), the relative path, and the filename with the | |
85 | current operating system's url. |
|
85 | current operating system's url. | |
86 | """ |
|
86 | """ | |
87 | parts = path.strip('/').split('/') |
|
87 | parts = path.strip('/').split('/') | |
88 | parts = [p for p in parts if p != ''] # remove duplicate splits |
|
88 | parts = [p for p in parts if p != ''] # remove duplicate splits | |
89 | if name is not None: |
|
89 | if name is not None: | |
90 | parts.append(name) |
|
90 | parts.append(name) | |
91 | path = os.path.join(self.notebook_dir, *parts) |
|
91 | path = os.path.join(self.notebook_dir, *parts) | |
92 | return path |
|
92 | return path | |
93 |
|
93 | |||
94 | def url_encode(self, path): |
|
|||
95 | """Takes a URL path with special characters and returns |
|
|||
96 | the path with all these characters URL encoded""" |
|
|||
97 | parts = path.split('/') |
|
|||
98 | return '/'.join([quote(p) for p in parts]) |
|
|||
99 |
|
||||
100 | def url_decode(self, path): |
|
|||
101 | """Takes a URL path with encoded special characters and |
|
|||
102 | returns the URL with special characters decoded""" |
|
|||
103 | parts = path.split('/') |
|
|||
104 | return '/'.join([unquote(p) for p in parts]) |
|
|||
105 |
|
||||
106 | def _notebook_dir_changed(self, name, old, new): |
|
94 | def _notebook_dir_changed(self, name, old, new): | |
107 | """Do a bit of validation of the notebook dir.""" |
|
95 | """Do a bit of validation of the notebook dir.""" | |
108 | if not os.path.isabs(new): |
|
96 | if not os.path.isabs(new): | |
109 | # If we receive a non-absolute path, make it absolute. |
|
97 | # If we receive a non-absolute path, make it absolute. | |
110 | self.notebook_dir = os.path.abspath(new) |
|
98 | self.notebook_dir = os.path.abspath(new) | |
111 | return |
|
99 | return | |
112 | if os.path.exists(new) and not os.path.isdir(new): |
|
100 | if os.path.exists(new) and not os.path.isdir(new): | |
113 | raise TraitError("notebook dir %r is not a directory" % new) |
|
101 | raise TraitError("notebook dir %r is not a directory" % new) | |
114 | if not os.path.exists(new): |
|
102 | if not os.path.exists(new): | |
115 | self.log.info("Creating notebook dir %s", new) |
|
103 | self.log.info("Creating notebook dir %s", new) | |
116 | try: |
|
104 | try: | |
117 | os.mkdir(new) |
|
105 | os.mkdir(new) | |
118 | except: |
|
106 | except: | |
119 | raise TraitError("Couldn't create notebook dir %r" % new) |
|
107 | raise TraitError("Couldn't create notebook dir %r" % new) | |
120 |
|
108 | |||
121 | # Main notebook API |
|
109 | # Main notebook API | |
122 |
|
110 | |||
123 | def increment_filename(self, basename, path=''): |
|
111 | def increment_filename(self, basename, path=''): | |
124 | """Increment a notebook filename without the .ipynb to make it unique. |
|
112 | """Increment a notebook filename without the .ipynb to make it unique. | |
125 |
|
113 | |||
126 | Parameters |
|
114 | Parameters | |
127 | ---------- |
|
115 | ---------- | |
128 | basename : unicode |
|
116 | basename : unicode | |
129 | The name of a notebook without the ``.ipynb`` file extension. |
|
117 | The name of a notebook without the ``.ipynb`` file extension. | |
130 | path : unicode |
|
118 | path : unicode | |
131 | The URL path of the notebooks directory |
|
119 | The URL path of the notebooks directory | |
132 | """ |
|
120 | """ | |
133 | return basename |
|
121 | return basename | |
134 |
|
122 | |||
135 | def list_notebooks(self): |
|
123 | def list_notebooks(self): | |
136 | """Return a list of notebook dicts without content. |
|
124 | """Return a list of notebook dicts without content. | |
137 |
|
125 | |||
138 | This returns a list of dicts, each of the form:: |
|
126 | This returns a list of dicts, each of the form:: | |
139 |
|
127 | |||
140 | dict(notebook_id=notebook,name=name) |
|
128 | dict(notebook_id=notebook,name=name) | |
141 |
|
129 | |||
142 | This list of dicts should be sorted by name:: |
|
130 | This list of dicts should be sorted by name:: | |
143 |
|
131 | |||
144 | data = sorted(data, key=lambda item: item['name']) |
|
132 | data = sorted(data, key=lambda item: item['name']) | |
145 | """ |
|
133 | """ | |
146 | raise NotImplementedError('must be implemented in a subclass') |
|
134 | raise NotImplementedError('must be implemented in a subclass') | |
147 |
|
135 | |||
148 | def get_notebook_model(self, name, path='', content=True): |
|
136 | def get_notebook_model(self, name, path='', content=True): | |
149 | """Get the notebook model with or without content.""" |
|
137 | """Get the notebook model with or without content.""" | |
150 | raise NotImplementedError('must be implemented in a subclass') |
|
138 | raise NotImplementedError('must be implemented in a subclass') | |
151 |
|
139 | |||
152 | def save_notebook_model(self, model, name, path=''): |
|
140 | def save_notebook_model(self, model, name, path=''): | |
153 | """Save the notebook model and return the model with no content.""" |
|
141 | """Save the notebook model and return the model with no content.""" | |
154 | raise NotImplementedError('must be implemented in a subclass') |
|
142 | raise NotImplementedError('must be implemented in a subclass') | |
155 |
|
143 | |||
156 | def update_notebook_model(self, model, name, path=''): |
|
144 | def update_notebook_model(self, model, name, path=''): | |
157 | """Update the notebook model and return the model with no content.""" |
|
145 | """Update the notebook model and return the model with no content.""" | |
158 | raise NotImplementedError('must be implemented in a subclass') |
|
146 | raise NotImplementedError('must be implemented in a subclass') | |
159 |
|
147 | |||
160 | def delete_notebook_model(self, name, path): |
|
148 | def delete_notebook_model(self, name, path): | |
161 | """Delete notebook by name and path.""" |
|
149 | """Delete notebook by name and path.""" | |
162 | raise NotImplementedError('must be implemented in a subclass') |
|
150 | raise NotImplementedError('must be implemented in a subclass') | |
163 |
|
151 | |||
164 | def create_notebook_model(self, model=None, path=''): |
|
152 | def create_notebook_model(self, model=None, path=''): | |
165 | """Create a new untitled notebook and return its model with no content.""" |
|
153 | """Create a new untitled notebook and return its model with no content.""" | |
166 | name = self.increment_filename('Untitled', path) |
|
154 | name = self.increment_filename('Untitled', path) | |
167 | if model is None: |
|
155 | if model is None: | |
168 | model = {} |
|
156 | model = {} | |
169 | metadata = current.new_metadata(name=u'') |
|
157 | metadata = current.new_metadata(name=u'') | |
170 | nb = current.new_notebook(metadata=metadata) |
|
158 | nb = current.new_notebook(metadata=metadata) | |
171 | model['content'] = nb |
|
159 | model['content'] = nb | |
172 | model['name'] = name |
|
160 | model['name'] = name | |
173 | model['path'] = path |
|
161 | model['path'] = path | |
174 | model = self.save_notebook_model(model, name, path) |
|
162 | model = self.save_notebook_model(model, name, path) | |
175 | return model |
|
163 | return model | |
176 |
|
164 | |||
177 | def copy_notebook(self, name, path='/', content=False): |
|
165 | def copy_notebook(self, name, path='/', content=False): | |
178 | """Copy an existing notebook and return its new model.""" |
|
166 | """Copy an existing notebook and return its new model.""" | |
179 | model = self.get_notebook_model(name, path) |
|
167 | model = self.get_notebook_model(name, path) | |
180 | name = os.path.splitext(name)[0] + '-Copy' |
|
168 | name = os.path.splitext(name)[0] + '-Copy' | |
181 | name = self.increment_filename(name, path) + self.filename_ext |
|
169 | name = self.increment_filename(name, path) + self.filename_ext | |
182 | model['name'] = name |
|
170 | model['name'] = name | |
183 | model = self.save_notebook_model(model, name, path, content=content) |
|
171 | model = self.save_notebook_model(model, name, path, content=content) | |
184 | return model |
|
172 | return model | |
185 |
|
173 | |||
186 | # Checkpoint-related |
|
174 | # Checkpoint-related | |
187 |
|
175 | |||
188 | def create_checkpoint(self, name, path='/'): |
|
176 | def create_checkpoint(self, name, path='/'): | |
189 | """Create a checkpoint of the current state of a notebook |
|
177 | """Create a checkpoint of the current state of a notebook | |
190 |
|
178 | |||
191 | Returns a checkpoint_id for the new checkpoint. |
|
179 | Returns a checkpoint_id for the new checkpoint. | |
192 | """ |
|
180 | """ | |
193 | raise NotImplementedError("must be implemented in a subclass") |
|
181 | raise NotImplementedError("must be implemented in a subclass") | |
194 |
|
182 | |||
195 | def list_checkpoints(self, name, path='/'): |
|
183 | def list_checkpoints(self, name, path='/'): | |
196 | """Return a list of checkpoints for a given notebook""" |
|
184 | """Return a list of checkpoints for a given notebook""" | |
197 | return [] |
|
185 | return [] | |
198 |
|
186 | |||
199 | def restore_checkpoint(self, checkpoint_id, name, path='/'): |
|
187 | def restore_checkpoint(self, checkpoint_id, name, path='/'): | |
200 | """Restore a notebook from one of its checkpoints""" |
|
188 | """Restore a notebook from one of its checkpoints""" | |
201 | raise NotImplementedError("must be implemented in a subclass") |
|
189 | raise NotImplementedError("must be implemented in a subclass") | |
202 |
|
190 | |||
203 | def delete_checkpoint(self, checkpoint_id, name, path='/'): |
|
191 | def delete_checkpoint(self, checkpoint_id, name, path='/'): | |
204 | """delete a checkpoint for a notebook""" |
|
192 | """delete a checkpoint for a notebook""" | |
205 | raise NotImplementedError("must be implemented in a subclass") |
|
193 | raise NotImplementedError("must be implemented in a subclass") | |
206 |
|
194 | |||
207 | def log_info(self): |
|
195 | def log_info(self): | |
208 | self.log.info(self.info_string()) |
|
196 | self.log.info(self.info_string()) | |
209 |
|
197 | |||
210 | def info_string(self): |
|
198 | def info_string(self): | |
211 | return "Serving notebooks" |
|
199 | return "Serving notebooks" |
@@ -1,246 +1,210 b'' | |||||
1 | """Tests for the notebook manager.""" |
|
1 | """Tests for the notebook manager.""" | |
2 |
|
2 | |||
3 | import os |
|
3 | import os | |
4 |
|
4 | |||
5 | from tornado.web import HTTPError |
|
5 | from tornado.web import HTTPError | |
6 | from unittest import TestCase |
|
6 | from unittest import TestCase | |
7 | from tempfile import NamedTemporaryFile |
|
7 | from tempfile import NamedTemporaryFile | |
8 |
|
8 | |||
9 | from IPython.utils.tempdir import TemporaryDirectory |
|
9 | from IPython.utils.tempdir import TemporaryDirectory | |
10 | from IPython.utils.traitlets import TraitError |
|
10 | from IPython.utils.traitlets import TraitError | |
11 | from IPython.html.utils import url_path_join |
|
11 | from IPython.html.utils import url_path_join | |
12 |
|
12 | |||
13 | from ..filenbmanager import FileNotebookManager |
|
13 | from ..filenbmanager import FileNotebookManager | |
14 | from ..nbmanager import NotebookManager |
|
14 | from ..nbmanager import NotebookManager | |
15 |
|
15 | |||
16 | class TestFileNotebookManager(TestCase): |
|
16 | class TestFileNotebookManager(TestCase): | |
17 |
|
17 | |||
18 | def test_nb_dir(self): |
|
18 | def test_nb_dir(self): | |
19 | with TemporaryDirectory() as td: |
|
19 | with TemporaryDirectory() as td: | |
20 | fm = FileNotebookManager(notebook_dir=td) |
|
20 | fm = FileNotebookManager(notebook_dir=td) | |
21 | self.assertEqual(fm.notebook_dir, td) |
|
21 | self.assertEqual(fm.notebook_dir, td) | |
22 |
|
22 | |||
23 | def test_create_nb_dir(self): |
|
23 | def test_create_nb_dir(self): | |
24 | with TemporaryDirectory() as td: |
|
24 | with TemporaryDirectory() as td: | |
25 | nbdir = os.path.join(td, 'notebooks') |
|
25 | nbdir = os.path.join(td, 'notebooks') | |
26 | fm = FileNotebookManager(notebook_dir=nbdir) |
|
26 | fm = FileNotebookManager(notebook_dir=nbdir) | |
27 | self.assertEqual(fm.notebook_dir, nbdir) |
|
27 | self.assertEqual(fm.notebook_dir, nbdir) | |
28 |
|
28 | |||
29 | def test_missing_nb_dir(self): |
|
29 | def test_missing_nb_dir(self): | |
30 | with TemporaryDirectory() as td: |
|
30 | with TemporaryDirectory() as td: | |
31 | nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing') |
|
31 | nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing') | |
32 | self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir) |
|
32 | self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir) | |
33 |
|
33 | |||
34 | def test_invalid_nb_dir(self): |
|
34 | def test_invalid_nb_dir(self): | |
35 | with NamedTemporaryFile() as tf: |
|
35 | with NamedTemporaryFile() as tf: | |
36 | self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name) |
|
36 | self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name) | |
37 |
|
37 | |||
38 | def test_get_os_path(self): |
|
38 | def test_get_os_path(self): | |
39 | # full filesystem path should be returned with correct operating system |
|
39 | # full filesystem path should be returned with correct operating system | |
40 | # separators. |
|
40 | # separators. | |
41 | with TemporaryDirectory() as td: |
|
41 | with TemporaryDirectory() as td: | |
42 | nbdir = os.path.join(td, 'notebooks') |
|
42 | nbdir = os.path.join(td, 'notebooks') | |
43 | fm = FileNotebookManager(notebook_dir=nbdir) |
|
43 | fm = FileNotebookManager(notebook_dir=nbdir) | |
44 | path = fm.get_os_path('test.ipynb', '/path/to/notebook/') |
|
44 | path = fm.get_os_path('test.ipynb', '/path/to/notebook/') | |
45 | rel_path_list = '/path/to/notebook/test.ipynb'.split('/') |
|
45 | rel_path_list = '/path/to/notebook/test.ipynb'.split('/') | |
46 | fs_path = os.path.join(fm.notebook_dir, *rel_path_list) |
|
46 | fs_path = os.path.join(fm.notebook_dir, *rel_path_list) | |
47 | self.assertEqual(path, fs_path) |
|
47 | self.assertEqual(path, fs_path) | |
48 |
|
48 | |||
49 | fm = FileNotebookManager(notebook_dir=nbdir) |
|
49 | fm = FileNotebookManager(notebook_dir=nbdir) | |
50 | path = fm.get_os_path('test.ipynb') |
|
50 | path = fm.get_os_path('test.ipynb') | |
51 | fs_path = os.path.join(fm.notebook_dir, 'test.ipynb') |
|
51 | fs_path = os.path.join(fm.notebook_dir, 'test.ipynb') | |
52 | self.assertEqual(path, fs_path) |
|
52 | self.assertEqual(path, fs_path) | |
53 |
|
53 | |||
54 | fm = FileNotebookManager(notebook_dir=nbdir) |
|
54 | fm = FileNotebookManager(notebook_dir=nbdir) | |
55 | path = fm.get_os_path('test.ipynb', '////') |
|
55 | path = fm.get_os_path('test.ipynb', '////') | |
56 | fs_path = os.path.join(fm.notebook_dir, 'test.ipynb') |
|
56 | fs_path = os.path.join(fm.notebook_dir, 'test.ipynb') | |
57 | self.assertEqual(path, fs_path) |
|
57 | self.assertEqual(path, fs_path) | |
58 |
|
58 | |||
59 | class TestNotebookManager(TestCase): |
|
59 | class TestNotebookManager(TestCase): | |
60 |
|
60 | |||
61 | def make_dir(self, abs_path, rel_path): |
|
61 | def make_dir(self, abs_path, rel_path): | |
62 | """make subdirectory, rel_path is the relative path |
|
62 | """make subdirectory, rel_path is the relative path | |
63 | to that directory from the location where the server started""" |
|
63 | to that directory from the location where the server started""" | |
64 | os_path = os.path.join(abs_path, rel_path) |
|
64 | os_path = os.path.join(abs_path, rel_path) | |
65 | try: |
|
65 | try: | |
66 | os.makedirs(os_path) |
|
66 | os.makedirs(os_path) | |
67 | except OSError: |
|
67 | except OSError: | |
68 | print "Directory already exists." |
|
68 | print "Directory already exists." | |
69 |
|
69 | |||
70 | def test_url_encode(self): |
|
|||
71 | nm = NotebookManager() |
|
|||
72 |
|
||||
73 | # changes path or notebook name with special characters to url encoding |
|
|||
74 | # these tests specifically encode paths with spaces |
|
|||
75 | path = nm.url_encode('/this is a test/for spaces/') |
|
|||
76 | self.assertEqual(path, '/this%20is%20a%20test/for%20spaces/') |
|
|||
77 |
|
||||
78 | path = nm.url_encode('notebook with space.ipynb') |
|
|||
79 | self.assertEqual(path, 'notebook%20with%20space.ipynb') |
|
|||
80 |
|
||||
81 | path = nm.url_encode('/path with a/notebook and space.ipynb') |
|
|||
82 | self.assertEqual(path, '/path%20with%20a/notebook%20and%20space.ipynb') |
|
|||
83 |
|
||||
84 | path = nm.url_encode('/ !@$#%^&* / test %^ notebook @#$ name.ipynb') |
|
|||
85 | self.assertEqual(path, |
|
|||
86 | '/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb') |
|
|||
87 |
|
||||
88 | def test_url_decode(self): |
|
|||
89 | nm = NotebookManager() |
|
|||
90 |
|
||||
91 | # decodes a url string to a plain string |
|
|||
92 | # these tests decode paths with spaces |
|
|||
93 | path = nm.url_decode('/this%20is%20a%20test/for%20spaces/') |
|
|||
94 | self.assertEqual(path, '/this is a test/for spaces/') |
|
|||
95 |
|
||||
96 | path = nm.url_decode('notebook%20with%20space.ipynb') |
|
|||
97 | self.assertEqual(path, 'notebook with space.ipynb') |
|
|||
98 |
|
||||
99 | path = nm.url_decode('/path%20with%20a/notebook%20and%20space.ipynb') |
|
|||
100 | self.assertEqual(path, '/path with a/notebook and space.ipynb') |
|
|||
101 |
|
||||
102 | path = nm.url_decode( |
|
|||
103 | '/%20%21%40%24%23%25%5E%26%2A%20/%20test%20%25%5E%20notebook%20%40%23%24%20name.ipynb') |
|
|||
104 | self.assertEqual(path, '/ !@$#%^&* / test %^ notebook @#$ name.ipynb') |
|
|||
105 |
|
||||
106 | def test_create_notebook_model(self): |
|
70 | def test_create_notebook_model(self): | |
107 | with TemporaryDirectory() as td: |
|
71 | with TemporaryDirectory() as td: | |
108 | # Test in root directory |
|
72 | # Test in root directory | |
109 | nm = FileNotebookManager(notebook_dir=td) |
|
73 | nm = FileNotebookManager(notebook_dir=td) | |
110 | model = nm.create_notebook_model() |
|
74 | model = nm.create_notebook_model() | |
111 | assert isinstance(model, dict) |
|
75 | assert isinstance(model, dict) | |
112 | self.assertIn('name', model) |
|
76 | self.assertIn('name', model) | |
113 | self.assertIn('path', model) |
|
77 | self.assertIn('path', model) | |
114 | self.assertEqual(model['name'], 'Untitled0.ipynb') |
|
78 | self.assertEqual(model['name'], 'Untitled0.ipynb') | |
115 | self.assertEqual(model['path'], '/') |
|
79 | self.assertEqual(model['path'], '/') | |
116 |
|
80 | |||
117 | # Test in sub-directory |
|
81 | # Test in sub-directory | |
118 | sub_dir = '/foo/' |
|
82 | sub_dir = '/foo/' | |
119 | self.make_dir(nm.notebook_dir, 'foo') |
|
83 | self.make_dir(nm.notebook_dir, 'foo') | |
120 | model = nm.create_notebook_model(None, sub_dir) |
|
84 | model = nm.create_notebook_model(None, sub_dir) | |
121 | assert isinstance(model, dict) |
|
85 | assert isinstance(model, dict) | |
122 | self.assertIn('name', model) |
|
86 | self.assertIn('name', model) | |
123 | self.assertIn('path', model) |
|
87 | self.assertIn('path', model) | |
124 | self.assertEqual(model['name'], 'Untitled0.ipynb') |
|
88 | self.assertEqual(model['name'], 'Untitled0.ipynb') | |
125 | self.assertEqual(model['path'], sub_dir) |
|
89 | self.assertEqual(model['path'], sub_dir) | |
126 |
|
90 | |||
127 | def test_get_notebook_model(self): |
|
91 | def test_get_notebook_model(self): | |
128 | with TemporaryDirectory() as td: |
|
92 | with TemporaryDirectory() as td: | |
129 | # Test in root directory |
|
93 | # Test in root directory | |
130 | # Create a notebook |
|
94 | # Create a notebook | |
131 | nm = FileNotebookManager(notebook_dir=td) |
|
95 | nm = FileNotebookManager(notebook_dir=td) | |
132 | model = nm.create_notebook_model() |
|
96 | model = nm.create_notebook_model() | |
133 | name = model['name'] |
|
97 | name = model['name'] | |
134 | path = model['path'] |
|
98 | path = model['path'] | |
135 |
|
99 | |||
136 | # Check that we 'get' on the notebook we just created |
|
100 | # Check that we 'get' on the notebook we just created | |
137 | model2 = nm.get_notebook_model(name, path) |
|
101 | model2 = nm.get_notebook_model(name, path) | |
138 | assert isinstance(model2, dict) |
|
102 | assert isinstance(model2, dict) | |
139 | self.assertIn('name', model2) |
|
103 | self.assertIn('name', model2) | |
140 | self.assertIn('path', model2) |
|
104 | self.assertIn('path', model2) | |
141 | self.assertEqual(model['name'], name) |
|
105 | self.assertEqual(model['name'], name) | |
142 | self.assertEqual(model['path'], path) |
|
106 | self.assertEqual(model['path'], path) | |
143 |
|
107 | |||
144 | # Test in sub-directory |
|
108 | # Test in sub-directory | |
145 | sub_dir = '/foo/' |
|
109 | sub_dir = '/foo/' | |
146 | self.make_dir(nm.notebook_dir, 'foo') |
|
110 | self.make_dir(nm.notebook_dir, 'foo') | |
147 | model = nm.create_notebook_model(None, sub_dir) |
|
111 | model = nm.create_notebook_model(None, sub_dir) | |
148 | model2 = nm.get_notebook_model(name, sub_dir) |
|
112 | model2 = nm.get_notebook_model(name, sub_dir) | |
149 | assert isinstance(model2, dict) |
|
113 | assert isinstance(model2, dict) | |
150 | self.assertIn('name', model2) |
|
114 | self.assertIn('name', model2) | |
151 | self.assertIn('path', model2) |
|
115 | self.assertIn('path', model2) | |
152 | self.assertIn('content', model2) |
|
116 | self.assertIn('content', model2) | |
153 | self.assertEqual(model2['name'], 'Untitled0.ipynb') |
|
117 | self.assertEqual(model2['name'], 'Untitled0.ipynb') | |
154 | self.assertEqual(model2['path'], sub_dir) |
|
118 | self.assertEqual(model2['path'], sub_dir) | |
155 |
|
119 | |||
156 | def test_update_notebook_model(self): |
|
120 | def test_update_notebook_model(self): | |
157 | with TemporaryDirectory() as td: |
|
121 | with TemporaryDirectory() as td: | |
158 | # Test in root directory |
|
122 | # Test in root directory | |
159 | # Create a notebook |
|
123 | # Create a notebook | |
160 | nm = FileNotebookManager(notebook_dir=td) |
|
124 | nm = FileNotebookManager(notebook_dir=td) | |
161 | model = nm.create_notebook_model() |
|
125 | model = nm.create_notebook_model() | |
162 | name = model['name'] |
|
126 | name = model['name'] | |
163 | path = model['path'] |
|
127 | path = model['path'] | |
164 |
|
128 | |||
165 | # Change the name in the model for rename |
|
129 | # Change the name in the model for rename | |
166 | model['name'] = 'test.ipynb' |
|
130 | model['name'] = 'test.ipynb' | |
167 | model = nm.update_notebook_model(model, name, path) |
|
131 | model = nm.update_notebook_model(model, name, path) | |
168 | assert isinstance(model, dict) |
|
132 | assert isinstance(model, dict) | |
169 | self.assertIn('name', model) |
|
133 | self.assertIn('name', model) | |
170 | self.assertIn('path', model) |
|
134 | self.assertIn('path', model) | |
171 | self.assertEqual(model['name'], 'test.ipynb') |
|
135 | self.assertEqual(model['name'], 'test.ipynb') | |
172 |
|
136 | |||
173 | # Make sure the old name is gone |
|
137 | # Make sure the old name is gone | |
174 | self.assertRaises(HTTPError, nm.get_notebook_model, name, path) |
|
138 | self.assertRaises(HTTPError, nm.get_notebook_model, name, path) | |
175 |
|
139 | |||
176 | # Test in sub-directory |
|
140 | # Test in sub-directory | |
177 | # Create a directory and notebook in that directory |
|
141 | # Create a directory and notebook in that directory | |
178 | sub_dir = '/foo/' |
|
142 | sub_dir = '/foo/' | |
179 | self.make_dir(nm.notebook_dir, 'foo') |
|
143 | self.make_dir(nm.notebook_dir, 'foo') | |
180 | model = nm.create_notebook_model(None, sub_dir) |
|
144 | model = nm.create_notebook_model(None, sub_dir) | |
181 | name = model['name'] |
|
145 | name = model['name'] | |
182 | path = model['path'] |
|
146 | path = model['path'] | |
183 |
|
147 | |||
184 | # Change the name in the model for rename |
|
148 | # Change the name in the model for rename | |
185 | model['name'] = 'test_in_sub.ipynb' |
|
149 | model['name'] = 'test_in_sub.ipynb' | |
186 | model = nm.update_notebook_model(model, name, path) |
|
150 | model = nm.update_notebook_model(model, name, path) | |
187 | assert isinstance(model, dict) |
|
151 | assert isinstance(model, dict) | |
188 | self.assertIn('name', model) |
|
152 | self.assertIn('name', model) | |
189 | self.assertIn('path', model) |
|
153 | self.assertIn('path', model) | |
190 | self.assertEqual(model['name'], 'test_in_sub.ipynb') |
|
154 | self.assertEqual(model['name'], 'test_in_sub.ipynb') | |
191 | self.assertEqual(model['path'], sub_dir) |
|
155 | self.assertEqual(model['path'], sub_dir) | |
192 |
|
156 | |||
193 | # Make sure the old name is gone |
|
157 | # Make sure the old name is gone | |
194 | self.assertRaises(HTTPError, nm.get_notebook_model, name, path) |
|
158 | self.assertRaises(HTTPError, nm.get_notebook_model, name, path) | |
195 |
|
159 | |||
196 | def test_save_notebook_model(self): |
|
160 | def test_save_notebook_model(self): | |
197 | with TemporaryDirectory() as td: |
|
161 | with TemporaryDirectory() as td: | |
198 | # Test in the root directory |
|
162 | # Test in the root directory | |
199 | # Create a notebook |
|
163 | # Create a notebook | |
200 | nm = FileNotebookManager(notebook_dir=td) |
|
164 | nm = FileNotebookManager(notebook_dir=td) | |
201 | model = nm.create_notebook_model() |
|
165 | model = nm.create_notebook_model() | |
202 | name = model['name'] |
|
166 | name = model['name'] | |
203 | path = model['path'] |
|
167 | path = model['path'] | |
204 |
|
168 | |||
205 | # Get the model with 'content' |
|
169 | # Get the model with 'content' | |
206 | full_model = nm.get_notebook_model(name, path) |
|
170 | full_model = nm.get_notebook_model(name, path) | |
207 |
|
171 | |||
208 | # Save the notebook |
|
172 | # Save the notebook | |
209 | model = nm.save_notebook_model(full_model, name, path) |
|
173 | model = nm.save_notebook_model(full_model, name, path) | |
210 | assert isinstance(model, dict) |
|
174 | assert isinstance(model, dict) | |
211 | self.assertIn('name', model) |
|
175 | self.assertIn('name', model) | |
212 | self.assertIn('path', model) |
|
176 | self.assertIn('path', model) | |
213 | self.assertEqual(model['name'], name) |
|
177 | self.assertEqual(model['name'], name) | |
214 | self.assertEqual(model['path'], path) |
|
178 | self.assertEqual(model['path'], path) | |
215 |
|
179 | |||
216 | # Test in sub-directory |
|
180 | # Test in sub-directory | |
217 | # Create a directory and notebook in that directory |
|
181 | # Create a directory and notebook in that directory | |
218 | sub_dir = '/foo/' |
|
182 | sub_dir = '/foo/' | |
219 | self.make_dir(nm.notebook_dir, 'foo') |
|
183 | self.make_dir(nm.notebook_dir, 'foo') | |
220 | model = nm.create_notebook_model(None, sub_dir) |
|
184 | model = nm.create_notebook_model(None, sub_dir) | |
221 | name = model['name'] |
|
185 | name = model['name'] | |
222 | path = model['path'] |
|
186 | path = model['path'] | |
223 | model = nm.get_notebook_model(name, path) |
|
187 | model = nm.get_notebook_model(name, path) | |
224 |
|
188 | |||
225 | # Change the name in the model for rename |
|
189 | # Change the name in the model for rename | |
226 | model = nm.save_notebook_model(model, name, path) |
|
190 | model = nm.save_notebook_model(model, name, path) | |
227 | assert isinstance(model, dict) |
|
191 | assert isinstance(model, dict) | |
228 | self.assertIn('name', model) |
|
192 | self.assertIn('name', model) | |
229 | self.assertIn('path', model) |
|
193 | self.assertIn('path', model) | |
230 | self.assertEqual(model['name'], 'Untitled0.ipynb') |
|
194 | self.assertEqual(model['name'], 'Untitled0.ipynb') | |
231 | self.assertEqual(model['path'], sub_dir) |
|
195 | self.assertEqual(model['path'], sub_dir) | |
232 |
|
196 | |||
233 | def test_delete_notebook_model(self): |
|
197 | def test_delete_notebook_model(self): | |
234 | with TemporaryDirectory() as td: |
|
198 | with TemporaryDirectory() as td: | |
235 | # Test in the root directory |
|
199 | # Test in the root directory | |
236 | # Create a notebook |
|
200 | # Create a notebook | |
237 | nm = FileNotebookManager(notebook_dir=td) |
|
201 | nm = FileNotebookManager(notebook_dir=td) | |
238 | model = nm.create_notebook_model() |
|
202 | model = nm.create_notebook_model() | |
239 | name = model['name'] |
|
203 | name = model['name'] | |
240 | path = model['path'] |
|
204 | path = model['path'] | |
241 |
|
205 | |||
242 | # Delete the notebook |
|
206 | # Delete the notebook | |
243 | nm.delete_notebook_model(name, path) |
|
207 | nm.delete_notebook_model(name, path) | |
244 |
|
208 | |||
245 | # Check that a 'get' on the deleted notebook raises and error |
|
209 | # Check that a 'get' on the deleted notebook raises and error | |
246 | self.assertRaises(HTTPError, nm.get_notebook_model, name, path) |
|
210 | self.assertRaises(HTTPError, nm.get_notebook_model, name, path) |
@@ -1,51 +1,66 b'' | |||||
1 | """Notebook related utilities |
|
1 | """Notebook related utilities | |
2 |
|
2 | |||
3 | Authors: |
|
3 | Authors: | |
4 |
|
4 | |||
5 | * Brian Granger |
|
5 | * Brian Granger | |
6 | """ |
|
6 | """ | |
7 |
|
7 | |||
8 | #----------------------------------------------------------------------------- |
|
8 | #----------------------------------------------------------------------------- | |
9 | # Copyright (C) 2011 The IPython Development Team |
|
9 | # Copyright (C) 2011 The IPython Development Team | |
10 | # |
|
10 | # | |
11 | # Distributed under the terms of the BSD License. The full license is in |
|
11 | # Distributed under the terms of the BSD License. The full license is in | |
12 | # the file COPYING, distributed as part of this software. |
|
12 | # the file COPYING, distributed as part of this software. | |
13 | #----------------------------------------------------------------------------- |
|
13 | #----------------------------------------------------------------------------- | |
14 |
|
14 | |||
15 | import os |
|
15 | import os | |
16 | from urllib import quote, unquote |
|
16 | from urllib import quote, unquote | |
17 |
|
17 | |||
18 | #----------------------------------------------------------------------------- |
|
18 | #----------------------------------------------------------------------------- | |
19 | # Imports |
|
19 | # Imports | |
20 | #----------------------------------------------------------------------------- |
|
20 | #----------------------------------------------------------------------------- | |
21 |
|
21 | |||
22 | def url_path_join(*pieces): |
|
22 | def url_path_join(*pieces): | |
23 | """Join components of url into a relative url |
|
23 | """Join components of url into a relative url | |
24 |
|
24 | |||
25 | Use to prevent double slash when joining subpath. This will leave the |
|
25 | Use to prevent double slash when joining subpath. This will leave the | |
26 | initial and final / in place |
|
26 | initial and final / in place | |
27 | """ |
|
27 | """ | |
28 | initial = pieces[0].startswith('/') |
|
28 | initial = pieces[0].startswith('/') | |
29 | final = pieces[-1].endswith('/') |
|
29 | final = pieces[-1].endswith('/') | |
30 | stripped = [s.strip('/') for s in pieces] |
|
30 | stripped = [s.strip('/') for s in pieces] | |
31 | result = '/'.join(s for s in stripped if s) |
|
31 | result = '/'.join(s for s in stripped if s) | |
32 | if initial: result = '/' + result |
|
32 | if initial: result = '/' + result | |
33 | if final: result = result + '/' |
|
33 | if final: result = result + '/' | |
34 | if result == '//': result = '/' |
|
34 | if result == '//': result = '/' | |
35 | return result |
|
35 | return result | |
36 |
|
36 | |||
37 | def path2url(path): |
|
37 | def path2url(path): | |
38 | """Convert a local file path to a URL""" |
|
38 | """Convert a local file path to a URL""" | |
39 | pieces = [ quote(p) for p in path.split(os.path.sep) ] |
|
39 | pieces = [ quote(p) for p in path.split(os.path.sep) ] | |
40 | # preserve trailing / |
|
40 | # preserve trailing / | |
41 | if pieces[-1] == '': |
|
41 | if pieces[-1] == '': | |
42 | pieces[-1] = '/' |
|
42 | pieces[-1] = '/' | |
43 | url = url_path_join(*pieces) |
|
43 | url = url_path_join(*pieces) | |
44 | return url |
|
44 | return url | |
45 |
|
45 | |||
46 | def url2path(url): |
|
46 | def url2path(url): | |
47 | """Convert a URL to a local file path""" |
|
47 | """Convert a URL to a local file path""" | |
48 | pieces = [ unquote(p) for p in url.split('/') ] |
|
48 | pieces = [ unquote(p) for p in url.split('/') ] | |
49 | path = os.path.join(*pieces) |
|
49 | path = os.path.join(*pieces) | |
50 | return path |
|
50 | return path | |
51 |
|
51 | |||
|
52 | def url_escape(path): | |||
|
53 | """Escape special characters in a URL path | |||
|
54 | ||||
|
55 | Turns '/foo bar/' into '/foo%20bar/' | |||
|
56 | """ | |||
|
57 | parts = path.split('/') | |||
|
58 | return '/'.join([quote(p) for p in parts]) | |||
|
59 | ||||
|
60 | def url_unescape(path): | |||
|
61 | """Unescape special characters in a URL path | |||
|
62 | ||||
|
63 | Turns '/foo%20bar/' into '/foo bar/' | |||
|
64 | """ | |||
|
65 | return '/'.join([unquote(p) for p in path.split('/')]) | |||
|
66 |
General Comments 0
You need to be logged in to leave comments.
Login now