##// END OF EJS Templates
Define flags in application that's going to use them.
Fernando Perez -
Show More
@@ -1,380 +1,383
1 1 """A tornado based IPython notebook server.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-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 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import signal
24 24 import socket
25 25 import sys
26 26 import threading
27 27 import webbrowser
28 28
29 29 # Third party
30 30 import zmq
31 31
32 32 # Install the pyzmq ioloop. This has to be done before anything else from
33 33 # tornado is imported.
34 34 from zmq.eventloop import ioloop
35 35 # FIXME: ioloop.install is new in pyzmq-2.1.7, so remove this conditional
36 36 # when pyzmq dependency is updated beyond that.
37 37 if hasattr(ioloop, 'install'):
38 38 ioloop.install()
39 39 else:
40 40 import tornado.ioloop
41 41 tornado.ioloop.IOLoop = ioloop.IOLoop
42 42
43 43 from tornado import httpserver
44 44 from tornado import web
45 45
46 46 # Our own libraries
47 47 from .kernelmanager import MappingKernelManager
48 48 from .handlers import (LoginHandler, LogoutHandler,
49 49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
50 50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
51 51 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
52 52 )
53 from .notebookmanager import NotebookManager, manager_flags
53 from .notebookmanager import NotebookManager
54 54
55 from IPython.config.application import catch_config_error
55 from IPython.config.application import catch_config_error, boolean_flag
56 56 from IPython.core.application import BaseIPythonApplication
57 57 from IPython.core.profiledir import ProfileDir
58 58 from IPython.lib.kernel import swallow_argv
59 59 from IPython.zmq.session import Session, default_secure
60 60 from IPython.zmq.zmqshell import ZMQInteractiveShell
61 61 from IPython.zmq.ipkernel import (
62 62 flags as ipkernel_flags,
63 63 aliases as ipkernel_aliases,
64 64 IPKernelApp
65 65 )
66 66 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
67 67
68 68 #-----------------------------------------------------------------------------
69 69 # Module globals
70 70 #-----------------------------------------------------------------------------
71 71
72 72 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
73 73 _kernel_action_regex = r"(?P<action>restart|interrupt)"
74 74 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
75 75
76 76 LOCALHOST = '127.0.0.1'
77 77
78 78 _examples = """
79 79 ipython notebook # start the notebook
80 80 ipython notebook --profile=sympy # use the sympy profile
81 81 ipython notebook --pylab=inline # pylab in inline plotting mode
82 82 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
83 83 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
84 84 """
85 85
86 86 #-----------------------------------------------------------------------------
87 87 # The Tornado web application
88 88 #-----------------------------------------------------------------------------
89 89
90 90 class NotebookWebApplication(web.Application):
91 91
92 92 def __init__(self, ipython_app, kernel_manager, notebook_manager, log, settings_overrides):
93 93 handlers = [
94 94 (r"/", ProjectDashboardHandler),
95 95 (r"/login", LoginHandler),
96 96 (r"/logout", LogoutHandler),
97 97 (r"/new", NewHandler),
98 98 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
99 99 (r"/kernels", MainKernelHandler),
100 100 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
101 101 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
102 102 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
103 103 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
104 104 (r"/notebooks", NotebookRootHandler),
105 105 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
106 106 (r"/rstservice/render", RSTHandler)
107 107 ]
108 108 settings = dict(
109 109 template_path=os.path.join(os.path.dirname(__file__), "templates"),
110 110 static_path=os.path.join(os.path.dirname(__file__), "static"),
111 111 cookie_secret=os.urandom(1024),
112 112 login_url="/login",
113 113 )
114 114
115 115 # allow custom overrides for the tornado web app.
116 116 settings.update(settings_overrides)
117 117
118 118 super(NotebookWebApplication, self).__init__(handlers, **settings)
119 119
120 120 self.kernel_manager = kernel_manager
121 121 self.log = log
122 122 self.notebook_manager = notebook_manager
123 123 self.ipython_app = ipython_app
124 124 self.read_only = self.ipython_app.read_only
125 125
126 126
127 127 #-----------------------------------------------------------------------------
128 128 # Aliases and Flags
129 129 #-----------------------------------------------------------------------------
130 130
131 131 flags = dict(ipkernel_flags)
132 132 flags['no-browser']=(
133 133 {'NotebookApp' : {'open_browser' : False}},
134 134 "Don't open the notebook in a browser after startup."
135 135 )
136 136 flags['no-mathjax']=(
137 137 {'NotebookApp' : {'enable_mathjax' : False}},
138 138 """Disable MathJax
139 139
140 140 MathJax is the javascript library IPython uses to render math/LaTeX. It is
141 141 very large, so you may want to disable it if you have a slow internet
142 142 connection, or for offline use of the notebook.
143 143
144 144 When disabled, equations etc. will appear as their untransformed TeX source.
145 145 """
146 146 )
147 147 flags['read-only'] = (
148 148 {'NotebookApp' : {'read_only' : True}},
149 149 """Allow read-only access to notebooks.
150 150
151 151 When using a password to protect the notebook server, this flag
152 152 allows unauthenticated clients to view the notebook list, and
153 153 individual notebooks, but not edit them, start kernels, or run
154 154 code.
155 155
156 156 If no password is set, the server will be entirely read-only.
157 157 """
158 158 )
159 159
160 flags.update(manager_flags)
160 # Add notebook manager flags
161 flags.update(boolean_flag('script', 'NotebookManager.save_script',
162 'Auto-save a .py script everytime the .ipynb notebook is saved',
163 'Do not auto-save .py scripts for every notebook'))
161 164
162 165 # the flags that are specific to the frontend
163 166 # these must be scrubbed before being passed to the kernel,
164 167 # or it will raise an error on unrecognized flags
165 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script']
168 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
166 169
167 170 aliases = dict(ipkernel_aliases)
168 171
169 172 aliases.update({
170 173 'ip': 'NotebookApp.ip',
171 174 'port': 'NotebookApp.port',
172 175 'keyfile': 'NotebookApp.keyfile',
173 176 'certfile': 'NotebookApp.certfile',
174 177 'notebook-dir': 'NotebookManager.notebook_dir',
175 178 })
176 179
177 180 # remove ipkernel flags that are singletons, and don't make sense in
178 181 # multi-kernel evironment:
179 182 aliases.pop('f', None)
180 183
181 184 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
182 185 u'notebook-dir']
183 186
184 187 #-----------------------------------------------------------------------------
185 188 # NotebookApp
186 189 #-----------------------------------------------------------------------------
187 190
188 191 class NotebookApp(BaseIPythonApplication):
189 192
190 193 name = 'ipython-notebook'
191 194 default_config_file_name='ipython_notebook_config.py'
192 195
193 196 description = """
194 197 The IPython HTML Notebook.
195 198
196 199 This launches a Tornado based HTML Notebook Server that serves up an
197 200 HTML5/Javascript Notebook client.
198 201 """
199 202 examples = _examples
200 203
201 204 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
202 205 MappingKernelManager, NotebookManager]
203 206 flags = Dict(flags)
204 207 aliases = Dict(aliases)
205 208
206 209 kernel_argv = List(Unicode)
207 210
208 211 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
209 212 default_value=logging.INFO,
210 213 config=True,
211 214 help="Set the log level by value or name.")
212 215
213 216 # Network related information.
214 217
215 218 ip = Unicode(LOCALHOST, config=True,
216 219 help="The IP address the notebook server will listen on."
217 220 )
218 221
219 222 def _ip_changed(self, name, old, new):
220 223 if new == u'*': self.ip = u''
221 224
222 225 port = Integer(8888, config=True,
223 226 help="The port the notebook server will listen on."
224 227 )
225 228
226 229 certfile = Unicode(u'', config=True,
227 230 help="""The full path to an SSL/TLS certificate file."""
228 231 )
229 232
230 233 keyfile = Unicode(u'', config=True,
231 234 help="""The full path to a private key file for usage with SSL/TLS."""
232 235 )
233 236
234 237 password = Unicode(u'', config=True,
235 238 help="""Hashed password to use for web authentication.
236 239
237 240 To generate, type in a python/IPython shell:
238 241
239 242 from IPython.lib import passwd; passwd()
240 243
241 244 The string should be of the form type:salt:hashed-password.
242 245 """
243 246 )
244 247
245 248 open_browser = Bool(True, config=True,
246 249 help="Whether to open in a browser after starting.")
247 250
248 251 read_only = Bool(False, config=True,
249 252 help="Whether to prevent editing/execution of notebooks."
250 253 )
251 254
252 255 webapp_settings = Dict(config=True,
253 256 help="Supply overrides for the tornado.web.Application that the "
254 257 "IPython notebook uses.")
255 258
256 259 enable_mathjax = Bool(True, config=True,
257 260 help="""Whether to enable MathJax for typesetting math/TeX
258 261
259 262 MathJax is the javascript library IPython uses to render math/LaTeX. It is
260 263 very large, so you may want to disable it if you have a slow internet
261 264 connection, or for offline use of the notebook.
262 265
263 266 When disabled, equations etc. will appear as their untransformed TeX source.
264 267 """
265 268 )
266 269 def _enable_mathjax_changed(self, name, old, new):
267 270 """set mathjax url to empty if mathjax is disabled"""
268 271 if not new:
269 272 self.mathjax_url = u''
270 273
271 274 mathjax_url = Unicode("", config=True,
272 275 help="""The url for MathJax.js."""
273 276 )
274 277 def _mathjax_url_default(self):
275 278 if not self.enable_mathjax:
276 279 return u''
277 280 static_path = self.webapp_settings.get("static_path", os.path.join(os.path.dirname(__file__), "static"))
278 281 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
279 282 self.log.info("Using local MathJax")
280 283 return u"static/mathjax/MathJax.js"
281 284 else:
282 285 self.log.info("Using MathJax from CDN")
283 286 return u"http://cdn.mathjax.org/mathjax/latest/MathJax.js"
284 287
285 288 def _mathjax_url_changed(self, name, old, new):
286 289 if new and not self.enable_mathjax:
287 290 # enable_mathjax=False overrides mathjax_url
288 291 self.mathjax_url = u''
289 292 else:
290 293 self.log.info("Using MathJax: %s", new)
291 294
292 295 def parse_command_line(self, argv=None):
293 296 super(NotebookApp, self).parse_command_line(argv)
294 297 if argv is None:
295 298 argv = sys.argv[1:]
296 299
297 300 # Scrub frontend-specific flags
298 301 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
299 302 # Kernel should inherit default config file from frontend
300 303 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
301 304
302 305 def init_configurables(self):
303 306 # Don't let Qt or ZMQ swallow KeyboardInterupts.
304 307 signal.signal(signal.SIGINT, signal.SIG_DFL)
305 308
306 309 # force Session default to be secure
307 310 default_secure(self.config)
308 311 # Create a KernelManager and start a kernel.
309 312 self.kernel_manager = MappingKernelManager(
310 313 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
311 314 connection_dir = self.profile_dir.security_dir,
312 315 )
313 316 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
314 317 self.notebook_manager.list_notebooks()
315 318
316 319 def init_logging(self):
317 320 super(NotebookApp, self).init_logging()
318 321 # This prevents double log messages because tornado use a root logger that
319 322 # self.log is a child of. The logging module dipatches log messages to a log
320 323 # and all of its ancenstors until propagate is set to False.
321 324 self.log.propagate = False
322 325
323 326 @catch_config_error
324 327 def initialize(self, argv=None):
325 328 super(NotebookApp, self).initialize(argv)
326 329 self.init_configurables()
327 330 self.web_app = NotebookWebApplication(
328 331 self, self.kernel_manager, self.notebook_manager, self.log,
329 332 self.webapp_settings
330 333 )
331 334 if self.certfile:
332 335 ssl_options = dict(certfile=self.certfile)
333 336 if self.keyfile:
334 337 ssl_options['keyfile'] = self.keyfile
335 338 else:
336 339 ssl_options = None
337 340 self.web_app.password = self.password
338 341 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
339 342 if ssl_options is None and not self.ip:
340 343 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
341 344 'but not using any encryption or authentication. This is highly '
342 345 'insecure and not recommended.')
343 346
344 347 # Try random ports centered around the default.
345 348 from random import randint
346 349 n = 50 # Max number of attempts, keep reasonably large.
347 350 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
348 351 try:
349 352 self.http_server.listen(port, self.ip)
350 353 except socket.error, e:
351 354 if e.errno != errno.EADDRINUSE:
352 355 raise
353 356 self.log.info('The port %i is already in use, trying another random port.' % port)
354 357 else:
355 358 self.port = port
356 359 break
357 360
358 361 def start(self):
359 362 ip = self.ip if self.ip else '[all ip addresses on your system]'
360 363 proto = 'https' if self.certfile else 'http'
361 364 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
362 365 ip,
363 366 self.port))
364 367 if self.open_browser:
365 368 ip = self.ip or '127.0.0.1'
366 369 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
367 370 new=2)
368 371 threading.Thread(target=b).start()
369 372
370 373 ioloop.IOLoop.instance().start()
371 374
372 375 #-----------------------------------------------------------------------------
373 376 # Main entry point
374 377 #-----------------------------------------------------------------------------
375 378
376 379 def launch_new_instance():
377 380 app = NotebookApp()
378 381 app.initialize()
379 382 app.start()
380 383
@@ -1,264 +1,255
1 1 """A notebook manager that uses the local file system for storage.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-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 datetime
20 20 import os
21 21 import uuid
22 22 import glob
23 23
24 24 from tornado import web
25 25
26 from IPython.config.application import boolean_flag
27 26 from IPython.config.configurable import LoggingConfigurable
28 27 from IPython.nbformat import current
29 28 from IPython.utils.traitlets import Unicode, List, Dict, Bool
30 29
31 30 #-----------------------------------------------------------------------------
32 # Aliases and Flags
33 #-----------------------------------------------------------------------------
34
35 manager_flags =boolean_flag('script', 'NotebookManager.save_script',
36 'Auto-save a .py script everytime the .ipynb notebook is saved',
37 'Do not auto-save .py scripts for every notebook')
38
39 #-----------------------------------------------------------------------------
40 31 # Classes
41 32 #-----------------------------------------------------------------------------
42 33
43 34 class NotebookManager(LoggingConfigurable):
44 35
45 36 notebook_dir = Unicode(os.getcwd(), config=True, help="""
46 37 The directory to use for notebooks.
47 38 """)
48 39
49 40 save_script = Bool(False, config=True,
50 41 help="""Automatically create a Python script when saving the notebook.
51 42
52 43 For easier use of import, %run and %loadpy across notebooks, a
53 44 <notebook-name>.py script will be created next to any
54 45 <notebook-name>.ipynb on each save. This can also be set with the
55 46 short `--script` flag.
56 47 """
57 48 )
58 49
59 50 filename_ext = Unicode(u'.ipynb')
60 51 allowed_formats = List([u'json',u'py'])
61 52
62 53 # Map notebook_ids to notebook names
63 54 mapping = Dict()
64 55 # Map notebook names to notebook_ids
65 56 rev_mapping = Dict()
66 57
67 58 def list_notebooks(self):
68 59 """List all notebooks in the notebook dir.
69 60
70 61 This returns a list of dicts of the form::
71 62
72 63 dict(notebook_id=notebook,name=name)
73 64 """
74 65 names = glob.glob(os.path.join(self.notebook_dir,
75 66 '*' + self.filename_ext))
76 67 names = [os.path.splitext(os.path.basename(name))[0]
77 68 for name in names]
78 69
79 70 data = []
80 71 for name in names:
81 72 if name not in self.rev_mapping:
82 73 notebook_id = self.new_notebook_id(name)
83 74 else:
84 75 notebook_id = self.rev_mapping[name]
85 76 data.append(dict(notebook_id=notebook_id,name=name))
86 77 data = sorted(data, key=lambda item: item['name'])
87 78 return data
88 79
89 80 def new_notebook_id(self, name):
90 81 """Generate a new notebook_id for a name and store its mappings."""
91 82 # TODO: the following will give stable urls for notebooks, but unless
92 83 # the notebooks are immediately redirected to their new urls when their
93 84 # filemname changes, nasty inconsistencies result. So for now it's
94 85 # disabled and instead we use a random uuid4() call. But we leave the
95 86 # logic here so that we can later reactivate it, whhen the necessary
96 87 # url redirection code is written.
97 88 #notebook_id = unicode(uuid.uuid5(uuid.NAMESPACE_URL,
98 89 # 'file://'+self.get_path_by_name(name).encode('utf-8')))
99 90
100 91 notebook_id = unicode(uuid.uuid4())
101 92
102 93 self.mapping[notebook_id] = name
103 94 self.rev_mapping[name] = notebook_id
104 95 return notebook_id
105 96
106 97 def delete_notebook_id(self, notebook_id):
107 98 """Delete a notebook's id only. This doesn't delete the actual notebook."""
108 99 name = self.mapping[notebook_id]
109 100 del self.mapping[notebook_id]
110 101 del self.rev_mapping[name]
111 102
112 103 def notebook_exists(self, notebook_id):
113 104 """Does a notebook exist?"""
114 105 if notebook_id not in self.mapping:
115 106 return False
116 107 path = self.get_path_by_name(self.mapping[notebook_id])
117 108 return os.path.isfile(path)
118 109
119 110 def find_path(self, notebook_id):
120 111 """Return a full path to a notebook given its notebook_id."""
121 112 try:
122 113 name = self.mapping[notebook_id]
123 114 except KeyError:
124 115 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
125 116 return self.get_path_by_name(name)
126 117
127 118 def get_path_by_name(self, name):
128 119 """Return a full path to a notebook given its name."""
129 120 filename = name + self.filename_ext
130 121 path = os.path.join(self.notebook_dir, filename)
131 122 return path
132 123
133 124 def get_notebook(self, notebook_id, format=u'json'):
134 125 """Get the representation of a notebook in format by notebook_id."""
135 126 format = unicode(format)
136 127 if format not in self.allowed_formats:
137 128 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
138 129 last_modified, nb = self.get_notebook_object(notebook_id)
139 130 kwargs = {}
140 131 if format == 'json':
141 132 # don't split lines for sending over the wire, because it
142 133 # should match the Python in-memory format.
143 134 kwargs['split_lines'] = False
144 135 data = current.writes(nb, format, **kwargs)
145 136 name = nb.get('name','notebook')
146 137 return last_modified, name, data
147 138
148 139 def get_notebook_object(self, notebook_id):
149 140 """Get the NotebookNode representation of a notebook by notebook_id."""
150 141 path = self.find_path(notebook_id)
151 142 if not os.path.isfile(path):
152 143 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
153 144 info = os.stat(path)
154 145 last_modified = datetime.datetime.utcfromtimestamp(info.st_mtime)
155 146 with open(path,'r') as f:
156 147 s = f.read()
157 148 try:
158 149 # v1 and v2 and json in the .ipynb files.
159 150 nb = current.reads(s, u'json')
160 151 except:
161 152 raise web.HTTPError(500, u'Unreadable JSON notebook.')
162 153 if 'name' not in nb:
163 154 nb.name = os.path.split(path)[-1].split(u'.')[0]
164 155 return last_modified, nb
165 156
166 157 def save_new_notebook(self, data, name=None, format=u'json'):
167 158 """Save a new notebook and return its notebook_id.
168 159
169 160 If a name is passed in, it overrides any values in the notebook data
170 161 and the value in the data is updated to use that value.
171 162 """
172 163 if format not in self.allowed_formats:
173 164 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
174 165
175 166 try:
176 167 nb = current.reads(data.decode('utf-8'), format)
177 168 except:
178 169 raise web.HTTPError(400, u'Invalid JSON data')
179 170
180 171 if name is None:
181 172 try:
182 173 name = nb.metadata.name
183 174 except AttributeError:
184 175 raise web.HTTPError(400, u'Missing notebook name')
185 176 nb.metadata.name = name
186 177
187 178 notebook_id = self.new_notebook_id(name)
188 179 self.save_notebook_object(notebook_id, nb)
189 180 return notebook_id
190 181
191 182 def save_notebook(self, notebook_id, data, name=None, format=u'json'):
192 183 """Save an existing notebook by notebook_id."""
193 184 if format not in self.allowed_formats:
194 185 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
195 186
196 187 try:
197 188 nb = current.reads(data.decode('utf-8'), format)
198 189 except:
199 190 raise web.HTTPError(400, u'Invalid JSON data')
200 191
201 192 if name is not None:
202 193 nb.metadata.name = name
203 194 self.save_notebook_object(notebook_id, nb)
204 195
205 196 def save_notebook_object(self, notebook_id, nb):
206 197 """Save an existing notebook object by notebook_id."""
207 198 if notebook_id not in self.mapping:
208 199 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
209 200 old_name = self.mapping[notebook_id]
210 201 try:
211 202 new_name = nb.metadata.name
212 203 except AttributeError:
213 204 raise web.HTTPError(400, u'Missing notebook name')
214 205 path = self.get_path_by_name(new_name)
215 206 try:
216 207 with open(path,'w') as f:
217 208 current.write(nb, f, u'json')
218 209 except Exception as e:
219 210 raise web.HTTPError(400, u'Unexpected error while saving notebook: %s' % e)
220 211 # save .py script as well
221 212 if self.save_script:
222 213 pypath = os.path.splitext(path)[0] + '.py'
223 214 try:
224 215 with open(pypath,'w') as f:
225 216 current.write(nb, f, u'py')
226 217 except Exception as e:
227 218 raise web.HTTPError(400, u'Unexpected error while saving notebook as script: %s' % e)
228 219
229 220 if old_name != new_name:
230 221 old_path = self.get_path_by_name(old_name)
231 222 if os.path.isfile(old_path):
232 223 os.unlink(old_path)
233 224 if self.save_script:
234 225 old_pypath = os.path.splitext(old_path)[0] + '.py'
235 226 if os.path.isfile(old_pypath):
236 227 os.unlink(old_pypath)
237 228 self.mapping[notebook_id] = new_name
238 229 self.rev_mapping[new_name] = notebook_id
239 230
240 231 def delete_notebook(self, notebook_id):
241 232 """Delete notebook by notebook_id."""
242 233 path = self.find_path(notebook_id)
243 234 if not os.path.isfile(path):
244 235 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
245 236 os.unlink(path)
246 237 self.delete_notebook_id(notebook_id)
247 238
248 239 def new_notebook(self):
249 240 """Create a new notebook and returns its notebook_id."""
250 241 i = 0
251 242 while True:
252 243 name = u'Untitled%i' % i
253 244 path = self.get_path_by_name(name)
254 245 if not os.path.isfile(path):
255 246 break
256 247 else:
257 248 i = i+1
258 249 notebook_id = self.new_notebook_id(name)
259 250 metadata = current.new_metadata(name=name)
260 251 nb = current.new_notebook(metadata=metadata)
261 252 with open(path,'w') as f:
262 253 current.write(nb, f, u'json')
263 254 return notebook_id
264 255
General Comments 0
You need to be logged in to leave comments. Login now