##// END OF EJS Templates
Merge pull request #2045 from ellisonbg/azurenb...
Brian E. Granger -
r8186:c8c254cc merge
parent child Browse files
Show More
@@ -0,0 +1,143 b''
1 """A notebook manager that uses Azure blob storage.
2
3 Authors:
4
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2012 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
19 import datetime
20
21 import azure
22 from azure.storage import BlobService
23
24 from tornado import web
25
26 from .basenbmanager import BaseNotebookManager
27 from IPython.nbformat import current
28 from IPython.utils.traitlets import Unicode, Instance
29
30
31 #-----------------------------------------------------------------------------
32 # Classes
33 #-----------------------------------------------------------------------------
34
35 class AzureNotebookManager(BaseNotebookManager):
36
37 account_name = Unicode('', config=True, help='Azure storage account name.')
38 account_key = Unicode('', config=True, help='Azure storage account key.')
39 container = Unicode('', config=True, help='Container name for notebooks.')
40
41 blob_service_host_base = Unicode('.blob.core.windows.net', config=True,
42 help='The basename for the blob service URL. If running on the preview site this '
43 'will be .blob.core.azure-preview.com.')
44 def _blob_service_host_base_changed(self, new):
45 self._update_service_host_base(new)
46
47 blob_service = Instance('azure.storage.BlobService')
48 def _blob_service_default(self):
49 return BlobService(account_name=self.account_name, account_key=self.account_key)
50
51 def __init__(self, **kwargs):
52 super(AzureNotebookManager, self).__init__(**kwargs)
53 self._update_service_host_base(self.blob_service_host_base)
54 self._create_container()
55
56 def _update_service_host_base(self, shb):
57 azure.BLOB_SERVICE_HOST_BASE = shb
58
59 def _create_container(self):
60 self.blob_service.create_container(self.container)
61
62 def load_notebook_names(self):
63 """On startup load the notebook ids and names from Azure.
64
65 The blob names are the notebook ids and the notebook names are stored
66 as blob metadata.
67 """
68 self.mapping = {}
69 blobs = self.blob_service.list_blobs(self.container)
70 ids = [blob.name for blob in blobs]
71
72 for id in ids:
73 md = self.blob_service.get_blob_metadata(self.container, id)
74 name = md['x-ms-meta-nbname']
75 self.mapping[id] = name
76
77 def list_notebooks(self):
78 """List all notebooks in the container.
79
80 This version uses `self.mapping` as the authoritative notebook list.
81 """
82 data = [dict(notebook_id=id,name=name) for id, name in self.mapping.items()]
83 data = sorted(data, key=lambda item: item['name'])
84 return data
85
86 def read_notebook_object(self, notebook_id):
87 """Get the object representation of a notebook by notebook_id."""
88 if not self.notebook_exists(notebook_id):
89 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
90 try:
91 s = self.blob_service.get_blob(self.container, notebook_id)
92 except:
93 raise web.HTTPError(500, u'Notebook cannot be read.')
94 try:
95 # v1 and v2 and json in the .ipynb files.
96 nb = current.reads(s, u'json')
97 except:
98 raise web.HTTPError(500, u'Unreadable JSON notebook.')
99 # Todo: The last modified should actually be saved in the notebook document.
100 # We are just using the current datetime until that is implemented.
101 last_modified = datetime.datetime.utcnow()
102 return last_modified, nb
103
104 def write_notebook_object(self, nb, notebook_id=None):
105 """Save an existing notebook object by notebook_id."""
106 try:
107 new_name = nb.metadata.name
108 except AttributeError:
109 raise web.HTTPError(400, u'Missing notebook name')
110
111 if notebook_id is None:
112 notebook_id = self.new_notebook_id(new_name)
113
114 if notebook_id not in self.mapping:
115 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
116
117 try:
118 data = current.writes(nb, u'json')
119 except Exception as e:
120 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
121
122 metadata = {'nbname': new_name}
123 try:
124 self.blob_service.put_blob(self.container, notebook_id, data, 'BlockBlob', x_ms_meta_name_values=metadata)
125 except Exception as e:
126 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
127
128 self.mapping[notebook_id] = new_name
129 return notebook_id
130
131 def delete_notebook(self, notebook_id):
132 """Delete notebook by notebook_id."""
133 if not self.notebook_exists(notebook_id):
134 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
135 try:
136 self.blob_service.delete_blob(self.container, notebook_id)
137 except Exception as e:
138 raise web.HTTPError(400, u'Unexpected error while deleting notebook: %s' % e)
139 else:
140 self.delete_notebook_id(notebook_id)
141
142 def log_info(self):
143 self.log.info("Serving notebooks from Azure storage: %s, %s", self.account_name, self.container)
@@ -0,0 +1,205 b''
1 """A base class notebook manager.
2
3 Authors:
4
5 * Brian Granger
6 """
7
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
10 #
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
14
15 #-----------------------------------------------------------------------------
16 # Imports
17 #-----------------------------------------------------------------------------
18
19 import os
20 import uuid
21
22 from tornado import web
23
24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.nbformat import current
26 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
27
28 #-----------------------------------------------------------------------------
29 # Classes
30 #-----------------------------------------------------------------------------
31
32 class BaseNotebookManager(LoggingConfigurable):
33
34 # Todo:
35 # The notebook_dir attribute is used to mean a couple of different things:
36 # 1. Where the notebooks are stored if FileNotebookManager is used.
37 # 2. The cwd of the kernel for a project.
38 # Right now we use this attribute in a number of different places and
39 # we are going to have to disentagle all of this.
40 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
41 The directory to use for notebooks.
42 """)
43 def _notebook_dir_changed(self, name, old, new):
44 """do a bit of validation of the notebook dir"""
45 if os.path.exists(new) and not os.path.isdir(new):
46 raise TraitError("notebook dir %r is not a directory" % new)
47 if not os.path.exists(new):
48 self.log.info("Creating notebook dir %s", new)
49 try:
50 os.mkdir(new)
51 except:
52 raise TraitError("Couldn't create notebook dir %r" % new)
53
54 allowed_formats = List([u'json',u'py'])
55
56 # Map notebook_ids to notebook names
57 mapping = Dict()
58
59 def load_notebook_names(self):
60 """Load the notebook names into memory.
61
62 This should be called once immediately after the notebook manager
63 is created to load the existing notebooks into the mapping in
64 memory.
65 """
66 self.list_notebooks()
67
68 def list_notebooks(self):
69 """List all notebooks.
70
71 This returns a list of dicts, each of the form::
72
73 dict(notebook_id=notebook,name=name)
74
75 This list of dicts should be sorted by name::
76
77 data = sorted(data, key=lambda item: item['name'])
78 """
79 raise NotImplementedError('must be implemented in a subclass')
80
81
82 def new_notebook_id(self, name):
83 """Generate a new notebook_id for a name and store its mapping."""
84 # TODO: the following will give stable urls for notebooks, but unless
85 # the notebooks are immediately redirected to their new urls when their
86 # filemname changes, nasty inconsistencies result. So for now it's
87 # disabled and instead we use a random uuid4() call. But we leave the
88 # logic here so that we can later reactivate it, whhen the necessary
89 # url redirection code is written.
90 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
91 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
92
93 notebook_id = unicode(uuid.uuid4())
94 self.mapping[notebook_id] = name
95 return notebook_id
96
97 def delete_notebook_id(self, notebook_id):
98 """Delete a notebook's id in the mapping.
99
100 This doesn't delete the actual notebook, only its entry in the mapping.
101 """
102 del self.mapping[notebook_id]
103
104 def notebook_exists(self, notebook_id):
105 """Does a notebook exist?"""
106 return notebook_id in self.mapping
107
108 def get_notebook(self, notebook_id, format=u'json'):
109 """Get the representation of a notebook in format by notebook_id."""
110 format = unicode(format)
111 if format not in self.allowed_formats:
112 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
113 last_modified, nb = self.read_notebook_object(notebook_id)
114 kwargs = {}
115 if format == 'json':
116 # don't split lines for sending over the wire, because it
117 # should match the Python in-memory format.
118 kwargs['split_lines'] = False
119 data = current.writes(nb, format, **kwargs)
120 name = nb.get('name','notebook')
121 return last_modified, name, data
122
123 def read_notebook_object(self, notebook_id):
124 """Get the object representation of a notebook by notebook_id."""
125 raise NotImplementedError('must be implemented in a subclass')
126
127 def save_new_notebook(self, data, name=None, format=u'json'):
128 """Save a new notebook and return its notebook_id.
129
130 If a name is passed in, it overrides any values in the notebook data
131 and the value in the data is updated to use that value.
132 """
133 if format not in self.allowed_formats:
134 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
135
136 try:
137 nb = current.reads(data.decode('utf-8'), format)
138 except:
139 raise web.HTTPError(400, u'Invalid JSON data')
140
141 if name is None:
142 try:
143 name = nb.metadata.name
144 except AttributeError:
145 raise web.HTTPError(400, u'Missing notebook name')
146 nb.metadata.name = name
147
148 notebook_id = self.write_notebook_object(nb)
149 return notebook_id
150
151 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
152 """Save an existing notebook by notebook_id."""
153 if format not in self.allowed_formats:
154 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
155
156 try:
157 nb = current.reads(data.decode('utf-8'), format)
158 except:
159 raise web.HTTPError(400, u'Invalid JSON data')
160
161 if name is not None:
162 nb.metadata.name = name
163 self.write_notebook_object(nb, notebook_id)
164
165 def write_notebook_object(self, nb, notebook_id=None):
166 """Write a notebook object and return its notebook_id.
167
168 If notebook_id is None, this method should create a new notebook_id.
169 If notebook_id is not None, this method should check to make sure it
170 exists and is valid.
171 """
172 raise NotImplementedError('must be implemented in a subclass')
173
174 def delete_notebook(self, notebook_id):
175 """Delete notebook by notebook_id."""
176 raise NotImplementedError('must be implemented in a subclass')
177
178 def increment_filename(self, name):
179 """Increment a filename to make it unique.
180
181 This exists for notebook stores that must have unique names. When a notebook
182 is created or copied this method constructs a unique filename, typically
183 by appending an integer to the name.
184 """
185 return name
186
187 def new_notebook(self):
188 """Create a new notebook and return its notebook_id."""
189 name = self.increment_filename('Untitled')
190 metadata = current.new_metadata(name=name)
191 nb = current.new_notebook(metadata=metadata)
192 notebook_id = self.write_notebook_object(nb)
193 return notebook_id
194
195 def copy_notebook(self, notebook_id):
196 """Copy an existing notebook and return its notebook_id."""
197 last_mod, nb = self.read_notebook_object(notebook_id)
198 name = nb.metadata.name + '-Copy'
199 name = self.increment_filename(name)
200 nb.metadata.name = name
201 notebook_id = self.write_notebook_object(nb)
202 return notebook_id
203
204 def log_info(self):
205 self.log.info("Serving notebooks") No newline at end of file
@@ -6,7 +6,7 b' Authors:'
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-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.
@@ -19,34 +19,19 b' Authors:'
19 import datetime
19 import datetime
20 import io
20 import io
21 import os
21 import os
22 import uuid
23 import glob
22 import glob
24
23
25 from tornado import web
24 from tornado import web
26
25
27 from IPython.config.configurable import LoggingConfigurable
26 from .basenbmanager import BaseNotebookManager
28 from IPython.nbformat import current
27 from IPython.nbformat import current
29 from IPython.utils.traitlets import Unicode, List, Dict, Bool, TraitError
28 from IPython.utils.traitlets import Unicode, Dict, Bool, TraitError
30
29
31 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
32 # Classes
31 # Classes
33 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
34
33
35 class NotebookManager(LoggingConfigurable):
34 class FileNotebookManager(BaseNotebookManager):
36
37 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
38 The directory to use for notebooks.
39 """)
40 def _notebook_dir_changed(self, name, old, new):
41 """do a bit of validation of the notebook dir"""
42 if os.path.exists(new) and not os.path.isdir(new):
43 raise TraitError("notebook dir %r is not a directory" % new)
44 if not os.path.exists(new):
45 self.log.info("Creating notebook dir %s", new)
46 try:
47 os.mkdir(new)
48 except:
49 raise TraitError("Couldn't create notebook dir %r" % new)
50
35
51 save_script = Bool(False, config=True,
36 save_script = Bool(False, config=True,
52 help="""Automatically create a Python script when saving the notebook.
37 help="""Automatically create a Python script when saving the notebook.
@@ -59,24 +44,21 b' class NotebookManager(LoggingConfigurable):'
59 )
44 )
60
45
61 filename_ext = Unicode(u'.ipynb')
46 filename_ext = Unicode(u'.ipynb')
62 allowed_formats = List([u'json',u'py'])
63
47
64 # Map notebook_ids to notebook names
65 mapping = Dict()
66 # Map notebook names to notebook_ids
48 # Map notebook names to notebook_ids
67 rev_mapping = Dict()
49 rev_mapping = Dict()
68
50
69 def list_notebooks(self):
51 def get_notebook_names(self):
70 """List all notebooks in the notebook dir.
52 """List all notebook names in the notebook dir."""
71
72 This returns a list of dicts of the form::
73
74 dict(notebook_id=notebook,name=name)
75 """
76 names = glob.glob(os.path.join(self.notebook_dir,
53 names = glob.glob(os.path.join(self.notebook_dir,
77 '*' + self.filename_ext))
54 '*' + self.filename_ext))
78 names = [os.path.splitext(os.path.basename(name))[0]
55 names = [os.path.splitext(os.path.basename(name))[0]
79 for name in names]
56 for name in names]
57 return names
58
59 def list_notebooks(self):
60 """List all notebooks in the notebook dir."""
61 names = self.get_notebook_names()
80
62
81 data = []
63 data = []
82 for name in names:
64 for name in names:
@@ -90,30 +72,20 b' class NotebookManager(LoggingConfigurable):'
90
72
91 def new_notebook_id(self, name):
73 def new_notebook_id(self, name):
92 """Generate a new notebook_id for a name and store its mappings."""
74 """Generate a new notebook_id for a name and store its mappings."""
93 # TODO: the following will give stable urls for notebooks, but unless
75 notebook_id = super(FileNotebookManager, self).new_notebook_id(name)
94 # the notebooks are immediately redirected to their new urls when their
95 # filemname changes, nasty inconsistencies result. So for now it's
96 # disabled and instead we use a random uuid4() call. But we leave the
97 # logic here so that we can later reactivate it, whhen the necessary
98 # url redirection code is written.
99 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
100 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
101
102 notebook_id = unicode(uuid.uuid4())
103
104 self.mapping[notebook_id] = name
105 self.rev_mapping[name] = notebook_id
76 self.rev_mapping[name] = notebook_id
106 return notebook_id
77 return notebook_id
107
78
108 def delete_notebook_id(self, notebook_id):
79 def delete_notebook_id(self, notebook_id):
109 """Delete a notebook's id only. This doesn't delete the actual notebook."""
80 """Delete a notebook's id in the mapping."""
110 name = self.mapping[notebook_id]
81 name = self.mapping[notebook_id]
111 del self.mapping[notebook_id]
82 super(FileNotebookManager, self).delete_notebook_id(notebook_id)
112 del self.rev_mapping[name]
83 del self.rev_mapping[name]
113
84
114 def notebook_exists(self, notebook_id):
85 def notebook_exists(self, notebook_id):
115 """Does a notebook exist?"""
86 """Does a notebook exist?"""
116 if notebook_id not in self.mapping:
87 exists = super(FileNotebookManager, self).notebook_exists(notebook_id)
88 if not exists:
117 return False
89 return False
118 path = self.get_path_by_name(self.mapping[notebook_id])
90 path = self.get_path_by_name(self.mapping[notebook_id])
119 return os.path.isfile(path)
91 return os.path.isfile(path)
@@ -132,22 +104,7 b' class NotebookManager(LoggingConfigurable):'
132 path = os.path.join(self.notebook_dir, filename)
104 path = os.path.join(self.notebook_dir, filename)
133 return path
105 return path
134
106
135 def get_notebook(self, notebook_id, format=u'json'):
107 def read_notebook_object(self, notebook_id):
136 """Get the representation of a notebook in format by notebook_id."""
137 format = unicode(format)
138 if format not in self.allowed_formats:
139 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
140 last_modified, nb = self.get_notebook_object(notebook_id)
141 kwargs = {}
142 if format == 'json':
143 # don't split lines for sending over the wire, because it
144 # should match the Python in-memory format.
145 kwargs['split_lines'] = False
146 data = current.writes(nb, format, **kwargs)
147 name = nb.get('name','notebook')
148 return last_modified, name, data
149
150 def get_notebook_object(self, notebook_id):
151 """Get the NotebookNode representation of a notebook by notebook_id."""
108 """Get the NotebookNode representation of a notebook by notebook_id."""
152 path = self.find_path(notebook_id)
109 path = self.find_path(notebook_id)
153 if not os.path.isfile(path):
110 if not os.path.isfile(path):
@@ -165,60 +122,27 b' class NotebookManager(LoggingConfigurable):'
165 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
122 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
166 return last_modified, nb
123 return last_modified, nb
167
124
168 def save_new_notebook(self, data, name=None, format=u'json'):
125 def write_notebook_object(self, nb, notebook_id=None):
169 """Save a new notebook and return its notebook_id.
126 """Save an existing notebook object by notebook_id."""
170
171 If a name is passed in, it overrides any values in the notebook data
172 and the value in the data is updated to use that value.
173 """
174 if format not in self.allowed_formats:
175 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
176
177 try:
178 nb = current.reads(data.decode('utf-8'), format)
179 except:
180 raise web.HTTPError(400, u'Invalid JSON data')
181
182 if name is None:
183 try:
184 name = nb.metadata.name
185 except AttributeError:
186 raise web.HTTPError(400, u'Missing notebook name')
187 nb.metadata.name = name
188
189 notebook_id = self.new_notebook_id(name)
190 self.save_notebook_object(notebook_id, nb)
191 return notebook_id
192
193 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
194 """Save an existing notebook by notebook_id."""
195 if format not in self.allowed_formats:
196 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
197
198 try:
127 try:
199 nb = current.reads(data.decode('utf-8'), format)
128 new_name = nb.metadata.name
200 except:
129 except AttributeError:
201 raise web.HTTPError(400, u'Invalid JSON data')
130 raise web.HTTPError(400, u'Missing notebook name')
202
131
203 if name is not None:
132 if notebook_id is None:
204 nb.metadata.name = name
133 notebook_id = self.new_notebook_id(new_name)
205 self.save_notebook_object(notebook_id, nb)
206
134
207 def save_notebook_object(self, notebook_id, nb):
208 """Save an existing notebook object by notebook_id."""
209 if notebook_id not in self.mapping:
135 if notebook_id not in self.mapping:
210 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
136 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
137
211 old_name = self.mapping[notebook_id]
138 old_name = self.mapping[notebook_id]
212 try:
213 new_name = nb.metadata.name
214 except AttributeError:
215 raise web.HTTPError(400, u'Missing notebook name')
216 path = self.get_path_by_name(new_name)
139 path = self.get_path_by_name(new_name)
217 try:
140 try:
218 with open(path,'w') as f:
141 with open(path,'w') as f:
219 current.write(nb, f, u'json')
142 current.write(nb, f, u'json')
220 except Exception as e:
143 except Exception as e:
221 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
144 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
145
222 # save .py script as well
146 # save .py script as well
223 if self.save_script:
147 if self.save_script:
224 pypath = os.path.splitext(path)[0] + '.py'
148 pypath = os.path.splitext(path)[0] + '.py'
@@ -228,6 +152,7 b' class NotebookManager(LoggingConfigurable):'
228 except Exception as e:
152 except Exception as e:
229 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
153 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
230
154
155 # remove old files if the name changed
231 if old_name != new_name:
156 if old_name != new_name:
232 old_path = self.get_path_by_name(old_name)
157 old_path = self.get_path_by_name(old_name)
233 if os.path.isfile(old_path):
158 if os.path.isfile(old_path):
@@ -239,6 +164,8 b' class NotebookManager(LoggingConfigurable):'
239 self.mapping[notebook_id] = new_name
164 self.mapping[notebook_id] = new_name
240 self.rev_mapping[new_name] = notebook_id
165 self.rev_mapping[new_name] = notebook_id
241 del self.rev_mapping[old_name]
166 del self.rev_mapping[old_name]
167
168 return notebook_id
242
169
243 def delete_notebook(self, notebook_id):
170 def delete_notebook(self, notebook_id):
244 """Delete notebook by notebook_id."""
171 """Delete notebook by notebook_id."""
@@ -263,24 +190,7 b' class NotebookManager(LoggingConfigurable):'
263 break
190 break
264 else:
191 else:
265 i = i+1
192 i = i+1
266 return path, name
193 return name
267
268 def new_notebook(self):
269 """Create a new notebook and return its notebook_id."""
270 path, name = self.increment_filename('Untitled')
271 notebook_id = self.new_notebook_id(name)
272 metadata = current.new_metadata(name=name)
273 nb = current.new_notebook(metadata=metadata)
274 with open(path,'w') as f:
275 current.write(nb, f, u'json')
276 return notebook_id
277
194
278 def copy_notebook(self, notebook_id):
195 def log_info(self):
279 """Copy an existing notebook and return its notebook_id."""
196 self.log.info("Serving notebooks from local directory: %s", self.notebook_dir)
280 last_mod, nb = self.get_notebook_object(notebook_id)
281 name = nb.metadata.name + '-Copy'
282 path, name = self.increment_filename(name)
283 nb.metadata.name = name
284 notebook_id = self.new_notebook_id(name)
285 self.save_notebook_object(notebook_id, nb)
286 return notebook_id
@@ -51,7 +51,8 b' from .handlers import (LoginHandler, LogoutHandler,'
51 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
51 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
52 FileFindHandler,
52 FileFindHandler,
53 )
53 )
54 from .notebookmanager import NotebookManager
54 from .basenbmanager import BaseNotebookManager
55 from .filenbmanager import FileNotebookManager
55 from .clustermanager import ClusterManager
56 from .clustermanager import ClusterManager
56
57
57 from IPython.config.application import catch_config_error, boolean_flag
58 from IPython.config.application import catch_config_error, boolean_flag
@@ -66,7 +67,11 b' from IPython.zmq.ipkernel import ('
66 aliases as ipkernel_aliases,
67 aliases as ipkernel_aliases,
67 IPKernelApp
68 IPKernelApp
68 )
69 )
69 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
70 from IPython.utils.importstring import import_item
71 from IPython.utils.traitlets import (
72 Dict, Unicode, Integer, List, Enum, Bool,
73 DottedObjectName
74 )
70 from IPython.utils import py3compat
75 from IPython.utils import py3compat
71 from IPython.utils.path import filefind
76 from IPython.utils.path import filefind
72
77
@@ -215,7 +220,7 b" flags['read-only'] = ("
215 )
220 )
216
221
217 # Add notebook manager flags
222 # Add notebook manager flags
218 flags.update(boolean_flag('script', 'NotebookManager.save_script',
223 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
219 'Auto-save a .py script everytime the .ipynb notebook is saved',
224 'Auto-save a .py script everytime the .ipynb notebook is saved',
220 'Do not auto-save .py scripts for every notebook'))
225 'Do not auto-save .py scripts for every notebook'))
221
226
@@ -232,7 +237,7 b' aliases.update({'
232 'port-retries': 'NotebookApp.port_retries',
237 'port-retries': 'NotebookApp.port_retries',
233 'keyfile': 'NotebookApp.keyfile',
238 'keyfile': 'NotebookApp.keyfile',
234 'certfile': 'NotebookApp.certfile',
239 'certfile': 'NotebookApp.certfile',
235 'notebook-dir': 'NotebookManager.notebook_dir',
240 'notebook-dir': 'BaseNotebookManager.notebook_dir',
236 'browser': 'NotebookApp.browser',
241 'browser': 'NotebookApp.browser',
237 })
242 })
238
243
@@ -260,7 +265,8 b' class NotebookApp(BaseIPythonApplication):'
260 """
265 """
261 examples = _examples
266 examples = _examples
262
267
263 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager]
268 classes = IPythonConsoleApp.classes + [MappingKernelManager, BaseNotebookManager,
269 FileNotebookManager]
264 flags = Dict(flags)
270 flags = Dict(flags)
265 aliases = Dict(aliases)
271 aliases = Dict(aliases)
266
272
@@ -404,6 +410,10 b' class NotebookApp(BaseIPythonApplication):'
404 else:
410 else:
405 self.log.info("Using MathJax: %s", new)
411 self.log.info("Using MathJax: %s", new)
406
412
413 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
414 config=True,
415 help='The notebook manager class to use.')
416
407 def parse_command_line(self, argv=None):
417 def parse_command_line(self, argv=None):
408 super(NotebookApp, self).parse_command_line(argv)
418 super(NotebookApp, self).parse_command_line(argv)
409 if argv is None:
419 if argv is None:
@@ -421,7 +431,7 b' class NotebookApp(BaseIPythonApplication):'
421 else:
431 else:
422 self.file_to_run = f
432 self.file_to_run = f
423 nbdir = os.path.dirname(f)
433 nbdir = os.path.dirname(f)
424 self.config.NotebookManager.notebook_dir = nbdir
434 self.config.BaseNotebookManager.notebook_dir = nbdir
425
435
426 def init_configurables(self):
436 def init_configurables(self):
427 # force Session default to be secure
437 # force Session default to be secure
@@ -430,9 +440,10 b' class NotebookApp(BaseIPythonApplication):'
430 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
440 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
431 connection_dir = self.profile_dir.security_dir,
441 connection_dir = self.profile_dir.security_dir,
432 )
442 )
433 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
443 kls = import_item(self.notebook_manager_class)
434 self.log.info("Serving notebooks from %s", self.notebook_manager.notebook_dir)
444 self.notebook_manager = kls(config=self.config, log=self.log)
435 self.notebook_manager.list_notebooks()
445 self.notebook_manager.log_info()
446 self.notebook_manager.load_notebook_names()
436 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
447 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
437 self.cluster_manager.update_profiles()
448 self.cluster_manager.update_profiles()
438
449
@@ -7,28 +7,28 b' from tempfile import NamedTemporaryFile'
7 from IPython.utils.tempdir import TemporaryDirectory
7 from IPython.utils.tempdir import TemporaryDirectory
8 from IPython.utils.traitlets import TraitError
8 from IPython.utils.traitlets import TraitError
9
9
10 from IPython.frontend.html.notebook.notebookmanager import NotebookManager
10 from IPython.frontend.html.notebook.filenbmanager import FileNotebookManager
11
11
12 class TestNotebookManager(TestCase):
12 class TestNotebookManager(TestCase):
13
13
14 def test_nb_dir(self):
14 def test_nb_dir(self):
15 with TemporaryDirectory() as td:
15 with TemporaryDirectory() as td:
16 km = NotebookManager(notebook_dir=td)
16 km = FileNotebookManager(notebook_dir=td)
17 self.assertEqual(km.notebook_dir, td)
17 self.assertEquals(km.notebook_dir, td)
18
18
19 def test_create_nb_dir(self):
19 def test_create_nb_dir(self):
20 with TemporaryDirectory() as td:
20 with TemporaryDirectory() as td:
21 nbdir = os.path.join(td, 'notebooks')
21 nbdir = os.path.join(td, 'notebooks')
22 km = NotebookManager(notebook_dir=nbdir)
22 km = FileNotebookManager(notebook_dir=nbdir)
23 self.assertEqual(km.notebook_dir, nbdir)
23 self.assertEquals(km.notebook_dir, nbdir)
24
24
25 def test_missing_nb_dir(self):
25 def test_missing_nb_dir(self):
26 with TemporaryDirectory() as td:
26 with TemporaryDirectory() as td:
27 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
27 nbdir = os.path.join(td, 'notebook', 'dir', 'is', 'missing')
28 self.assertRaises(TraitError, NotebookManager, notebook_dir=nbdir)
28 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=nbdir)
29
29
30 def test_invalid_nb_dir(self):
30 def test_invalid_nb_dir(self):
31 with NamedTemporaryFile() as tf:
31 with NamedTemporaryFile() as tf:
32 self.assertRaises(TraitError, NotebookManager, notebook_dir=tf.name)
32 self.assertRaises(TraitError, FileNotebookManager, notebook_dir=tf.name)
33
33
34
34
@@ -164,6 +164,7 b" have['oct2py'] = test_for('oct2py')"
164 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
164 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
165 have['wx'] = test_for('wx')
165 have['wx'] = test_for('wx')
166 have['wx.aui'] = test_for('wx.aui')
166 have['wx.aui'] = test_for('wx.aui')
167 have['azure'] = test_for('azure')
167
168
168 if os.name == 'nt':
169 if os.name == 'nt':
169 min_zmq = (2,1,7)
170 min_zmq = (2,1,7)
@@ -303,6 +304,9 b' def make_exclude():'
303 exclusions.append(ipjoin('extensions', 'rmagic'))
304 exclusions.append(ipjoin('extensions', 'rmagic'))
304 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
305 exclusions.append(ipjoin('extensions', 'tests', 'test_rmagic'))
305
306
307 if not have['azure']:
308 exclusions.append(ipjoin('frontend', 'html', 'notebook', 'azurenbmanager'))
309
306 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
310 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
307 if sys.platform == 'win32':
311 if sys.platform == 'win32':
308 exclusions = [s.replace('\\','\\\\') for s in exclusions]
312 exclusions = [s.replace('\\','\\\\') for s in exclusions]
@@ -243,7 +243,6 b' and then on any cell that you need to protect, use::'
243 if script:
243 if script:
244 # rest of the cell...
244 # rest of the cell...
245
245
246
247 Keyboard use
246 Keyboard use
248 ------------
247 ------------
249
248
@@ -333,9 +332,11 b' notebook server over ``https://``, not over plain ``http://``. The startup'
333 message from the server prints this, but it's easy to overlook and think the
332 message from the server prints this, but it's easy to overlook and think the
334 server is for some reason non-responsive.
333 server is for some reason non-responsive.
335
334
335 Quick how to's
336 ==============
336
337
337 Quick Howto: running a public notebook server
338 Running a public notebook server
338 =============================================
339 --------------------------------
339
340
340 If you want to access your notebook server remotely with just a web browser,
341 If you want to access your notebook server remotely with just a web browser,
341 here is a quick set of instructions. Start by creating a certificate file and
342 here is a quick set of instructions. Start by creating a certificate file and
@@ -365,7 +366,7 b' You can then start the notebook and access it later by pointing your browser to'
365 ``https://your.host.com:9999`` with ``ipython notebook --profile=nbserver``.
366 ``https://your.host.com:9999`` with ``ipython notebook --profile=nbserver``.
366
367
367 Running with a different URL prefix
368 Running with a different URL prefix
368 ===================================
369 -----------------------------------
369
370
370 The notebook dashboard (i.e. the default landing page with an overview
371 The notebook dashboard (i.e. the default landing page with an overview
371 of all your notebooks) typically lives at a URL path of
372 of all your notebooks) typically lives at a URL path of
@@ -379,6 +380,27 b' modifying ``ipython_notebook_config.py``)::'
379 c.NotebookApp.base_kernel_url = '/ipython/'
380 c.NotebookApp.base_kernel_url = '/ipython/'
380 c.NotebookApp.webapp_settings = {'static_url_prefix':'/ipython/static/'}
381 c.NotebookApp.webapp_settings = {'static_url_prefix':'/ipython/static/'}
381
382
383 Using a different notebook store
384 --------------------------------
385
386 By default the notebook server stores notebooks as files in the working
387 directory of the notebook server, also known as the ``notebook_dir``. This
388 logic is implemented in the :class:`FileNotebookManager` class. However, the
389 server can be configured to use a different notebook manager class, which can
390 store the notebooks in a different format. Currently, we ship a
391 :class:`AzureNotebookManager` class that stores notebooks in Azure blob
392 storage. This can be used by adding the following lines to your
393 ``ipython_notebook_config.py`` file::
394
395 c.NotebookApp.notebook_manager_class = 'IPython.frontend.html.notebook.azurenbmanager.AzureNotebookManager'
396 c.AzureNotebookManager.account_name = u'paste_your_account_name_here'
397 c.AzureNotebookManager.account_key = u'paste_your_account_key_here'
398 c.AzureNotebookManager.container = u'notebooks'
399
400 In addition to providing your Azure Blob Storage account name and key, you will
401 have to provide a container name; you can use multiple containers to organize
402 your Notebooks.
403
382 .. _notebook_format:
404 .. _notebook_format:
383
405
384 The notebook format
406 The notebook format
@@ -423,7 +445,7 b' cell, when exported to python format::'
423 print "hello IPython"
445 print "hello IPython"
424
446
425
447
426 Known Issues
448 Known issues
427 ============
449 ============
428
450
429 When behind a proxy, especially if your system or browser is set to autodetect
451 When behind a proxy, especially if your system or browser is set to autodetect
General Comments 0
You need to be logged in to leave comments. Login now