##// 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 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.
@@ -19,34 +19,19 b' Authors:'
19 19 import datetime
20 20 import io
21 21 import os
22 import uuid
23 22 import glob
24 23
25 24 from tornado import web
26 25
27 from IPython.config.configurable import LoggingConfigurable
26 from .basenbmanager import BaseNotebookManager
28 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 31 # Classes
33 32 #-----------------------------------------------------------------------------
34 33
35 class NotebookManager(LoggingConfigurable):
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)
34 class FileNotebookManager(BaseNotebookManager):
50 35
51 36 save_script = Bool(False, config=True,
52 37 help="""Automatically create a Python script when saving the notebook.
@@ -59,24 +44,21 b' class NotebookManager(LoggingConfigurable):'
59 44 )
60 45
61 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 48 # Map notebook names to notebook_ids
67 49 rev_mapping = Dict()
68 50
69 def list_notebooks(self):
70 """List all notebooks in the notebook dir.
71
72 This returns a list of dicts of the form::
73
74 dict(notebook_id=notebook,name=name)
75 """
51 def get_notebook_names(self):
52 """List all notebook names in the notebook dir."""
76 53 names = glob.glob(os.path.join(self.notebook_dir,
77 54 '*' + self.filename_ext))
78 55 names = [os.path.splitext(os.path.basename(name))[0]
79 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 63 data = []
82 64 for name in names:
@@ -90,30 +72,20 b' class NotebookManager(LoggingConfigurable):'
90 72
91 73 def new_notebook_id(self, name):
92 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
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
75 notebook_id = super(FileNotebookManager, self).new_notebook_id(name)
105 76 self.rev_mapping[name] = notebook_id
106 77 return notebook_id
107 78
108 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 81 name = self.mapping[notebook_id]
111 del self.mapping[notebook_id]
82 super(FileNotebookManager, self).delete_notebook_id(notebook_id)
112 83 del self.rev_mapping[name]
113 84
114 85 def notebook_exists(self, notebook_id):
115 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 89 return False
118 90 path = self.get_path_by_name(self.mapping[notebook_id])
119 91 return os.path.isfile(path)
@@ -132,22 +104,7 b' class NotebookManager(LoggingConfigurable):'
132 104 path = os.path.join(self.notebook_dir, filename)
133 105 return path
134 106
135 def get_notebook(self, notebook_id, format=u'json'):
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):
107 def read_notebook_object(self, notebook_id):
151 108 """Get the NotebookNode representation of a notebook by notebook_id."""
152 109 path = self.find_path(notebook_id)
153 110 if not os.path.isfile(path):
@@ -165,60 +122,27 b' class NotebookManager(LoggingConfigurable):'
165 122 nb.metadata.name = os.path.splitext(os.path.basename(path))[0]
166 123 return last_modified, nb
167 124
168 def save_new_notebook(self, data, name=None, format=u'json'):
169 """Save a new notebook and return its 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
125 def write_notebook_object(self, nb, notebook_id=None):
126 """Save an existing notebook object by notebook_id."""
198 127 try:
199 nb = current.reads(data.decode('utf-8'), format)
200 except:
201 raise web.HTTPError(400, u'Invalid JSON data')
128 new_name = nb.metadata.name
129 except AttributeError:
130 raise web.HTTPError(400, u'Missing notebook name')
202 131
203 if name is not None:
204 nb.metadata.name = name
205 self.save_notebook_object(notebook_id, nb)
132 if notebook_id is None:
133 notebook_id = self.new_notebook_id(new_name)
206 134
207 def save_notebook_object(self, notebook_id, nb):
208 """Save an existing notebook object by notebook_id."""
209 135 if notebook_id not in self.mapping:
210 136 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
137
211 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 139 path = self.get_path_by_name(new_name)
217 140 try:
218 141 with open(path,'w') as f:
219 142 current.write(nb, f, u'json')
220 143 except Exception as e:
221 144 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
145
222 146 # save .py script as well
223 147 if self.save_script:
224 148 pypath = os.path.splitext(path)[0] + '.py'
@@ -228,6 +152,7 b' class NotebookManager(LoggingConfigurable):'
228 152 except Exception as e:
229 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 156 if old_name != new_name:
232 157 old_path = self.get_path_by_name(old_name)
233 158 if os.path.isfile(old_path):
@@ -239,6 +164,8 b' class NotebookManager(LoggingConfigurable):'
239 164 self.mapping[notebook_id] = new_name
240 165 self.rev_mapping[new_name] = notebook_id
241 166 del self.rev_mapping[old_name]
167
168 return notebook_id
242 169
243 170 def delete_notebook(self, notebook_id):
244 171 """Delete notebook by notebook_id."""
@@ -263,24 +190,7 b' class NotebookManager(LoggingConfigurable):'
263 190 break
264 191 else:
265 192 i = i+1
266 return path, 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
193 return name
277 194
278 def copy_notebook(self, notebook_id):
279 """Copy an existing notebook and return its notebook_id."""
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
195 def log_info(self):
196 self.log.info("Serving notebooks from local directory: %s", self.notebook_dir)
@@ -51,7 +51,8 b' from .handlers import (LoginHandler, LogoutHandler,'
51 51 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
52 52 FileFindHandler,
53 53 )
54 from .notebookmanager import NotebookManager
54 from .basenbmanager import BaseNotebookManager
55 from .filenbmanager import FileNotebookManager
55 56 from .clustermanager import ClusterManager
56 57
57 58 from IPython.config.application import catch_config_error, boolean_flag
@@ -66,7 +67,11 b' from IPython.zmq.ipkernel import ('
66 67 aliases as ipkernel_aliases,
67 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 75 from IPython.utils import py3compat
71 76 from IPython.utils.path import filefind
72 77
@@ -215,7 +220,7 b" flags['read-only'] = ("
215 220 )
216 221
217 222 # Add notebook manager flags
218 flags.update(boolean_flag('script', 'NotebookManager.save_script',
223 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
219 224 'Auto-save a .py script everytime the .ipynb notebook is saved',
220 225 'Do not auto-save .py scripts for every notebook'))
221 226
@@ -232,7 +237,7 b' aliases.update({'
232 237 'port-retries': 'NotebookApp.port_retries',
233 238 'keyfile': 'NotebookApp.keyfile',
234 239 'certfile': 'NotebookApp.certfile',
235 'notebook-dir': 'NotebookManager.notebook_dir',
240 'notebook-dir': 'BaseNotebookManager.notebook_dir',
236 241 'browser': 'NotebookApp.browser',
237 242 })
238 243
@@ -260,7 +265,8 b' class NotebookApp(BaseIPythonApplication):'
260 265 """
261 266 examples = _examples
262 267
263 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager]
268 classes = IPythonConsoleApp.classes + [MappingKernelManager, BaseNotebookManager,
269 FileNotebookManager]
264 270 flags = Dict(flags)
265 271 aliases = Dict(aliases)
266 272
@@ -404,6 +410,10 b' class NotebookApp(BaseIPythonApplication):'
404 410 else:
405 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 417 def parse_command_line(self, argv=None):
408 418 super(NotebookApp, self).parse_command_line(argv)
409 419 if argv is None:
@@ -421,7 +431,7 b' class NotebookApp(BaseIPythonApplication):'
421 431 else:
422 432 self.file_to_run = f
423 433 nbdir = os.path.dirname(f)
424 self.config.NotebookManager.notebook_dir = nbdir
434 self.config.BaseNotebookManager.notebook_dir = nbdir
425 435
426 436 def init_configurables(self):
427 437 # force Session default to be secure
@@ -430,9 +440,10 b' class NotebookApp(BaseIPythonApplication):'
430 440 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
431 441 connection_dir = self.profile_dir.security_dir,
432 442 )
433 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
434 self.log.info("Serving notebooks from %s", self.notebook_manager.notebook_dir)
435 self.notebook_manager.list_notebooks()
443 kls = import_item(self.notebook_manager_class)
444 self.notebook_manager = kls(config=self.config, log=self.log)
445 self.notebook_manager.log_info()
446 self.notebook_manager.load_notebook_names()
436 447 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
437 448 self.cluster_manager.update_profiles()
438 449
@@ -7,28 +7,28 b' from tempfile import NamedTemporaryFile'
7 7 from IPython.utils.tempdir import TemporaryDirectory
8 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 12 class TestNotebookManager(TestCase):
13 13
14 14 def test_nb_dir(self):
15 15 with TemporaryDirectory() as td:
16 km = NotebookManager(notebook_dir=td)
17 self.assertEqual(km.notebook_dir, td)
16 km = FileNotebookManager(notebook_dir=td)
17 self.assertEquals(km.notebook_dir, td)
18 18
19 19 def test_create_nb_dir(self):
20 20 with TemporaryDirectory() as td:
21 21 nbdir = os.path.join(td, 'notebooks')
22 km = NotebookManager(notebook_dir=nbdir)
23 self.assertEqual(km.notebook_dir, nbdir)
22 km = FileNotebookManager(notebook_dir=nbdir)
23 self.assertEquals(km.notebook_dir, nbdir)
24 24
25 25 def test_missing_nb_dir(self):
26 26 with TemporaryDirectory() as td:
27 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 30 def test_invalid_nb_dir(self):
31 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 164 have['tornado'] = test_for('tornado.version_info', (2,1,0), callback=None)
165 165 have['wx'] = test_for('wx')
166 166 have['wx.aui'] = test_for('wx.aui')
167 have['azure'] = test_for('azure')
167 168
168 169 if os.name == 'nt':
169 170 min_zmq = (2,1,7)
@@ -303,6 +304,9 b' def make_exclude():'
303 304 exclusions.append(ipjoin('extensions', 'rmagic'))
304 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 310 # This is needed for the reg-exp to match on win32 in the ipdoctest plugin.
307 311 if sys.platform == 'win32':
308 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 243 if script:
244 244 # rest of the cell...
245 245
246
247 246 Keyboard use
248 247 ------------
249 248
@@ -333,9 +332,11 b' notebook server over ``https://``, not over plain ``http://``. The startup'
333 332 message from the server prints this, but it's easy to overlook and think the
334 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 =============================================
338 Running a public notebook server
339 --------------------------------
339 340
340 341 If you want to access your notebook server remotely with just a web browser,
341 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 366 ``https://your.host.com:9999`` with ``ipython notebook --profile=nbserver``.
366 367
367 368 Running with a different URL prefix
368 ===================================
369 -----------------------------------
369 370
370 371 The notebook dashboard (i.e. the default landing page with an overview
371 372 of all your notebooks) typically lives at a URL path of
@@ -379,6 +380,27 b' modifying ``ipython_notebook_config.py``)::'
379 380 c.NotebookApp.base_kernel_url = '/ipython/'
380 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 404 .. _notebook_format:
383 405
384 406 The notebook format
@@ -423,7 +445,7 b' cell, when exported to python format::'
423 445 print "hello IPython"
424 446
425 447
426 Known Issues
448 Known issues
427 449 ============
428 450
429 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