##// END OF EJS Templates
move url_[un]escape to utils from nbm
MinRK -
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 = nbm.url_encode(name)
67 name = url_escape(name)
68 path = nbm.url_encode(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