##// END OF EJS Templates
remove again a few other occurences
Matthias BUSSONNIER -
Show More
@@ -1,725 +1,724
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) 2013 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 select
25 25 import signal
26 26 import socket
27 27 import sys
28 28 import threading
29 29 import time
30 30 import webbrowser
31 31
32 32
33 33 # Third party
34 34 # check for pyzmq 2.1.11
35 35 from IPython.utils.zmqrelated import check_for_zmq
36 36 check_for_zmq('2.1.11', 'IPython.html')
37 37
38 38 from jinja2 import Environment, FileSystemLoader
39 39
40 40 # Install the pyzmq ioloop. This has to be done before anything else from
41 41 # tornado is imported.
42 42 from zmq.eventloop import ioloop
43 43 ioloop.install()
44 44
45 45 # check for tornado 2.1.0
46 46 msg = "The IPython Notebook requires tornado >= 2.1.0"
47 47 try:
48 48 import tornado
49 49 except ImportError:
50 50 raise ImportError(msg)
51 51 try:
52 52 version_info = tornado.version_info
53 53 except AttributeError:
54 54 raise ImportError(msg + ", but you have < 1.1.0")
55 55 if version_info < (2,1,0):
56 56 raise ImportError(msg + ", but you have %s" % tornado.version)
57 57
58 58 from tornado import httpserver
59 59 from tornado import web
60 60
61 61 # Our own libraries
62 62 from IPython.html import DEFAULT_STATIC_FILES_PATH
63 63
64 64 from .services.kernels.kernelmanager import MappingKernelManager
65 65 from .services.notebooks.nbmanager import NotebookManager
66 66 from .services.notebooks.filenbmanager import FileNotebookManager
67 67 from .services.clusters.clustermanager import ClusterManager
68 68
69 69 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
70 70
71 71 from IPython.config.application import catch_config_error, boolean_flag
72 72 from IPython.core.application import BaseIPythonApplication
73 73 from IPython.consoleapp import IPythonConsoleApp
74 74 from IPython.kernel import swallow_argv
75 75 from IPython.kernel.zmq.session import default_secure
76 76 from IPython.kernel.zmq.kernelapp import (
77 77 kernel_flags,
78 78 kernel_aliases,
79 79 )
80 80 from IPython.utils.importstring import import_item
81 81 from IPython.utils.localinterfaces import LOCALHOST
82 82 from IPython.utils import submodule
83 83 from IPython.utils.traitlets import (
84 84 Dict, Unicode, Integer, List, Bool, Bytes,
85 85 DottedObjectName
86 86 )
87 87 from IPython.utils import py3compat
88 88 from IPython.utils.path import filefind
89 89
90 90 from .utils import url_path_join
91 91
92 92 #-----------------------------------------------------------------------------
93 93 # Module globals
94 94 #-----------------------------------------------------------------------------
95 95
96 96 _examples = """
97 97 ipython notebook # start the notebook
98 98 ipython notebook --profile=sympy # use the sympy profile
99 ipython notebook --pylab=inline # pylab in inline plotting mode
99 ipython notebook --matplotlib=inline # inline plotting mode
100 100 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
101 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
102 101 """
103 102
104 103 #-----------------------------------------------------------------------------
105 104 # Helper functions
106 105 #-----------------------------------------------------------------------------
107 106
108 107 def random_ports(port, n):
109 108 """Generate a list of n random ports near the given port.
110 109
111 110 The first 5 ports will be sequential, and the remaining n-5 will be
112 111 randomly selected in the range [port-2*n, port+2*n].
113 112 """
114 113 for i in range(min(5, n)):
115 114 yield port + i
116 115 for i in range(n-5):
117 116 yield port + random.randint(-2*n, 2*n)
118 117
119 118 def load_handlers(name):
120 119 """Load the (URL pattern, handler) tuples for each component."""
121 120 name = 'IPython.html.' + name
122 121 mod = __import__(name, fromlist=['default_handlers'])
123 122 return mod.default_handlers
124 123
125 124 #-----------------------------------------------------------------------------
126 125 # The Tornado web application
127 126 #-----------------------------------------------------------------------------
128 127
129 128 class NotebookWebApplication(web.Application):
130 129
131 130 def __init__(self, ipython_app, kernel_manager, notebook_manager,
132 131 cluster_manager, log,
133 132 base_project_url, settings_overrides):
134 133
135 134 settings = self.init_settings(
136 135 ipython_app, kernel_manager, notebook_manager, cluster_manager,
137 136 log, base_project_url, settings_overrides)
138 137 handlers = self.init_handlers(settings)
139 138
140 139 super(NotebookWebApplication, self).__init__(handlers, **settings)
141 140
142 141 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
143 142 cluster_manager, log,
144 143 base_project_url, settings_overrides):
145 144 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
146 145 # base_project_url will always be unicode, which will in turn
147 146 # make the patterns unicode, and ultimately result in unicode
148 147 # keys in kwargs to handler._execute(**kwargs) in tornado.
149 148 # This enforces that base_project_url be ascii in that situation.
150 149 #
151 150 # Note that the URLs these patterns check against are escaped,
152 151 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
153 152 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
154 153 template_path = os.path.join(os.path.dirname(__file__), "templates")
155 154 settings = dict(
156 155 # basics
157 156 base_project_url=base_project_url,
158 157 base_kernel_url=ipython_app.base_kernel_url,
159 158 template_path=template_path,
160 159 static_path=ipython_app.static_file_path,
161 160 static_handler_class = FileFindHandler,
162 161 static_url_prefix = url_path_join(base_project_url,'/static/'),
163 162
164 163 # authentication
165 164 cookie_secret=ipython_app.cookie_secret,
166 165 login_url=url_path_join(base_project_url,'/login'),
167 166 password=ipython_app.password,
168 167
169 168 # managers
170 169 kernel_manager=kernel_manager,
171 170 notebook_manager=notebook_manager,
172 171 cluster_manager=cluster_manager,
173 172
174 173 # IPython stuff
175 174 mathjax_url=ipython_app.mathjax_url,
176 175 config=ipython_app.config,
177 176 use_less=ipython_app.use_less,
178 177 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
179 178 )
180 179
181 180 # allow custom overrides for the tornado web app.
182 181 settings.update(settings_overrides)
183 182 return settings
184 183
185 184 def init_handlers(self, settings):
186 185 # Load the (URL pattern, handler) tuples for each component.
187 186 handlers = []
188 187 handlers.extend(load_handlers('base.handlers'))
189 188 handlers.extend(load_handlers('tree.handlers'))
190 189 handlers.extend(load_handlers('auth.login'))
191 190 handlers.extend(load_handlers('auth.logout'))
192 191 handlers.extend(load_handlers('notebook.handlers'))
193 192 handlers.extend(load_handlers('services.kernels.handlers'))
194 193 handlers.extend(load_handlers('services.notebooks.handlers'))
195 194 handlers.extend(load_handlers('services.clusters.handlers'))
196 195 handlers.extend([
197 196 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
198 197 ])
199 198 # prepend base_project_url onto the patterns that we match
200 199 new_handlers = []
201 200 for handler in handlers:
202 201 pattern = url_path_join(settings['base_project_url'], handler[0])
203 202 new_handler = tuple([pattern] + list(handler[1:]))
204 203 new_handlers.append(new_handler)
205 204 return new_handlers
206 205
207 206
208 207
209 208 #-----------------------------------------------------------------------------
210 209 # Aliases and Flags
211 210 #-----------------------------------------------------------------------------
212 211
213 212 flags = dict(kernel_flags)
214 213 flags['no-browser']=(
215 214 {'NotebookApp' : {'open_browser' : False}},
216 215 "Don't open the notebook in a browser after startup."
217 216 )
218 217 flags['no-mathjax']=(
219 218 {'NotebookApp' : {'enable_mathjax' : False}},
220 219 """Disable MathJax
221 220
222 221 MathJax is the javascript library IPython uses to render math/LaTeX. It is
223 222 very large, so you may want to disable it if you have a slow internet
224 223 connection, or for offline use of the notebook.
225 224
226 225 When disabled, equations etc. will appear as their untransformed TeX source.
227 226 """
228 227 )
229 228
230 229 # Add notebook manager flags
231 230 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
232 231 'Auto-save a .py script everytime the .ipynb notebook is saved',
233 232 'Do not auto-save .py scripts for every notebook'))
234 233
235 234 # the flags that are specific to the frontend
236 235 # these must be scrubbed before being passed to the kernel,
237 236 # or it will raise an error on unrecognized flags
238 237 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
239 238
240 239 aliases = dict(kernel_aliases)
241 240
242 241 aliases.update({
243 242 'ip': 'NotebookApp.ip',
244 243 'port': 'NotebookApp.port',
245 244 'port-retries': 'NotebookApp.port_retries',
246 245 'transport': 'KernelManager.transport',
247 246 'keyfile': 'NotebookApp.keyfile',
248 247 'certfile': 'NotebookApp.certfile',
249 248 'notebook-dir': 'NotebookManager.notebook_dir',
250 249 'browser': 'NotebookApp.browser',
251 250 })
252 251
253 252 # remove ipkernel flags that are singletons, and don't make sense in
254 253 # multi-kernel evironment:
255 254 aliases.pop('f', None)
256 255
257 256 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
258 257 u'notebook-dir']
259 258
260 259 #-----------------------------------------------------------------------------
261 260 # NotebookApp
262 261 #-----------------------------------------------------------------------------
263 262
264 263 class NotebookApp(BaseIPythonApplication):
265 264
266 265 name = 'ipython-notebook'
267 266
268 267 description = """
269 268 The IPython HTML Notebook.
270 269
271 270 This launches a Tornado based HTML Notebook Server that serves up an
272 271 HTML5/Javascript Notebook client.
273 272 """
274 273 examples = _examples
275 274
276 275 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
277 276 FileNotebookManager]
278 277 flags = Dict(flags)
279 278 aliases = Dict(aliases)
280 279
281 280 kernel_argv = List(Unicode)
282 281
283 282 def _log_level_default(self):
284 283 return logging.INFO
285 284
286 285 def _log_format_default(self):
287 286 """override default log format to include time"""
288 287 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
289 288
290 289 # create requested profiles by default, if they don't exist:
291 290 auto_create = Bool(True)
292 291
293 292 # file to be opened in the notebook server
294 293 file_to_run = Unicode('')
295 294
296 295 # Network related information.
297 296
298 297 ip = Unicode(LOCALHOST, config=True,
299 298 help="The IP address the notebook server will listen on."
300 299 )
301 300
302 301 def _ip_changed(self, name, old, new):
303 302 if new == u'*': self.ip = u''
304 303
305 304 port = Integer(8888, config=True,
306 305 help="The port the notebook server will listen on."
307 306 )
308 307 port_retries = Integer(50, config=True,
309 308 help="The number of additional ports to try if the specified port is not available."
310 309 )
311 310
312 311 certfile = Unicode(u'', config=True,
313 312 help="""The full path to an SSL/TLS certificate file."""
314 313 )
315 314
316 315 keyfile = Unicode(u'', config=True,
317 316 help="""The full path to a private key file for usage with SSL/TLS."""
318 317 )
319 318
320 319 cookie_secret = Bytes(b'', config=True,
321 320 help="""The random bytes used to secure cookies.
322 321 By default this is a new random number every time you start the Notebook.
323 322 Set it to a value in a config file to enable logins to persist across server sessions.
324 323
325 324 Note: Cookie secrets should be kept private, do not share config files with
326 325 cookie_secret stored in plaintext (you can read the value from a file).
327 326 """
328 327 )
329 328 def _cookie_secret_default(self):
330 329 return os.urandom(1024)
331 330
332 331 password = Unicode(u'', config=True,
333 332 help="""Hashed password to use for web authentication.
334 333
335 334 To generate, type in a python/IPython shell:
336 335
337 336 from IPython.lib import passwd; passwd()
338 337
339 338 The string should be of the form type:salt:hashed-password.
340 339 """
341 340 )
342 341
343 342 open_browser = Bool(True, config=True,
344 343 help="""Whether to open in a browser after starting.
345 344 The specific browser used is platform dependent and
346 345 determined by the python standard library `webbrowser`
347 346 module, unless it is overridden using the --browser
348 347 (NotebookApp.browser) configuration option.
349 348 """)
350 349
351 350 browser = Unicode(u'', config=True,
352 351 help="""Specify what command to use to invoke a web
353 352 browser when opening the notebook. If not specified, the
354 353 default browser will be determined by the `webbrowser`
355 354 standard library module, which allows setting of the
356 355 BROWSER environment variable to override it.
357 356 """)
358 357
359 358 use_less = Bool(False, config=True,
360 359 help="""Wether to use Browser Side less-css parsing
361 360 instead of compiled css version in templates that allows
362 361 it. This is mainly convenient when working on the less
363 362 file to avoid a build step, or if user want to overwrite
364 363 some of the less variables without having to recompile
365 364 everything.
366 365
367 366 You will need to install the less.js component in the static directory
368 367 either in the source tree or in your profile folder.
369 368 """)
370 369
371 370 webapp_settings = Dict(config=True,
372 371 help="Supply overrides for the tornado.web.Application that the "
373 372 "IPython notebook uses.")
374 373
375 374 enable_mathjax = Bool(True, config=True,
376 375 help="""Whether to enable MathJax for typesetting math/TeX
377 376
378 377 MathJax is the javascript library IPython uses to render math/LaTeX. It is
379 378 very large, so you may want to disable it if you have a slow internet
380 379 connection, or for offline use of the notebook.
381 380
382 381 When disabled, equations etc. will appear as their untransformed TeX source.
383 382 """
384 383 )
385 384 def _enable_mathjax_changed(self, name, old, new):
386 385 """set mathjax url to empty if mathjax is disabled"""
387 386 if not new:
388 387 self.mathjax_url = u''
389 388
390 389 base_project_url = Unicode('/', config=True,
391 390 help='''The base URL for the notebook server.
392 391
393 392 Leading and trailing slashes can be omitted,
394 393 and will automatically be added.
395 394 ''')
396 395 def _base_project_url_changed(self, name, old, new):
397 396 if not new.startswith('/'):
398 397 self.base_project_url = '/'+new
399 398 elif not new.endswith('/'):
400 399 self.base_project_url = new+'/'
401 400
402 401 base_kernel_url = Unicode('/', config=True,
403 402 help='''The base URL for the kernel server
404 403
405 404 Leading and trailing slashes can be omitted,
406 405 and will automatically be added.
407 406 ''')
408 407 def _base_kernel_url_changed(self, name, old, new):
409 408 if not new.startswith('/'):
410 409 self.base_kernel_url = '/'+new
411 410 elif not new.endswith('/'):
412 411 self.base_kernel_url = new+'/'
413 412
414 413 websocket_url = Unicode("", config=True,
415 414 help="""The base URL for the websocket server,
416 415 if it differs from the HTTP server (hint: it almost certainly doesn't).
417 416
418 417 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
419 418 """
420 419 )
421 420
422 421 extra_static_paths = List(Unicode, config=True,
423 422 help="""Extra paths to search for serving static files.
424 423
425 424 This allows adding javascript/css to be available from the notebook server machine,
426 425 or overriding individual files in the IPython"""
427 426 )
428 427 def _extra_static_paths_default(self):
429 428 return [os.path.join(self.profile_dir.location, 'static')]
430 429
431 430 @property
432 431 def static_file_path(self):
433 432 """return extra paths + the default location"""
434 433 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
435 434
436 435 mathjax_url = Unicode("", config=True,
437 436 help="""The url for MathJax.js."""
438 437 )
439 438 def _mathjax_url_default(self):
440 439 if not self.enable_mathjax:
441 440 return u''
442 441 static_url_prefix = self.webapp_settings.get("static_url_prefix",
443 442 url_path_join(self.base_project_url, "static")
444 443 )
445 444 try:
446 445 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
447 446 except IOError:
448 447 if self.certfile:
449 448 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
450 449 base = u"https://c328740.ssl.cf1.rackcdn.com"
451 450 else:
452 451 base = u"http://cdn.mathjax.org"
453 452
454 453 url = base + u"/mathjax/latest/MathJax.js"
455 454 self.log.info("Using MathJax from CDN: %s", url)
456 455 return url
457 456 else:
458 457 self.log.info("Using local MathJax from %s" % mathjax)
459 458 return url_path_join(static_url_prefix, u"mathjax/MathJax.js")
460 459
461 460 def _mathjax_url_changed(self, name, old, new):
462 461 if new and not self.enable_mathjax:
463 462 # enable_mathjax=False overrides mathjax_url
464 463 self.mathjax_url = u''
465 464 else:
466 465 self.log.info("Using MathJax: %s", new)
467 466
468 467 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
469 468 config=True,
470 469 help='The notebook manager class to use.')
471 470
472 471 trust_xheaders = Bool(False, config=True,
473 472 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
474 473 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
475 474 )
476 475
477 476 def parse_command_line(self, argv=None):
478 477 super(NotebookApp, self).parse_command_line(argv)
479 478 if argv is None:
480 479 argv = sys.argv[1:]
481 480
482 481 # Scrub frontend-specific flags
483 482 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
484 483 # Kernel should inherit default config file from frontend
485 484 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
486 485
487 486 if self.extra_args:
488 487 f = os.path.abspath(self.extra_args[0])
489 488 if os.path.isdir(f):
490 489 nbdir = f
491 490 else:
492 491 self.file_to_run = f
493 492 nbdir = os.path.dirname(f)
494 493 self.config.NotebookManager.notebook_dir = nbdir
495 494
496 495 def init_configurables(self):
497 496 # force Session default to be secure
498 497 default_secure(self.config)
499 498 self.kernel_manager = MappingKernelManager(
500 499 parent=self, log=self.log, kernel_argv=self.kernel_argv,
501 500 connection_dir = self.profile_dir.security_dir,
502 501 )
503 502 kls = import_item(self.notebook_manager_class)
504 503 self.notebook_manager = kls(parent=self, log=self.log)
505 504 self.notebook_manager.load_notebook_names()
506 505 self.cluster_manager = ClusterManager(parent=self, log=self.log)
507 506 self.cluster_manager.update_profiles()
508 507
509 508 def init_logging(self):
510 509 # This prevents double log messages because tornado use a root logger that
511 510 # self.log is a child of. The logging module dipatches log messages to a log
512 511 # and all of its ancenstors until propagate is set to False.
513 512 self.log.propagate = False
514 513
515 514 # hook up tornado 3's loggers to our app handlers
516 515 for name in ('access', 'application', 'general'):
517 516 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
518 517
519 518 def init_webapp(self):
520 519 """initialize tornado webapp and httpserver"""
521 520 self.web_app = NotebookWebApplication(
522 521 self, self.kernel_manager, self.notebook_manager,
523 522 self.cluster_manager, self.log,
524 523 self.base_project_url, self.webapp_settings
525 524 )
526 525 if self.certfile:
527 526 ssl_options = dict(certfile=self.certfile)
528 527 if self.keyfile:
529 528 ssl_options['keyfile'] = self.keyfile
530 529 else:
531 530 ssl_options = None
532 531 self.web_app.password = self.password
533 532 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
534 533 xheaders=self.trust_xheaders)
535 534 if not self.ip:
536 535 warning = "WARNING: The notebook server is listening on all IP addresses"
537 536 if ssl_options is None:
538 537 self.log.critical(warning + " and not using encryption. This "
539 538 "is not recommended.")
540 539 if not self.password:
541 540 self.log.critical(warning + " and not using authentication. "
542 541 "This is highly insecure and not recommended.")
543 542 success = None
544 543 for port in random_ports(self.port, self.port_retries+1):
545 544 try:
546 545 self.http_server.listen(port, self.ip)
547 546 except socket.error as e:
548 547 # XXX: remove the e.errno == -9 block when we require
549 548 # tornado >= 3.0
550 549 if e.errno == -9 and tornado.version_info[0] < 3:
551 550 # The flags passed to socket.getaddrinfo from
552 551 # tornado.netutils.bind_sockets can cause "gaierror:
553 552 # [Errno -9] Address family for hostname not supported"
554 553 # when the interface is not associated, for example.
555 554 # Changing the flags to exclude socket.AI_ADDRCONFIG does
556 555 # not cause this error, but the only way to do this is to
557 556 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
558 557 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
559 558 self.log.warn('Monkeypatching socket to fix tornado bug')
560 559 del(socket.AI_ADDRCONFIG)
561 560 try:
562 561 # retry the tornado call without AI_ADDRCONFIG flags
563 562 self.http_server.listen(port, self.ip)
564 563 except socket.error as e2:
565 564 e = e2
566 565 else:
567 566 self.port = port
568 567 success = True
569 568 break
570 569 # restore the monekypatch
571 570 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
572 571 if e.errno != errno.EADDRINUSE:
573 572 raise
574 573 self.log.info('The port %i is already in use, trying another random port.' % port)
575 574 else:
576 575 self.port = port
577 576 success = True
578 577 break
579 578 if not success:
580 579 self.log.critical('ERROR: the notebook server could not be started because '
581 580 'no available port could be found.')
582 581 self.exit(1)
583 582
584 583 def init_signal(self):
585 584 if not sys.platform.startswith('win'):
586 585 signal.signal(signal.SIGINT, self._handle_sigint)
587 586 signal.signal(signal.SIGTERM, self._signal_stop)
588 587 if hasattr(signal, 'SIGUSR1'):
589 588 # Windows doesn't support SIGUSR1
590 589 signal.signal(signal.SIGUSR1, self._signal_info)
591 590 if hasattr(signal, 'SIGINFO'):
592 591 # only on BSD-based systems
593 592 signal.signal(signal.SIGINFO, self._signal_info)
594 593
595 594 def _handle_sigint(self, sig, frame):
596 595 """SIGINT handler spawns confirmation dialog"""
597 596 # register more forceful signal handler for ^C^C case
598 597 signal.signal(signal.SIGINT, self._signal_stop)
599 598 # request confirmation dialog in bg thread, to avoid
600 599 # blocking the App
601 600 thread = threading.Thread(target=self._confirm_exit)
602 601 thread.daemon = True
603 602 thread.start()
604 603
605 604 def _restore_sigint_handler(self):
606 605 """callback for restoring original SIGINT handler"""
607 606 signal.signal(signal.SIGINT, self._handle_sigint)
608 607
609 608 def _confirm_exit(self):
610 609 """confirm shutdown on ^C
611 610
612 611 A second ^C, or answering 'y' within 5s will cause shutdown,
613 612 otherwise original SIGINT handler will be restored.
614 613
615 614 This doesn't work on Windows.
616 615 """
617 616 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
618 617 time.sleep(0.1)
619 618 info = self.log.info
620 619 info('interrupted')
621 620 print self.notebook_info()
622 621 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
623 622 sys.stdout.flush()
624 623 r,w,x = select.select([sys.stdin], [], [], 5)
625 624 if r:
626 625 line = sys.stdin.readline()
627 626 if line.lower().startswith('y'):
628 627 self.log.critical("Shutdown confirmed")
629 628 ioloop.IOLoop.instance().stop()
630 629 return
631 630 else:
632 631 print "No answer for 5s:",
633 632 print "resuming operation..."
634 633 # no answer, or answer is no:
635 634 # set it back to original SIGINT handler
636 635 # use IOLoop.add_callback because signal.signal must be called
637 636 # from main thread
638 637 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
639 638
640 639 def _signal_stop(self, sig, frame):
641 640 self.log.critical("received signal %s, stopping", sig)
642 641 ioloop.IOLoop.instance().stop()
643 642
644 643 def _signal_info(self, sig, frame):
645 644 print self.notebook_info()
646 645
647 646 def init_components(self):
648 647 """Check the components submodule, and warn if it's unclean"""
649 648 status = submodule.check_submodule_status()
650 649 if status == 'missing':
651 650 self.log.warn("components submodule missing, running `git submodule update`")
652 651 submodule.update_submodules(submodule.ipython_parent())
653 652 elif status == 'unclean':
654 653 self.log.warn("components submodule unclean, you may see 404s on static/components")
655 654 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
656 655
657 656
658 657 @catch_config_error
659 658 def initialize(self, argv=None):
660 659 self.init_logging()
661 660 super(NotebookApp, self).initialize(argv)
662 661 self.init_configurables()
663 662 self.init_components()
664 663 self.init_webapp()
665 664 self.init_signal()
666 665
667 666 def cleanup_kernels(self):
668 667 """Shutdown all kernels.
669 668
670 669 The kernels will shutdown themselves when this process no longer exists,
671 670 but explicit shutdown allows the KernelManagers to cleanup the connection files.
672 671 """
673 672 self.log.info('Shutting down kernels')
674 673 self.kernel_manager.shutdown_all()
675 674
676 675 def notebook_info(self):
677 676 "Return the current working directory and the server url information"
678 677 mgr_info = self.notebook_manager.info_string() + "\n"
679 678 return mgr_info +"The IPython Notebook is running at: %s" % self._url
680 679
681 680 def start(self):
682 681 """ Start the IPython Notebook server app, after initialization
683 682
684 683 This method takes no arguments so all configuration and initialization
685 684 must be done prior to calling this method."""
686 685 ip = self.ip if self.ip else '[all ip addresses on your system]'
687 686 proto = 'https' if self.certfile else 'http'
688 687 info = self.log.info
689 688 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
690 689 self.base_project_url)
691 690 for line in self.notebook_info().split("\n"):
692 691 info(line)
693 692 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
694 693
695 694 if self.open_browser or self.file_to_run:
696 695 ip = self.ip or LOCALHOST
697 696 try:
698 697 browser = webbrowser.get(self.browser or None)
699 698 except webbrowser.Error as e:
700 699 self.log.warn('No web browser found: %s.' % e)
701 700 browser = None
702 701
703 702 if self.file_to_run:
704 703 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
705 704 url = self.notebook_manager.rev_mapping.get(name, '')
706 705 else:
707 706 url = ''
708 707 if browser:
709 708 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
710 709 self.port, self.base_project_url, url), new=2)
711 710 threading.Thread(target=b).start()
712 711 try:
713 712 ioloop.IOLoop.instance().start()
714 713 except KeyboardInterrupt:
715 714 info("Interrupted...")
716 715 finally:
717 716 self.cleanup_kernels()
718 717
719 718
720 719 #-----------------------------------------------------------------------------
721 720 # Main entry point
722 721 #-----------------------------------------------------------------------------
723 722
724 723 launch_new_instance = NotebookApp.launch_instance
725 724
@@ -1,385 +1,385
1 1 """ A minimal application using the Qt console-style IPython frontend.
2 2
3 3 This is not a complete console app, as subprocess will not be able to receive
4 4 input, there is no real readline support, among other limitations.
5 5
6 6 Authors:
7 7
8 8 * Evan Patterson
9 9 * Min RK
10 10 * Erik Tollerud
11 11 * Fernando Perez
12 12 * Bussonnier Matthias
13 13 * Thomas Kluyver
14 14 * Paul Ivanov
15 15
16 16 """
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 # stdlib imports
23 23 import os
24 24 import signal
25 25 import sys
26 26
27 27 # If run on Windows, install an exception hook which pops up a
28 28 # message box. Pythonw.exe hides the console, so without this
29 29 # the application silently fails to load.
30 30 #
31 31 # We always install this handler, because the expectation is for
32 32 # qtconsole to bring up a GUI even if called from the console.
33 33 # The old handler is called, so the exception is printed as well.
34 34 # If desired, check for pythonw with an additional condition
35 35 # (sys.executable.lower().find('pythonw.exe') >= 0).
36 36 if os.name == 'nt':
37 37 old_excepthook = sys.excepthook
38 38
39 39 def gui_excepthook(exctype, value, tb):
40 40 try:
41 41 import ctypes, traceback
42 42 MB_ICONERROR = 0x00000010L
43 43 title = u'Error starting IPython QtConsole'
44 44 msg = u''.join(traceback.format_exception(exctype, value, tb))
45 45 ctypes.windll.user32.MessageBoxW(0, msg, title, MB_ICONERROR)
46 46 finally:
47 47 # Also call the old exception hook to let it do
48 48 # its thing too.
49 49 old_excepthook(exctype, value, tb)
50 50
51 51 sys.excepthook = gui_excepthook
52 52
53 53 # System library imports
54 54 from IPython.external.qt import QtCore, QtGui
55 55
56 56 # Local imports
57 57 from IPython.config.application import catch_config_error
58 58 from IPython.core.application import BaseIPythonApplication
59 59 from IPython.qt.console.ipython_widget import IPythonWidget
60 60 from IPython.qt.console.rich_ipython_widget import RichIPythonWidget
61 61 from IPython.qt.console import styles
62 62 from IPython.qt.console.mainwindow import MainWindow
63 63 from IPython.qt.client import QtKernelClient
64 64 from IPython.qt.manager import QtKernelManager
65 65 from IPython.utils.traitlets import (
66 66 Dict, Unicode, CBool, Any
67 67 )
68 68
69 69 from IPython.consoleapp import (
70 70 IPythonConsoleApp, app_aliases, app_flags, flags, aliases
71 71 )
72 72
73 73 #-----------------------------------------------------------------------------
74 74 # Network Constants
75 75 #-----------------------------------------------------------------------------
76 76
77 77 from IPython.utils.localinterfaces import LOCALHOST, LOCAL_IPS
78 78
79 79 #-----------------------------------------------------------------------------
80 80 # Globals
81 81 #-----------------------------------------------------------------------------
82 82
83 83 _examples = """
84 84 ipython qtconsole # start the qtconsole
85 ipython qtconsole --pylab=inline # start with pylab in inline plotting mode
85 ipython qtconsole --matplotlib=inline # start with matplotlib inline plotting mode
86 86 """
87 87
88 88 #-----------------------------------------------------------------------------
89 89 # Aliases and Flags
90 90 #-----------------------------------------------------------------------------
91 91
92 92 # start with copy of flags
93 93 flags = dict(flags)
94 94 qt_flags = {
95 95 'plain' : ({'IPythonQtConsoleApp' : {'plain' : True}},
96 96 "Disable rich text support."),
97 97 }
98 98
99 99 # and app_flags from the Console Mixin
100 100 qt_flags.update(app_flags)
101 101 # add frontend flags to the full set
102 102 flags.update(qt_flags)
103 103
104 104 # start with copy of front&backend aliases list
105 105 aliases = dict(aliases)
106 106 qt_aliases = dict(
107 107 style = 'IPythonWidget.syntax_style',
108 108 stylesheet = 'IPythonQtConsoleApp.stylesheet',
109 109 colors = 'ZMQInteractiveShell.colors',
110 110
111 111 editor = 'IPythonWidget.editor',
112 112 paging = 'ConsoleWidget.paging',
113 113 )
114 114 # and app_aliases from the Console Mixin
115 115 qt_aliases.update(app_aliases)
116 116 qt_aliases.update({'gui-completion':'ConsoleWidget.gui_completion'})
117 117 # add frontend aliases to the full set
118 118 aliases.update(qt_aliases)
119 119
120 120 # get flags&aliases into sets, and remove a couple that
121 121 # shouldn't be scrubbed from backend flags:
122 122 qt_aliases = set(qt_aliases.keys())
123 123 qt_aliases.remove('colors')
124 124 qt_flags = set(qt_flags.keys())
125 125
126 126 #-----------------------------------------------------------------------------
127 127 # Classes
128 128 #-----------------------------------------------------------------------------
129 129
130 130 #-----------------------------------------------------------------------------
131 131 # IPythonQtConsole
132 132 #-----------------------------------------------------------------------------
133 133
134 134
135 135 class IPythonQtConsoleApp(BaseIPythonApplication, IPythonConsoleApp):
136 136 name = 'ipython-qtconsole'
137 137
138 138 description = """
139 139 The IPython QtConsole.
140 140
141 141 This launches a Console-style application using Qt. It is not a full
142 142 console, in that launched terminal subprocesses will not be able to accept
143 143 input.
144 144
145 145 The QtConsole supports various extra features beyond the Terminal IPython
146 146 shell, such as inline plotting with matplotlib, via:
147 147
148 ipython qtconsole --pylab=inline
148 ipython qtconsole --matplotlib=inline
149 149
150 150 as well as saving your session as HTML, and printing the output.
151 151
152 152 """
153 153 examples = _examples
154 154
155 155 classes = [IPythonWidget] + IPythonConsoleApp.classes
156 156 flags = Dict(flags)
157 157 aliases = Dict(aliases)
158 158 frontend_flags = Any(qt_flags)
159 159 frontend_aliases = Any(qt_aliases)
160 160 kernel_client_class = QtKernelClient
161 161 kernel_manager_class = QtKernelManager
162 162
163 163 stylesheet = Unicode('', config=True,
164 164 help="path to a custom CSS stylesheet")
165 165
166 166 hide_menubar = CBool(False, config=True,
167 167 help="Start the console window with the menu bar hidden.")
168 168
169 169 maximize = CBool(False, config=True,
170 170 help="Start the console window maximized.")
171 171
172 172 plain = CBool(False, config=True,
173 173 help="Use a plaintext widget instead of rich text (plain can't print/save).")
174 174
175 175 def _plain_changed(self, name, old, new):
176 176 kind = 'plain' if new else 'rich'
177 177 self.config.ConsoleWidget.kind = kind
178 178 if new:
179 179 self.widget_factory = IPythonWidget
180 180 else:
181 181 self.widget_factory = RichIPythonWidget
182 182
183 183 # the factory for creating a widget
184 184 widget_factory = Any(RichIPythonWidget)
185 185
186 186 def parse_command_line(self, argv=None):
187 187 super(IPythonQtConsoleApp, self).parse_command_line(argv)
188 188 self.build_kernel_argv(argv)
189 189
190 190
191 191 def new_frontend_master(self):
192 192 """ Create and return new frontend attached to new kernel, launched on localhost.
193 193 """
194 194 kernel_manager = self.kernel_manager_class(
195 195 connection_file=self._new_connection_file(),
196 196 parent=self,
197 197 autorestart=True,
198 198 )
199 199 # start the kernel
200 200 kwargs = dict()
201 201 kwargs['extra_arguments'] = self.kernel_argv
202 202 kernel_manager.start_kernel(**kwargs)
203 203 kernel_manager.client_factory = self.kernel_client_class
204 204 kernel_client = kernel_manager.client()
205 205 kernel_client.start_channels(shell=True, iopub=True)
206 206 widget = self.widget_factory(config=self.config,
207 207 local_kernel=True)
208 208 self.init_colors(widget)
209 209 widget.kernel_manager = kernel_manager
210 210 widget.kernel_client = kernel_client
211 211 widget._existing = False
212 212 widget._may_close = True
213 213 widget._confirm_exit = self.confirm_exit
214 214 return widget
215 215
216 216 def new_frontend_slave(self, current_widget):
217 217 """Create and return a new frontend attached to an existing kernel.
218 218
219 219 Parameters
220 220 ----------
221 221 current_widget : IPythonWidget
222 222 The IPythonWidget whose kernel this frontend is to share
223 223 """
224 224 kernel_client = self.kernel_client_class(
225 225 connection_file=current_widget.kernel_client.connection_file,
226 226 config = self.config,
227 227 )
228 228 kernel_client.load_connection_file()
229 229 kernel_client.start_channels()
230 230 widget = self.widget_factory(config=self.config,
231 231 local_kernel=False)
232 232 self.init_colors(widget)
233 233 widget._existing = True
234 234 widget._may_close = False
235 235 widget._confirm_exit = False
236 236 widget.kernel_client = kernel_client
237 237 widget.kernel_manager = current_widget.kernel_manager
238 238 return widget
239 239
240 240 def init_qt_app(self):
241 241 # separate from qt_elements, because it must run first
242 242 self.app = QtGui.QApplication([])
243 243
244 244 def init_qt_elements(self):
245 245 # Create the widget.
246 246
247 247 base_path = os.path.abspath(os.path.dirname(__file__))
248 248 icon_path = os.path.join(base_path, 'resources', 'icon', 'IPythonConsole.svg')
249 249 self.app.icon = QtGui.QIcon(icon_path)
250 250 QtGui.QApplication.setWindowIcon(self.app.icon)
251 251
252 252 try:
253 253 ip = self.config.KernelManager.ip
254 254 except AttributeError:
255 255 ip = LOCALHOST
256 256 local_kernel = (not self.existing) or ip in LOCAL_IPS
257 257 self.widget = self.widget_factory(config=self.config,
258 258 local_kernel=local_kernel)
259 259 self.init_colors(self.widget)
260 260 self.widget._existing = self.existing
261 261 self.widget._may_close = not self.existing
262 262 self.widget._confirm_exit = self.confirm_exit
263 263
264 264 self.widget.kernel_manager = self.kernel_manager
265 265 self.widget.kernel_client = self.kernel_client
266 266 self.window = MainWindow(self.app,
267 267 confirm_exit=self.confirm_exit,
268 268 new_frontend_factory=self.new_frontend_master,
269 269 slave_frontend_factory=self.new_frontend_slave,
270 270 )
271 271 self.window.log = self.log
272 272 self.window.add_tab_with_frontend(self.widget)
273 273 self.window.init_menu_bar()
274 274
275 275 # Ignore on OSX, where there is always a menu bar
276 276 if sys.platform != 'darwin' and self.hide_menubar:
277 277 self.window.menuBar().setVisible(False)
278 278
279 279 self.window.setWindowTitle('IPython')
280 280
281 281 def init_colors(self, widget):
282 282 """Configure the coloring of the widget"""
283 283 # Note: This will be dramatically simplified when colors
284 284 # are removed from the backend.
285 285
286 286 # parse the colors arg down to current known labels
287 287 try:
288 288 colors = self.config.ZMQInteractiveShell.colors
289 289 except AttributeError:
290 290 colors = None
291 291 try:
292 292 style = self.config.IPythonWidget.syntax_style
293 293 except AttributeError:
294 294 style = None
295 295 try:
296 296 sheet = self.config.IPythonWidget.style_sheet
297 297 except AttributeError:
298 298 sheet = None
299 299
300 300 # find the value for colors:
301 301 if colors:
302 302 colors=colors.lower()
303 303 if colors in ('lightbg', 'light'):
304 304 colors='lightbg'
305 305 elif colors in ('dark', 'linux'):
306 306 colors='linux'
307 307 else:
308 308 colors='nocolor'
309 309 elif style:
310 310 if style=='bw':
311 311 colors='nocolor'
312 312 elif styles.dark_style(style):
313 313 colors='linux'
314 314 else:
315 315 colors='lightbg'
316 316 else:
317 317 colors=None
318 318
319 319 # Configure the style
320 320 if style:
321 321 widget.style_sheet = styles.sheet_from_template(style, colors)
322 322 widget.syntax_style = style
323 323 widget._syntax_style_changed()
324 324 widget._style_sheet_changed()
325 325 elif colors:
326 326 # use a default dark/light/bw style
327 327 widget.set_default_style(colors=colors)
328 328
329 329 if self.stylesheet:
330 330 # we got an explicit stylesheet
331 331 if os.path.isfile(self.stylesheet):
332 332 with open(self.stylesheet) as f:
333 333 sheet = f.read()
334 334 else:
335 335 raise IOError("Stylesheet %r not found." % self.stylesheet)
336 336 if sheet:
337 337 widget.style_sheet = sheet
338 338 widget._style_sheet_changed()
339 339
340 340
341 341 def init_signal(self):
342 342 """allow clean shutdown on sigint"""
343 343 signal.signal(signal.SIGINT, lambda sig, frame: self.exit(-2))
344 344 # need a timer, so that QApplication doesn't block until a real
345 345 # Qt event fires (can require mouse movement)
346 346 # timer trick from http://stackoverflow.com/q/4938723/938949
347 347 timer = QtCore.QTimer()
348 348 # Let the interpreter run each 200 ms:
349 349 timer.timeout.connect(lambda: None)
350 350 timer.start(200)
351 351 # hold onto ref, so the timer doesn't get cleaned up
352 352 self._sigint_timer = timer
353 353
354 354 @catch_config_error
355 355 def initialize(self, argv=None):
356 356 self.init_qt_app()
357 357 super(IPythonQtConsoleApp, self).initialize(argv)
358 358 IPythonConsoleApp.initialize(self,argv)
359 359 self.init_qt_elements()
360 360 self.init_signal()
361 361
362 362 def start(self):
363 363
364 364 # draw the window
365 365 if self.maximize:
366 366 self.window.showMaximized()
367 367 else:
368 368 self.window.show()
369 369 self.window.raise_()
370 370
371 371 # Start the application main loop.
372 372 self.app.exec_()
373 373
374 374 #-----------------------------------------------------------------------------
375 375 # Main entry point
376 376 #-----------------------------------------------------------------------------
377 377
378 378 def main():
379 379 app = IPythonQtConsoleApp()
380 380 app.initialize()
381 381 app.start()
382 382
383 383
384 384 if __name__ == '__main__':
385 385 main()
General Comments 0
You need to be logged in to leave comments. Login now