##// END OF EJS Templates
log local mathjax path
MinRK -
Show More
@@ -1,611 +1,611
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
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 random
24 24 import re
25 25 import select
26 26 import signal
27 27 import socket
28 28 import sys
29 29 import threading
30 30 import time
31 31 import webbrowser
32 32
33 33 # Third party
34 34 import zmq
35 35
36 36 # Install the pyzmq ioloop. This has to be done before anything else from
37 37 # tornado is imported.
38 38 from zmq.eventloop import ioloop
39 39 ioloop.install()
40 40
41 41 from tornado import httpserver
42 42 from tornado import web
43 43
44 44 # Our own libraries
45 45 from .kernelmanager import MappingKernelManager
46 46 from .handlers import (LoginHandler, LogoutHandler,
47 47 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
48 48 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
49 49 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
50 50 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
51 51 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
52 52 FileFindHandler,
53 53 )
54 54 from .notebookmanager import NotebookManager
55 55 from .clustermanager import ClusterManager
56 56
57 57 from IPython.config.application import catch_config_error, boolean_flag
58 58 from IPython.core.application import BaseIPythonApplication
59 59 from IPython.core.profiledir import ProfileDir
60 60 from IPython.frontend.consoleapp import IPythonConsoleApp
61 61 from IPython.lib.kernel import swallow_argv
62 62 from IPython.zmq.session import Session, default_secure
63 63 from IPython.zmq.zmqshell import ZMQInteractiveShell
64 64 from IPython.zmq.ipkernel import (
65 65 flags as ipkernel_flags,
66 66 aliases as ipkernel_aliases,
67 67 IPKernelApp
68 68 )
69 69 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
70 70 from IPython.utils import py3compat
71 71 from IPython.utils.path import filefind
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Module globals
75 75 #-----------------------------------------------------------------------------
76 76
77 77 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
78 78 _kernel_action_regex = r"(?P<action>restart|interrupt)"
79 79 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
80 80 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
81 81 _cluster_action_regex = r"(?P<action>start|stop)"
82 82
83 83
84 84 LOCALHOST = '127.0.0.1'
85 85
86 86 _examples = """
87 87 ipython notebook # start the notebook
88 88 ipython notebook --profile=sympy # use the sympy profile
89 89 ipython notebook --pylab=inline # pylab in inline plotting mode
90 90 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
91 91 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
92 92 """
93 93
94 94 #-----------------------------------------------------------------------------
95 95 # Helper functions
96 96 #-----------------------------------------------------------------------------
97 97
98 98 def url_path_join(a,b):
99 99 if a.endswith('/') and b.startswith('/'):
100 100 return a[:-1]+b
101 101 else:
102 102 return a+b
103 103
104 104 def random_ports(port, n):
105 105 """Generate a list of n random ports near the given port.
106 106
107 107 The first 5 ports will be sequential, and the remaining n-5 will be
108 108 randomly selected in the range [port-2*n, port+2*n].
109 109 """
110 110 for i in range(min(5, n)):
111 111 yield port + i
112 112 for i in range(n-5):
113 113 yield port + random.randint(-2*n, 2*n)
114 114
115 115 #-----------------------------------------------------------------------------
116 116 # The Tornado web application
117 117 #-----------------------------------------------------------------------------
118 118
119 119 class NotebookWebApplication(web.Application):
120 120
121 121 def __init__(self, ipython_app, kernel_manager, notebook_manager,
122 122 cluster_manager, log,
123 123 base_project_url, settings_overrides):
124 124 handlers = [
125 125 (r"/", ProjectDashboardHandler),
126 126 (r"/login", LoginHandler),
127 127 (r"/logout", LogoutHandler),
128 128 (r"/new", NewHandler),
129 129 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
130 130 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
131 131 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
132 132 (r"/kernels", MainKernelHandler),
133 133 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
134 134 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
135 135 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
136 136 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
137 137 (r"/notebooks", NotebookRootHandler),
138 138 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
139 139 (r"/rstservice/render", RSTHandler),
140 140 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
141 141 (r"/clusters", MainClusterHandler),
142 142 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
143 143 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
144 144 ]
145 145
146 146 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
147 147 # base_project_url will always be unicode, which will in turn
148 148 # make the patterns unicode, and ultimately result in unicode
149 149 # keys in kwargs to handler._execute(**kwargs) in tornado.
150 150 # This enforces that base_project_url be ascii in that situation.
151 151 #
152 152 # Note that the URLs these patterns check against are escaped,
153 153 # and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'.
154 154 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
155 155
156 156 settings = dict(
157 157 template_path=os.path.join(os.path.dirname(__file__), "templates"),
158 158 static_path=ipython_app.static_file_path,
159 159 static_handler_class = FileFindHandler,
160 160 cookie_secret=os.urandom(1024),
161 161 login_url="%s/login"%(base_project_url.rstrip('/')),
162 162 )
163 163
164 164 # allow custom overrides for the tornado web app.
165 165 settings.update(settings_overrides)
166 166
167 167 # prepend base_project_url onto the patterns that we match
168 168 new_handlers = []
169 169 for handler in handlers:
170 170 pattern = url_path_join(base_project_url, handler[0])
171 171 new_handler = tuple([pattern]+list(handler[1:]))
172 172 new_handlers.append( new_handler )
173 173
174 174 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
175 175
176 176 self.kernel_manager = kernel_manager
177 177 self.notebook_manager = notebook_manager
178 178 self.cluster_manager = cluster_manager
179 179 self.ipython_app = ipython_app
180 180 self.read_only = self.ipython_app.read_only
181 181 self.log = log
182 182
183 183
184 184 #-----------------------------------------------------------------------------
185 185 # Aliases and Flags
186 186 #-----------------------------------------------------------------------------
187 187
188 188 flags = dict(ipkernel_flags)
189 189 flags['no-browser']=(
190 190 {'NotebookApp' : {'open_browser' : False}},
191 191 "Don't open the notebook in a browser after startup."
192 192 )
193 193 flags['no-mathjax']=(
194 194 {'NotebookApp' : {'enable_mathjax' : False}},
195 195 """Disable MathJax
196 196
197 197 MathJax is the javascript library IPython uses to render math/LaTeX. It is
198 198 very large, so you may want to disable it if you have a slow internet
199 199 connection, or for offline use of the notebook.
200 200
201 201 When disabled, equations etc. will appear as their untransformed TeX source.
202 202 """
203 203 )
204 204 flags['read-only'] = (
205 205 {'NotebookApp' : {'read_only' : True}},
206 206 """Allow read-only access to notebooks.
207 207
208 208 When using a password to protect the notebook server, this flag
209 209 allows unauthenticated clients to view the notebook list, and
210 210 individual notebooks, but not edit them, start kernels, or run
211 211 code.
212 212
213 213 If no password is set, the server will be entirely read-only.
214 214 """
215 215 )
216 216
217 217 # Add notebook manager flags
218 218 flags.update(boolean_flag('script', 'NotebookManager.save_script',
219 219 'Auto-save a .py script everytime the .ipynb notebook is saved',
220 220 'Do not auto-save .py scripts for every notebook'))
221 221
222 222 # the flags that are specific to the frontend
223 223 # these must be scrubbed before being passed to the kernel,
224 224 # or it will raise an error on unrecognized flags
225 225 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
226 226
227 227 aliases = dict(ipkernel_aliases)
228 228
229 229 aliases.update({
230 230 'ip': 'NotebookApp.ip',
231 231 'port': 'NotebookApp.port',
232 232 'port-retries': 'NotebookApp.port_retries',
233 233 'keyfile': 'NotebookApp.keyfile',
234 234 'certfile': 'NotebookApp.certfile',
235 235 'notebook-dir': 'NotebookManager.notebook_dir',
236 236 'browser': 'NotebookApp.browser',
237 237 })
238 238
239 239 # remove ipkernel flags that are singletons, and don't make sense in
240 240 # multi-kernel evironment:
241 241 aliases.pop('f', None)
242 242
243 243 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
244 244 u'notebook-dir']
245 245
246 246 #-----------------------------------------------------------------------------
247 247 # NotebookApp
248 248 #-----------------------------------------------------------------------------
249 249
250 250 class NotebookApp(BaseIPythonApplication):
251 251
252 252 name = 'ipython-notebook'
253 253 default_config_file_name='ipython_notebook_config.py'
254 254
255 255 description = """
256 256 The IPython HTML Notebook.
257 257
258 258 This launches a Tornado based HTML Notebook Server that serves up an
259 259 HTML5/Javascript Notebook client.
260 260 """
261 261 examples = _examples
262 262
263 263 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager]
264 264 flags = Dict(flags)
265 265 aliases = Dict(aliases)
266 266
267 267 kernel_argv = List(Unicode)
268 268
269 269 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
270 270 default_value=logging.INFO,
271 271 config=True,
272 272 help="Set the log level by value or name.")
273 273
274 274 # create requested profiles by default, if they don't exist:
275 275 auto_create = Bool(True)
276 276
277 277 # file to be opened in the notebook server
278 278 file_to_run = Unicode('')
279 279
280 280 # Network related information.
281 281
282 282 ip = Unicode(LOCALHOST, config=True,
283 283 help="The IP address the notebook server will listen on."
284 284 )
285 285
286 286 def _ip_changed(self, name, old, new):
287 287 if new == u'*': self.ip = u''
288 288
289 289 port = Integer(8888, config=True,
290 290 help="The port the notebook server will listen on."
291 291 )
292 292 port_retries = Integer(50, config=True,
293 293 help="The number of additional ports to try if the specified port is not available."
294 294 )
295 295
296 296 certfile = Unicode(u'', config=True,
297 297 help="""The full path to an SSL/TLS certificate file."""
298 298 )
299 299
300 300 keyfile = Unicode(u'', config=True,
301 301 help="""The full path to a private key file for usage with SSL/TLS."""
302 302 )
303 303
304 304 password = Unicode(u'', config=True,
305 305 help="""Hashed password to use for web authentication.
306 306
307 307 To generate, type in a python/IPython shell:
308 308
309 309 from IPython.lib import passwd; passwd()
310 310
311 311 The string should be of the form type:salt:hashed-password.
312 312 """
313 313 )
314 314
315 315 open_browser = Bool(True, config=True,
316 316 help="""Whether to open in a browser after starting.
317 317 The specific browser used is platform dependent and
318 318 determined by the python standard library `webbrowser`
319 319 module, unless it is overridden using the --browser
320 320 (NotebookApp.browser) configuration option.
321 321 """)
322 322
323 323 browser = Unicode(u'', config=True,
324 324 help="""Specify what command to use to invoke a web
325 325 browser when opening the notebook. If not specified, the
326 326 default browser will be determined by the `webbrowser`
327 327 standard library module, which allows setting of the
328 328 BROWSER environment variable to override it.
329 329 """)
330 330
331 331 read_only = Bool(False, config=True,
332 332 help="Whether to prevent editing/execution of notebooks."
333 333 )
334 334
335 335 webapp_settings = Dict(config=True,
336 336 help="Supply overrides for the tornado.web.Application that the "
337 337 "IPython notebook uses.")
338 338
339 339 enable_mathjax = Bool(True, config=True,
340 340 help="""Whether to enable MathJax for typesetting math/TeX
341 341
342 342 MathJax is the javascript library IPython uses to render math/LaTeX. It is
343 343 very large, so you may want to disable it if you have a slow internet
344 344 connection, or for offline use of the notebook.
345 345
346 346 When disabled, equations etc. will appear as their untransformed TeX source.
347 347 """
348 348 )
349 349 def _enable_mathjax_changed(self, name, old, new):
350 350 """set mathjax url to empty if mathjax is disabled"""
351 351 if not new:
352 352 self.mathjax_url = u''
353 353
354 354 base_project_url = Unicode('/', config=True,
355 355 help='''The base URL for the notebook server''')
356 356 base_kernel_url = Unicode('/', config=True,
357 357 help='''The base URL for the kernel server''')
358 358 websocket_host = Unicode("", config=True,
359 359 help="""The hostname for the websocket server."""
360 360 )
361 361
362 362 extra_static_paths = List(Unicode, config=True,
363 363 help="""Extra paths to search for serving static files.
364 364
365 365 This allows adding javascript/css to be available from the notebook server machine,
366 366 or overriding individual files in the IPython"""
367 367 )
368 368 def _extra_static_paths_default(self):
369 369 return [os.path.join(self.profile_dir.location, 'static')]
370 370
371 371 @property
372 372 def static_file_path(self):
373 373 """return extra paths + the default location"""
374 374 return self.extra_static_paths + [os.path.join(os.path.dirname(__file__), "static")]
375 375
376 376 mathjax_url = Unicode("", config=True,
377 377 help="""The url for MathJax.js."""
378 378 )
379 379 def _mathjax_url_default(self):
380 380 if not self.enable_mathjax:
381 381 return u''
382 382 static_url_prefix = self.webapp_settings.get("static_url_prefix",
383 383 "/static/")
384 384 try:
385 filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
385 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
386 386 except IOError:
387 387 if self.certfile:
388 388 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
389 389 base = u"https://c328740.ssl.cf1.rackcdn.com"
390 390 else:
391 391 base = u"http://cdn.mathjax.org"
392 392
393 393 url = base + u"/mathjax/latest/MathJax.js"
394 394 self.log.info("Using MathJax from CDN: %s", url)
395 395 return url
396 396 else:
397 self.log.info("Using local MathJax")
397 self.log.info("Using local MathJax from %s" % mathjax)
398 398 return static_url_prefix+u"mathjax/MathJax.js"
399 399
400 400 def _mathjax_url_changed(self, name, old, new):
401 401 if new and not self.enable_mathjax:
402 402 # enable_mathjax=False overrides mathjax_url
403 403 self.mathjax_url = u''
404 404 else:
405 405 self.log.info("Using MathJax: %s", new)
406 406
407 407 def parse_command_line(self, argv=None):
408 408 super(NotebookApp, self).parse_command_line(argv)
409 409 if argv is None:
410 410 argv = sys.argv[1:]
411 411
412 412 # Scrub frontend-specific flags
413 413 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
414 414 # Kernel should inherit default config file from frontend
415 415 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
416 416
417 417 if self.extra_args:
418 418 f = os.path.abspath(self.extra_args[0])
419 419 if os.path.isdir(f):
420 420 nbdir = f
421 421 else:
422 422 self.file_to_run = f
423 423 nbdir = os.path.dirname(f)
424 424 self.config.NotebookManager.notebook_dir = nbdir
425 425
426 426 def init_configurables(self):
427 427 # force Session default to be secure
428 428 default_secure(self.config)
429 429 self.kernel_manager = MappingKernelManager(
430 430 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
431 431 connection_dir = self.profile_dir.security_dir,
432 432 )
433 433 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
434 434 self.log.info("Serving notebooks from %s", self.notebook_manager.notebook_dir)
435 435 self.notebook_manager.list_notebooks()
436 436 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
437 437 self.cluster_manager.update_profiles()
438 438
439 439 def init_logging(self):
440 440 # This prevents double log messages because tornado use a root logger that
441 441 # self.log is a child of. The logging module dipatches log messages to a log
442 442 # and all of its ancenstors until propagate is set to False.
443 443 self.log.propagate = False
444 444
445 445 def init_webapp(self):
446 446 """initialize tornado webapp and httpserver"""
447 447 self.web_app = NotebookWebApplication(
448 448 self, self.kernel_manager, self.notebook_manager,
449 449 self.cluster_manager, self.log,
450 450 self.base_project_url, self.webapp_settings
451 451 )
452 452 if self.certfile:
453 453 ssl_options = dict(certfile=self.certfile)
454 454 if self.keyfile:
455 455 ssl_options['keyfile'] = self.keyfile
456 456 else:
457 457 ssl_options = None
458 458 self.web_app.password = self.password
459 459 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
460 460 if ssl_options is None and not self.ip and not (self.read_only and not self.password):
461 461 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
462 462 'but not using any encryption or authentication. This is highly '
463 463 'insecure and not recommended.')
464 464
465 465 success = None
466 466 for port in random_ports(self.port, self.port_retries+1):
467 467 try:
468 468 self.http_server.listen(port, self.ip)
469 469 except socket.error as e:
470 470 if e.errno != errno.EADDRINUSE:
471 471 raise
472 472 self.log.info('The port %i is already in use, trying another random port.' % port)
473 473 else:
474 474 self.port = port
475 475 success = True
476 476 break
477 477 if not success:
478 478 self.log.critical('ERROR: the notebook server could not be started because '
479 479 'no available port could be found.')
480 480 self.exit(1)
481 481
482 482 def init_signal(self):
483 483 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
484 484 # safely extract zmq version info:
485 485 try:
486 486 zmq_v = zmq.pyzmq_version_info()
487 487 except AttributeError:
488 488 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
489 489 if 'dev' in zmq.__version__:
490 490 zmq_v.append(999)
491 491 zmq_v = tuple(zmq_v)
492 492 if zmq_v >= (2,1,9) and not sys.platform.startswith('win'):
493 493 # This won't work with 2.1.7 and
494 494 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
495 495 # but it will work
496 496 signal.signal(signal.SIGINT, self._handle_sigint)
497 497 signal.signal(signal.SIGTERM, self._signal_stop)
498 498
499 499 def _handle_sigint(self, sig, frame):
500 500 """SIGINT handler spawns confirmation dialog"""
501 501 # register more forceful signal handler for ^C^C case
502 502 signal.signal(signal.SIGINT, self._signal_stop)
503 503 # request confirmation dialog in bg thread, to avoid
504 504 # blocking the App
505 505 thread = threading.Thread(target=self._confirm_exit)
506 506 thread.daemon = True
507 507 thread.start()
508 508
509 509 def _restore_sigint_handler(self):
510 510 """callback for restoring original SIGINT handler"""
511 511 signal.signal(signal.SIGINT, self._handle_sigint)
512 512
513 513 def _confirm_exit(self):
514 514 """confirm shutdown on ^C
515 515
516 516 A second ^C, or answering 'y' within 5s will cause shutdown,
517 517 otherwise original SIGINT handler will be restored.
518 518
519 519 This doesn't work on Windows.
520 520 """
521 521 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
522 522 time.sleep(0.1)
523 523 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
524 524 sys.stdout.flush()
525 525 r,w,x = select.select([sys.stdin], [], [], 5)
526 526 if r:
527 527 line = sys.stdin.readline()
528 528 if line.lower().startswith('y'):
529 529 self.log.critical("Shutdown confirmed")
530 530 ioloop.IOLoop.instance().stop()
531 531 return
532 532 else:
533 533 print "No answer for 5s:",
534 534 print "resuming operation..."
535 535 # no answer, or answer is no:
536 536 # set it back to original SIGINT handler
537 537 # use IOLoop.add_callback because signal.signal must be called
538 538 # from main thread
539 539 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
540 540
541 541 def _signal_stop(self, sig, frame):
542 542 self.log.critical("received signal %s, stopping", sig)
543 543 ioloop.IOLoop.instance().stop()
544 544
545 545 @catch_config_error
546 546 def initialize(self, argv=None):
547 547 self.init_logging()
548 548 super(NotebookApp, self).initialize(argv)
549 549 self.init_configurables()
550 550 self.init_webapp()
551 551 self.init_signal()
552 552
553 553 def cleanup_kernels(self):
554 554 """shutdown all kernels
555 555
556 556 The kernels will shutdown themselves when this process no longer exists,
557 557 but explicit shutdown allows the KernelManagers to cleanup the connection files.
558 558 """
559 559 self.log.info('Shutting down kernels')
560 560 km = self.kernel_manager
561 561 # copy list, since shutdown_kernel deletes keys
562 562 for kid in list(km.kernel_ids):
563 563 km.shutdown_kernel(kid)
564 564
565 565 def start(self):
566 566 ip = self.ip if self.ip else '[all ip addresses on your system]'
567 567 proto = 'https' if self.certfile else 'http'
568 568 info = self.log.info
569 569 info("The IPython Notebook is running at: %s://%s:%i%s" %
570 570 (proto, ip, self.port,self.base_project_url) )
571 571 info("Use Control-C to stop this server and shut down all kernels.")
572 572
573 573 if self.open_browser or self.file_to_run:
574 574 ip = self.ip or '127.0.0.1'
575 575 try:
576 576 browser = webbrowser.get(self.browser or None)
577 577 except webbrowser.Error as e:
578 578 self.log.warn('No web browser found: %s.' % e)
579 579 browser = None
580 580
581 581 if self.file_to_run:
582 582 filename, _ = os.path.splitext(os.path.basename(self.file_to_run))
583 583 for nb in self.notebook_manager.list_notebooks():
584 584 if filename == nb['name']:
585 585 url = nb['notebook_id']
586 586 break
587 587 else:
588 588 url = ''
589 589 else:
590 590 url = ''
591 591 if browser:
592 592 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
593 593 self.port, self.base_project_url, url), new=2)
594 594 threading.Thread(target=b).start()
595 595 try:
596 596 ioloop.IOLoop.instance().start()
597 597 except KeyboardInterrupt:
598 598 info("Interrupted...")
599 599 finally:
600 600 self.cleanup_kernels()
601 601
602 602
603 603 #-----------------------------------------------------------------------------
604 604 # Main entry point
605 605 #-----------------------------------------------------------------------------
606 606
607 607 def launch_new_instance():
608 608 app = NotebookApp.instance()
609 609 app.initialize()
610 610 app.start()
611 611
General Comments 0
You need to be logged in to leave comments. Login now