##// END OF EJS Templates
simplified named_notebook_path implementation...
Paul Ivanov -
Show More
@@ -1,271 +1,263 b''
1 1 """A base class notebook manager.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 import os
20 20 import uuid
21 21
22 22 from tornado import web
23 23 from urllib import quote, unquote
24 24
25 25 from IPython.config.configurable import LoggingConfigurable
26 26 from IPython.nbformat import current
27 27 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
28 28
29 29 #-----------------------------------------------------------------------------
30 30 # Classes
31 31 #-----------------------------------------------------------------------------
32 32
33 33 class NotebookManager(LoggingConfigurable):
34 34
35 35 # Todo:
36 36 # The notebook_dir attribute is used to mean a couple of different things:
37 37 # 1. Where the notebooks are stored if FileNotebookManager is used.
38 38 # 2. The cwd of the kernel for a project.
39 39 # Right now we use this attribute in a number of different places and
40 40 # we are going to have to disentangle all of this.
41 41 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
42 42 The directory to use for notebooks.
43 43 """)
44 44
45 45 def named_notebook_path(self, notebook_path):
46 46 """Given a notebook_path name, returns a (name, path) tuple, where
47 47 name is a .ipynb file, and path is the directory for the file.
48 48
49 49 Parameters
50 50 ----------
51 51 notebook_path : string
52 52 A path that may be a .ipynb name or a directory
53 53
54 54 Returns
55 55 -------
56 56 name : string or None
57 the filename of the notebook, or None if not a .ipynb extesnsion
58 path : string or None
57 the filename of the notebook, or None if not a .ipynb extension
58 path : string
59 59 the path to the directory which contains the notebook
60 60 """
61 61 names = notebook_path.split('/')
62 if len(names) > 1:
62
63 63 name = names[-1]
64 64 if name.endswith(".ipynb"):
65 65 name = name
66 path = notebook_path[:-1]+'/'
67 else:
68 name = None
69 path = notebook_path+'/'
70 else:
71 name = names[0]
72 if name.endswith(".ipynb"):
73 name = name
74 path = None
66 path = "/".join(names[:-1]) + '/'
75 67 else:
76 68 name = None
77 path = notebook_path+'/'
69 path = "/".join(names) + '/'
78 70 return name, path
79 71
80 72 def url_encode(self, path):
81 73 parts = path.split('/')
82 74 return os.path.join(*[quote(p) for p in parts])
83 75
84 76 def url_decode(self, path):
85 77 parts = path.split('/')
86 78 return os.path.join(*[unquote(p) for p in parts])
87 79
88 80 def _notebook_dir_changed(self, new):
89 81 """do a bit of validation of the notebook dir"""
90 82 if not os.path.isabs(new):
91 83 # If we receive a non-absolute path, make it absolute.
92 84 abs_new = os.path.abspath(new)
93 85 #self.notebook_dir = os.path.dirname(abs_new)
94 86 return
95 87 if os.path.exists(new) and not os.path.isdir(new):
96 88 raise TraitError("notebook dir %r is not a directory" % new)
97 89 if not os.path.exists(new):
98 90 self.log.info("Creating notebook dir %s", new)
99 91 try:
100 92 os.mkdir(new)
101 93 except:
102 94 raise TraitError("Couldn't create notebook dir %r" % new)
103 95
104 96 allowed_formats = List([u'json',u'py'])
105 97
106 98 def add_new_folder(self, path=None):
107 99 new_path = os.path.join(self.notebook_dir, path)
108 100 if not os.path.exists(new_path):
109 101 os.makedirs(new_path)
110 102 else:
111 103 raise web.HTTPError(409, u'Directory already exists or creation permission not allowed.')
112 104
113 105 def load_notebook_names(self, path):
114 106 """Load the notebook names into memory.
115 107
116 108 This should be called once immediately after the notebook manager
117 109 is created to load the existing notebooks into the mapping in
118 110 memory.
119 111 """
120 112 self.list_notebooks(path)
121 113
122 114 def list_notebooks(self):
123 115 """List all notebooks.
124 116
125 117 This returns a list of dicts, each of the form::
126 118
127 119 dict(notebook_id=notebook,name=name)
128 120
129 121 This list of dicts should be sorted by name::
130 122
131 123 data = sorted(data, key=lambda item: item['name'])
132 124 """
133 125 raise NotImplementedError('must be implemented in a subclass')
134 126
135 127
136 128 def notebook_exists(self, notebook_path):
137 129 """Does a notebook exist?"""
138 130
139 131
140 132 def notebook_model(self, notebook_name, notebook_path=None, content=True):
141 133 """ Creates the standard notebook model """
142 134 last_modified, contents = self.read_notebook_object(notebook_name, notebook_path)
143 135 model = {"name": notebook_name,
144 136 "path": notebook_path,
145 137 "last_modified (UTC)": last_modified.ctime()}
146 138 if content == True:
147 139 model['content'] = contents
148 140 return model
149 141
150 142 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
151 143 """Get the representation of a notebook in format by notebook_name."""
152 144 format = unicode(format)
153 145 if format not in self.allowed_formats:
154 146 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
155 147 kwargs = {}
156 148 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
157 149 if format == 'json':
158 150 # don't split lines for sending over the wire, because it
159 151 # should match the Python in-memory format.
160 152 kwargs['split_lines'] = False
161 153 representation = current.writes(nb, format, **kwargs)
162 154 name = nb.metadata.get('name', 'notebook')
163 155 return last_mod, representation, name
164 156
165 157 def read_notebook_object(self, notebook_name, notebook_path=None):
166 158 """Get the object representation of a notebook by notebook_id."""
167 159 raise NotImplementedError('must be implemented in a subclass')
168 160
169 161 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
170 162 """Save a new notebook and return its name.
171 163
172 164 If a name is passed in, it overrides any values in the notebook data
173 165 and the value in the data is updated to use that value.
174 166 """
175 167 if format not in self.allowed_formats:
176 168 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
177 169
178 170 try:
179 171 nb = current.reads(data.decode('utf-8'), format)
180 172 except:
181 173 raise web.HTTPError(400, u'Invalid JSON data')
182 174
183 175 if name is None:
184 176 try:
185 177 name = nb.metadata.name
186 178 except AttributeError:
187 179 raise web.HTTPError(400, u'Missing notebook name')
188 180 nb.metadata.name = name
189 181
190 182 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
191 183 return notebook_name
192 184
193 185 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
194 186 """Save an existing notebook by notebook_name."""
195 187 if format not in self.allowed_formats:
196 188 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
197 189
198 190 try:
199 191 nb = current.reads(data.decode('utf-8'), format)
200 192 except:
201 193 raise web.HTTPError(400, u'Invalid JSON data')
202 194
203 195 if name is not None:
204 196 nb.metadata.name = name
205 197 self.write_notebook_object(nb, name, notebook_path, new_name)
206 198
207 199 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
208 200 """Write a notebook object and return its notebook_name.
209 201
210 202 If notebook_name is None, this method should create a new notebook_name.
211 203 If notebook_name is not None, this method should check to make sure it
212 204 exists and is valid.
213 205 """
214 206 raise NotImplementedError('must be implemented in a subclass')
215 207
216 208 def delete_notebook(self, notebook_name, notebook_path):
217 209 """Delete notebook by notebook_id."""
218 210 raise NotImplementedError('must be implemented in a subclass')
219 211
220 212 def increment_filename(self, name):
221 213 """Increment a filename to make it unique.
222 214
223 215 This exists for notebook stores that must have unique names. When a notebook
224 216 is created or copied this method constructs a unique filename, typically
225 217 by appending an integer to the name.
226 218 """
227 219 return name
228 220
229 221 def new_notebook(self, notebook_path=None):
230 222 """Create a new notebook and return its notebook_name."""
231 223 name = self.increment_filename('Untitled', notebook_path)
232 224 metadata = current.new_metadata(name=name)
233 225 nb = current.new_notebook(metadata=metadata)
234 226 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
235 227 return notebook_name
236 228
237 229 def copy_notebook(self, name, path=None):
238 230 """Copy an existing notebook and return its new notebook_name."""
239 231 last_mod, nb = self.read_notebook_object(name, path)
240 232 name = nb.metadata.name + '-Copy'
241 233 name = self.increment_filename(name, path)
242 234 nb.metadata.name = name
243 235 notebook_name = self.write_notebook_object(nb, notebook_path = path)
244 236 return notebook_name
245 237
246 238 # Checkpoint-related
247 239
248 240 def create_checkpoint(self, notebook_name, notebook_path=None):
249 241 """Create a checkpoint of the current state of a notebook
250 242
251 243 Returns a checkpoint_id for the new checkpoint.
252 244 """
253 245 raise NotImplementedError("must be implemented in a subclass")
254 246
255 247 def list_checkpoints(self, notebook_name, notebook_path=None):
256 248 """Return a list of checkpoints for a given notebook"""
257 249 return []
258 250
259 251 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
260 252 """Restore a notebook from one of its checkpoints"""
261 253 raise NotImplementedError("must be implemented in a subclass")
262 254
263 255 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
264 256 """delete a checkpoint for a notebook"""
265 257 raise NotImplementedError("must be implemented in a subclass")
266 258
267 259 def log_info(self):
268 260 self.log.info(self.info_string())
269 261
270 262 def info_string(self):
271 263 return "Serving notebooks"
@@ -1,52 +1,59 b''
1 1 """Tests for the notebook manager."""
2 2
3 3 import os
4 4 from unittest import TestCase
5 5 from tempfile import NamedTemporaryFile
6 6
7 7 from IPython.utils.tempdir import TemporaryDirectory
8 8 from IPython.utils.traitlets import TraitError
9 9
10 10 from ..filenbmanager import FileNotebookManager
11 11 from ..nbmanager import NotebookManager
12 12
13 13 class TestFileNotebookManager(TestCase):
14 14
15 15 def test_nb_dir(self):
16 16 with TemporaryDirectory() as td:
17 17 km = FileNotebookManager(notebook_dir=td)
18 18 self.assertEqual(km.notebook_dir, td)
19 19
20 20 def test_create_nb_dir(self):
21 21 with TemporaryDirectory() as td:
22 22 nbdir = os.path.join(td, 'notebooks')
23 23 km = FileNotebookManager(notebook_dir=nbdir)
24 24 self.assertEqual(km.notebook_dir, nbdir)
25 25
26 26 def test_missing_nb_dir(self):
27 27 with TemporaryDirectory() as td:
28 28 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
29 29 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir)
30 30
31 31 def test_invalid_nb_dir(self):
32 32 with NamedTemporaryFile() as tf:
33 33 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name)
34 34
35 35 class TestNotebookManager(TestCase):
36 36 def test_named_notebook_path(self):
37 37 nm = NotebookManager()
38 38
39 39 # doesn't end with ipynb, should just be path
40 40 name, path = nm.named_notebook_path('hello')
41 41 self.assertEqual(name, None)
42 42 self.assertEqual(path, 'hello/')
43 43
44 name, path = nm.named_notebook_path('/')
45 self.assertEqual(name, None)
46
44 47 name, path = nm.named_notebook_path('hello.ipynb')
45 48 self.assertEqual(name, 'hello.ipynb')
46 self.assertEqual(path, None)
49 self.assertEqual(path, '/')
50
51 name, path = nm.named_notebook_path('/hello.ipynb')
52 self.assertEqual(name, 'hello.ipynb')
53 self.assertEqual(path, '/')
47 54
48 55 name, path = nm.named_notebook_path('/this/is/a/path/hello.ipynb')
49 56 self.assertEqual(name, 'hello.ipynb')
50 57 self.assertEqual(path, '/this/is/a/path/')
51 58
52 59
General Comments 0
You need to be logged in to leave comments. Login now