##// END OF EJS Templates
remove utils.zmqrelated...
Min RK -
Show More
@@ -1,1131 +1,1127 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 7 from __future__ import print_function
8 8
9 9 import base64
10 10 import datetime
11 11 import errno
12 12 import importlib
13 13 import io
14 14 import json
15 15 import logging
16 16 import os
17 17 import random
18 18 import re
19 19 import select
20 20 import signal
21 21 import socket
22 22 import ssl
23 23 import sys
24 24 import threading
25 25 import webbrowser
26 26
27 27
28 # check for pyzmq
29 from IPython.utils.zmqrelated import check_for_zmq
30 check_for_zmq('13', 'IPython.html')
31
32 28 from jinja2 import Environment, FileSystemLoader
33 29
34 30 # Install the pyzmq ioloop. This has to be done before anything else from
35 31 # tornado is imported.
36 32 from zmq.eventloop import ioloop
37 33 ioloop.install()
38 34
39 35 # check for tornado 3.1.0
40 36 msg = "The IPython Notebook requires tornado >= 4.0"
41 37 try:
42 38 import tornado
43 39 except ImportError:
44 40 raise ImportError(msg)
45 41 try:
46 42 version_info = tornado.version_info
47 43 except AttributeError:
48 44 raise ImportError(msg + ", but you have < 1.1.0")
49 45 if version_info < (4,0):
50 46 raise ImportError(msg + ", but you have %s" % tornado.version)
51 47
52 48 from tornado import httpserver
53 49 from tornado import web
54 50 from tornado.log import LogFormatter, app_log, access_log, gen_log
55 51
56 52 from IPython.html import (
57 53 DEFAULT_STATIC_FILES_PATH,
58 54 DEFAULT_TEMPLATE_PATH_LIST,
59 55 )
60 56 from .base.handlers import Template404
61 57 from .log import log_request
62 58 from .services.kernels.kernelmanager import MappingKernelManager
63 59 from .services.config import ConfigManager
64 60 from .services.contents.manager import ContentsManager
65 61 from .services.contents.filemanager import FileContentsManager
66 62 from .services.clusters.clustermanager import ClusterManager
67 63 from .services.sessions.sessionmanager import SessionManager
68 64
69 65 from .auth.login import LoginHandler
70 66 from .auth.logout import LogoutHandler
71 67 from .base.handlers import IPythonHandler, FileFindHandler
72 68
73 69 from IPython.config import Config
74 70 from IPython.config.application import catch_config_error, boolean_flag
75 71 from IPython.core.application import (
76 72 BaseIPythonApplication, base_flags, base_aliases,
77 73 )
78 74 from IPython.core.profiledir import ProfileDir
79 75 from IPython.kernel import KernelManager
80 76 from IPython.kernel.kernelspec import KernelSpecManager
81 77 from IPython.kernel.zmq.session import Session
82 78 from IPython.nbformat.sign import NotebookNotary
83 79 from IPython.utils.importstring import import_item
84 80 from IPython.utils import submodule
85 81 from IPython.utils.process import check_pid
86 82 from IPython.utils.traitlets import (
87 83 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
88 84 TraitError, Type,
89 85 )
90 86 from IPython.utils import py3compat
91 87 from IPython.utils.path import filefind, get_ipython_dir
92 88 from IPython.utils.sysinfo import get_sys_info
93 89
94 90 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
95 91 from .utils import url_path_join
96 92
97 93 #-----------------------------------------------------------------------------
98 94 # Module globals
99 95 #-----------------------------------------------------------------------------
100 96
101 97 _examples = """
102 98 ipython notebook # start the notebook
103 99 ipython notebook --profile=sympy # use the sympy profile
104 100 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
105 101 """
106 102
107 103 #-----------------------------------------------------------------------------
108 104 # Helper functions
109 105 #-----------------------------------------------------------------------------
110 106
111 107 def random_ports(port, n):
112 108 """Generate a list of n random ports near the given port.
113 109
114 110 The first 5 ports will be sequential, and the remaining n-5 will be
115 111 randomly selected in the range [port-2*n, port+2*n].
116 112 """
117 113 for i in range(min(5, n)):
118 114 yield port + i
119 115 for i in range(n-5):
120 116 yield max(1, port + random.randint(-2*n, 2*n))
121 117
122 118 def load_handlers(name):
123 119 """Load the (URL pattern, handler) tuples for each component."""
124 120 name = 'IPython.html.' + name
125 121 mod = __import__(name, fromlist=['default_handlers'])
126 122 return mod.default_handlers
127 123
128 124 #-----------------------------------------------------------------------------
129 125 # The Tornado web application
130 126 #-----------------------------------------------------------------------------
131 127
132 128 class NotebookWebApplication(web.Application):
133 129
134 130 def __init__(self, ipython_app, kernel_manager, contents_manager,
135 131 cluster_manager, session_manager, kernel_spec_manager,
136 132 config_manager, log,
137 133 base_url, default_url, settings_overrides, jinja_env_options):
138 134
139 135 settings = self.init_settings(
140 136 ipython_app, kernel_manager, contents_manager, cluster_manager,
141 137 session_manager, kernel_spec_manager, config_manager, log, base_url,
142 138 default_url, settings_overrides, jinja_env_options)
143 139 handlers = self.init_handlers(settings)
144 140
145 141 super(NotebookWebApplication, self).__init__(handlers, **settings)
146 142
147 143 def init_settings(self, ipython_app, kernel_manager, contents_manager,
148 144 cluster_manager, session_manager, kernel_spec_manager,
149 145 config_manager,
150 146 log, base_url, default_url, settings_overrides,
151 147 jinja_env_options=None):
152 148
153 149 _template_path = settings_overrides.get(
154 150 "template_path",
155 151 ipython_app.template_file_path,
156 152 )
157 153 if isinstance(_template_path, str):
158 154 _template_path = (_template_path,)
159 155 template_path = [os.path.expanduser(path) for path in _template_path]
160 156
161 157 jenv_opt = jinja_env_options if jinja_env_options else {}
162 158 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
163 159
164 160 sys_info = get_sys_info()
165 161 if sys_info['commit_source'] == 'repository':
166 162 # don't cache (rely on 304) when working from master
167 163 version_hash = ''
168 164 else:
169 165 # reset the cache on server restart
170 166 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
171 167
172 168 settings = dict(
173 169 # basics
174 170 log_function=log_request,
175 171 base_url=base_url,
176 172 default_url=default_url,
177 173 template_path=template_path,
178 174 static_path=ipython_app.static_file_path,
179 175 static_handler_class = FileFindHandler,
180 176 static_url_prefix = url_path_join(base_url,'/static/'),
181 177 static_handler_args = {
182 178 # don't cache custom.js
183 179 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
184 180 },
185 181 version_hash=version_hash,
186 182
187 183 # authentication
188 184 cookie_secret=ipython_app.cookie_secret,
189 185 login_url=url_path_join(base_url,'/login'),
190 186 login_handler_class=ipython_app.login_handler_class,
191 187 logout_handler_class=ipython_app.logout_handler_class,
192 188 password=ipython_app.password,
193 189
194 190 # managers
195 191 kernel_manager=kernel_manager,
196 192 contents_manager=contents_manager,
197 193 cluster_manager=cluster_manager,
198 194 session_manager=session_manager,
199 195 kernel_spec_manager=kernel_spec_manager,
200 196 config_manager=config_manager,
201 197
202 198 # IPython stuff
203 199 nbextensions_path=ipython_app.nbextensions_path,
204 200 websocket_url=ipython_app.websocket_url,
205 201 mathjax_url=ipython_app.mathjax_url,
206 202 config=ipython_app.config,
207 203 jinja2_env=env,
208 204 terminals_available=False, # Set later if terminals are available
209 205 )
210 206
211 207 # allow custom overrides for the tornado web app.
212 208 settings.update(settings_overrides)
213 209 return settings
214 210
215 211 def init_handlers(self, settings):
216 212 """Load the (URL pattern, handler) tuples for each component."""
217 213
218 214 # Order matters. The first handler to match the URL will handle the request.
219 215 handlers = []
220 216 handlers.extend(load_handlers('tree.handlers'))
221 217 handlers.extend([(r"/login", settings['login_handler_class'])])
222 218 handlers.extend([(r"/logout", settings['logout_handler_class'])])
223 219 handlers.extend(load_handlers('files.handlers'))
224 220 handlers.extend(load_handlers('notebook.handlers'))
225 221 handlers.extend(load_handlers('nbconvert.handlers'))
226 222 handlers.extend(load_handlers('kernelspecs.handlers'))
227 223 handlers.extend(load_handlers('edit.handlers'))
228 224 handlers.extend(load_handlers('services.config.handlers'))
229 225 handlers.extend(load_handlers('services.kernels.handlers'))
230 226 handlers.extend(load_handlers('services.contents.handlers'))
231 227 handlers.extend(load_handlers('services.clusters.handlers'))
232 228 handlers.extend(load_handlers('services.sessions.handlers'))
233 229 handlers.extend(load_handlers('services.nbconvert.handlers'))
234 230 handlers.extend(load_handlers('services.kernelspecs.handlers'))
235 231 handlers.extend(load_handlers('services.security.handlers'))
236 232 handlers.append(
237 233 (r"/nbextensions/(.*)", FileFindHandler, {
238 234 'path': settings['nbextensions_path'],
239 235 'no_cache_paths': ['/'], # don't cache anything in nbextensions
240 236 }),
241 237 )
242 238 # register base handlers last
243 239 handlers.extend(load_handlers('base.handlers'))
244 240 # set the URL that will be redirected from `/`
245 241 handlers.append(
246 242 (r'/?', web.RedirectHandler, {
247 243 'url' : settings['default_url'],
248 244 'permanent': False, # want 302, not 301
249 245 })
250 246 )
251 247 # prepend base_url onto the patterns that we match
252 248 new_handlers = []
253 249 for handler in handlers:
254 250 pattern = url_path_join(settings['base_url'], handler[0])
255 251 new_handler = tuple([pattern] + list(handler[1:]))
256 252 new_handlers.append(new_handler)
257 253 # add 404 on the end, which will catch everything that falls through
258 254 new_handlers.append((r'(.*)', Template404))
259 255 return new_handlers
260 256
261 257
262 258 class NbserverListApp(BaseIPythonApplication):
263 259
264 260 description="List currently running notebook servers in this profile."
265 261
266 262 flags = dict(
267 263 json=({'NbserverListApp': {'json': True}},
268 264 "Produce machine-readable JSON output."),
269 265 )
270 266
271 267 json = Bool(False, config=True,
272 268 help="If True, each line of output will be a JSON object with the "
273 269 "details from the server info file.")
274 270
275 271 def start(self):
276 272 if not self.json:
277 273 print("Currently running servers:")
278 274 for serverinfo in list_running_servers(self.profile):
279 275 if self.json:
280 276 print(json.dumps(serverinfo))
281 277 else:
282 278 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
283 279
284 280 #-----------------------------------------------------------------------------
285 281 # Aliases and Flags
286 282 #-----------------------------------------------------------------------------
287 283
288 284 flags = dict(base_flags)
289 285 flags['no-browser']=(
290 286 {'NotebookApp' : {'open_browser' : False}},
291 287 "Don't open the notebook in a browser after startup."
292 288 )
293 289 flags['pylab']=(
294 290 {'NotebookApp' : {'pylab' : 'warn'}},
295 291 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
296 292 )
297 293 flags['no-mathjax']=(
298 294 {'NotebookApp' : {'enable_mathjax' : False}},
299 295 """Disable MathJax
300 296
301 297 MathJax is the javascript library IPython uses to render math/LaTeX. It is
302 298 very large, so you may want to disable it if you have a slow internet
303 299 connection, or for offline use of the notebook.
304 300
305 301 When disabled, equations etc. will appear as their untransformed TeX source.
306 302 """
307 303 )
308 304
309 305 # Add notebook manager flags
310 306 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
311 307 'DEPRECATED, IGNORED',
312 308 'DEPRECATED, IGNORED'))
313 309
314 310 aliases = dict(base_aliases)
315 311
316 312 aliases.update({
317 313 'ip': 'NotebookApp.ip',
318 314 'port': 'NotebookApp.port',
319 315 'port-retries': 'NotebookApp.port_retries',
320 316 'transport': 'KernelManager.transport',
321 317 'keyfile': 'NotebookApp.keyfile',
322 318 'certfile': 'NotebookApp.certfile',
323 319 'notebook-dir': 'NotebookApp.notebook_dir',
324 320 'browser': 'NotebookApp.browser',
325 321 'pylab': 'NotebookApp.pylab',
326 322 })
327 323
328 324 #-----------------------------------------------------------------------------
329 325 # NotebookApp
330 326 #-----------------------------------------------------------------------------
331 327
332 328 class NotebookApp(BaseIPythonApplication):
333 329
334 330 name = 'ipython-notebook'
335 331
336 332 description = """
337 333 The IPython HTML Notebook.
338 334
339 335 This launches a Tornado based HTML Notebook Server that serves up an
340 336 HTML5/Javascript Notebook client.
341 337 """
342 338 examples = _examples
343 339 aliases = aliases
344 340 flags = flags
345 341
346 342 classes = [
347 343 KernelManager, ProfileDir, Session, MappingKernelManager,
348 344 ContentsManager, FileContentsManager, NotebookNotary,
349 345 KernelSpecManager,
350 346 ]
351 347 flags = Dict(flags)
352 348 aliases = Dict(aliases)
353 349
354 350 subcommands = dict(
355 351 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
356 352 )
357 353
358 354 _log_formatter_cls = LogFormatter
359 355
360 356 def _log_level_default(self):
361 357 return logging.INFO
362 358
363 359 def _log_datefmt_default(self):
364 360 """Exclude date from default date format"""
365 361 return "%H:%M:%S"
366 362
367 363 def _log_format_default(self):
368 364 """override default log format to include time"""
369 365 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
370 366
371 367 # create requested profiles by default, if they don't exist:
372 368 auto_create = Bool(True)
373 369
374 370 # file to be opened in the notebook server
375 371 file_to_run = Unicode('', config=True)
376 372
377 373 # Network related information
378 374
379 375 allow_origin = Unicode('', config=True,
380 376 help="""Set the Access-Control-Allow-Origin header
381 377
382 378 Use '*' to allow any origin to access your server.
383 379
384 380 Takes precedence over allow_origin_pat.
385 381 """
386 382 )
387 383
388 384 allow_origin_pat = Unicode('', config=True,
389 385 help="""Use a regular expression for the Access-Control-Allow-Origin header
390 386
391 387 Requests from an origin matching the expression will get replies with:
392 388
393 389 Access-Control-Allow-Origin: origin
394 390
395 391 where `origin` is the origin of the request.
396 392
397 393 Ignored if allow_origin is set.
398 394 """
399 395 )
400 396
401 397 allow_credentials = Bool(False, config=True,
402 398 help="Set the Access-Control-Allow-Credentials: true header"
403 399 )
404 400
405 401 default_url = Unicode('/tree', config=True,
406 402 help="The default URL to redirect to from `/`"
407 403 )
408 404
409 405 ip = Unicode('localhost', config=True,
410 406 help="The IP address the notebook server will listen on."
411 407 )
412 408 def _ip_default(self):
413 409 """Return localhost if available, 127.0.0.1 otherwise.
414 410
415 411 On some (horribly broken) systems, localhost cannot be bound.
416 412 """
417 413 s = socket.socket()
418 414 try:
419 415 s.bind(('localhost', 0))
420 416 except socket.error as e:
421 417 self.log.warn("Cannot bind to localhost, using 127.0.0.1 as default ip\n%s", e)
422 418 return '127.0.0.1'
423 419 else:
424 420 s.close()
425 421 return 'localhost'
426 422
427 423 def _ip_changed(self, name, old, new):
428 424 if new == u'*': self.ip = u''
429 425
430 426 port = Integer(8888, config=True,
431 427 help="The port the notebook server will listen on."
432 428 )
433 429 port_retries = Integer(50, config=True,
434 430 help="The number of additional ports to try if the specified port is not available."
435 431 )
436 432
437 433 certfile = Unicode(u'', config=True,
438 434 help="""The full path to an SSL/TLS certificate file."""
439 435 )
440 436
441 437 keyfile = Unicode(u'', config=True,
442 438 help="""The full path to a private key file for usage with SSL/TLS."""
443 439 )
444 440
445 441 cookie_secret_file = Unicode(config=True,
446 442 help="""The file where the cookie secret is stored."""
447 443 )
448 444 def _cookie_secret_file_default(self):
449 445 if self.profile_dir is None:
450 446 return ''
451 447 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
452 448
453 449 cookie_secret = Bytes(b'', config=True,
454 450 help="""The random bytes used to secure cookies.
455 451 By default this is a new random number every time you start the Notebook.
456 452 Set it to a value in a config file to enable logins to persist across server sessions.
457 453
458 454 Note: Cookie secrets should be kept private, do not share config files with
459 455 cookie_secret stored in plaintext (you can read the value from a file).
460 456 """
461 457 )
462 458 def _cookie_secret_default(self):
463 459 if os.path.exists(self.cookie_secret_file):
464 460 with io.open(self.cookie_secret_file, 'rb') as f:
465 461 return f.read()
466 462 else:
467 463 secret = base64.encodestring(os.urandom(1024))
468 464 self._write_cookie_secret_file(secret)
469 465 return secret
470 466
471 467 def _write_cookie_secret_file(self, secret):
472 468 """write my secret to my secret_file"""
473 469 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
474 470 with io.open(self.cookie_secret_file, 'wb') as f:
475 471 f.write(secret)
476 472 try:
477 473 os.chmod(self.cookie_secret_file, 0o600)
478 474 except OSError:
479 475 self.log.warn(
480 476 "Could not set permissions on %s",
481 477 self.cookie_secret_file
482 478 )
483 479
484 480 password = Unicode(u'', config=True,
485 481 help="""Hashed password to use for web authentication.
486 482
487 483 To generate, type in a python/IPython shell:
488 484
489 485 from IPython.lib import passwd; passwd()
490 486
491 487 The string should be of the form type:salt:hashed-password.
492 488 """
493 489 )
494 490
495 491 open_browser = Bool(True, config=True,
496 492 help="""Whether to open in a browser after starting.
497 493 The specific browser used is platform dependent and
498 494 determined by the python standard library `webbrowser`
499 495 module, unless it is overridden using the --browser
500 496 (NotebookApp.browser) configuration option.
501 497 """)
502 498
503 499 browser = Unicode(u'', config=True,
504 500 help="""Specify what command to use to invoke a web
505 501 browser when opening the notebook. If not specified, the
506 502 default browser will be determined by the `webbrowser`
507 503 standard library module, which allows setting of the
508 504 BROWSER environment variable to override it.
509 505 """)
510 506
511 507 webapp_settings = Dict(config=True,
512 508 help="DEPRECATED, use tornado_settings"
513 509 )
514 510 def _webapp_settings_changed(self, name, old, new):
515 511 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
516 512 self.tornado_settings = new
517 513
518 514 tornado_settings = Dict(config=True,
519 515 help="Supply overrides for the tornado.web.Application that the "
520 516 "IPython notebook uses.")
521 517
522 518 ssl_options = Dict(config=True,
523 519 help="""Supply SSL options for the tornado HTTPServer.
524 520 See the tornado docs for details.""")
525 521
526 522 jinja_environment_options = Dict(config=True,
527 523 help="Supply extra arguments that will be passed to Jinja environment.")
528 524
529 525 enable_mathjax = Bool(True, config=True,
530 526 help="""Whether to enable MathJax for typesetting math/TeX
531 527
532 528 MathJax is the javascript library IPython uses to render math/LaTeX. It is
533 529 very large, so you may want to disable it if you have a slow internet
534 530 connection, or for offline use of the notebook.
535 531
536 532 When disabled, equations etc. will appear as their untransformed TeX source.
537 533 """
538 534 )
539 535 def _enable_mathjax_changed(self, name, old, new):
540 536 """set mathjax url to empty if mathjax is disabled"""
541 537 if not new:
542 538 self.mathjax_url = u''
543 539
544 540 base_url = Unicode('/', config=True,
545 541 help='''The base URL for the notebook server.
546 542
547 543 Leading and trailing slashes can be omitted,
548 544 and will automatically be added.
549 545 ''')
550 546 def _base_url_changed(self, name, old, new):
551 547 if not new.startswith('/'):
552 548 self.base_url = '/'+new
553 549 elif not new.endswith('/'):
554 550 self.base_url = new+'/'
555 551
556 552 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
557 553 def _base_project_url_changed(self, name, old, new):
558 554 self.log.warn("base_project_url is deprecated, use base_url")
559 555 self.base_url = new
560 556
561 557 extra_static_paths = List(Unicode, config=True,
562 558 help="""Extra paths to search for serving static files.
563 559
564 560 This allows adding javascript/css to be available from the notebook server machine,
565 561 or overriding individual files in the IPython"""
566 562 )
567 563 def _extra_static_paths_default(self):
568 564 return [os.path.join(self.profile_dir.location, 'static')]
569 565
570 566 @property
571 567 def static_file_path(self):
572 568 """return extra paths + the default location"""
573 569 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
574 570
575 571 extra_template_paths = List(Unicode, config=True,
576 572 help="""Extra paths to search for serving jinja templates.
577 573
578 574 Can be used to override templates from IPython.html.templates."""
579 575 )
580 576 def _extra_template_paths_default(self):
581 577 return []
582 578
583 579 @property
584 580 def template_file_path(self):
585 581 """return extra paths + the default locations"""
586 582 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
587 583
588 584 extra_nbextensions_path = List(Unicode, config=True,
589 585 help="""extra paths to look for Javascript notebook extensions"""
590 586 )
591 587
592 588 @property
593 589 def nbextensions_path(self):
594 590 """The path to look for Javascript notebook extensions"""
595 591 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
596 592
597 593 websocket_url = Unicode("", config=True,
598 594 help="""The base URL for websockets,
599 595 if it differs from the HTTP server (hint: it almost certainly doesn't).
600 596
601 597 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
602 598 """
603 599 )
604 600 mathjax_url = Unicode("", config=True,
605 601 help="""The url for MathJax.js."""
606 602 )
607 603 def _mathjax_url_default(self):
608 604 if not self.enable_mathjax:
609 605 return u''
610 606 static_url_prefix = self.tornado_settings.get("static_url_prefix",
611 607 url_path_join(self.base_url, "static")
612 608 )
613 609
614 610 # try local mathjax, either in nbextensions/mathjax or static/mathjax
615 611 for (url_prefix, search_path) in [
616 612 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
617 613 (static_url_prefix, self.static_file_path),
618 614 ]:
619 615 self.log.debug("searching for local mathjax in %s", search_path)
620 616 try:
621 617 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
622 618 except IOError:
623 619 continue
624 620 else:
625 621 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
626 622 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
627 623 return url
628 624
629 625 # no local mathjax, serve from CDN
630 626 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
631 627 self.log.info("Using MathJax from CDN: %s", url)
632 628 return url
633 629
634 630 def _mathjax_url_changed(self, name, old, new):
635 631 if new and not self.enable_mathjax:
636 632 # enable_mathjax=False overrides mathjax_url
637 633 self.mathjax_url = u''
638 634 else:
639 635 self.log.info("Using MathJax: %s", new)
640 636
641 637 contents_manager_class = Type(
642 638 default_value=FileContentsManager,
643 639 klass=ContentsManager,
644 640 config=True,
645 641 help='The notebook manager class to use.'
646 642 )
647 643 kernel_manager_class = Type(
648 644 default_value=MappingKernelManager,
649 645 config=True,
650 646 help='The kernel manager class to use.'
651 647 )
652 648 session_manager_class = Type(
653 649 default_value=SessionManager,
654 650 config=True,
655 651 help='The session manager class to use.'
656 652 )
657 653 cluster_manager_class = Type(
658 654 default_value=ClusterManager,
659 655 config=True,
660 656 help='The cluster manager class to use.'
661 657 )
662 658
663 659 config_manager_class = Type(
664 660 default_value=ConfigManager,
665 661 config = True,
666 662 help='The config manager class to use'
667 663 )
668 664
669 665 kernel_spec_manager = Instance(KernelSpecManager, allow_none=True)
670 666
671 667 kernel_spec_manager_class = Type(
672 668 default_value=KernelSpecManager,
673 669 config=True,
674 670 help="""
675 671 The kernel spec manager class to use. Should be a subclass
676 672 of `IPython.kernel.kernelspec.KernelSpecManager`.
677 673
678 674 The Api of KernelSpecManager is provisional and might change
679 675 without warning between this version of IPython and the next stable one.
680 676 """
681 677 )
682 678
683 679 login_handler_class = Type(
684 680 default_value=LoginHandler,
685 681 klass=web.RequestHandler,
686 682 config=True,
687 683 help='The login handler class to use.',
688 684 )
689 685
690 686 logout_handler_class = Type(
691 687 default_value=LogoutHandler,
692 688 klass=web.RequestHandler,
693 689 config=True,
694 690 help='The logout handler class to use.',
695 691 )
696 692
697 693 trust_xheaders = Bool(False, config=True,
698 694 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
699 695 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
700 696 )
701 697
702 698 info_file = Unicode()
703 699
704 700 def _info_file_default(self):
705 701 info_file = "nbserver-%s.json"%os.getpid()
706 702 return os.path.join(self.profile_dir.security_dir, info_file)
707 703
708 704 pylab = Unicode('disabled', config=True,
709 705 help="""
710 706 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
711 707 """
712 708 )
713 709 def _pylab_changed(self, name, old, new):
714 710 """when --pylab is specified, display a warning and exit"""
715 711 if new != 'warn':
716 712 backend = ' %s' % new
717 713 else:
718 714 backend = ''
719 715 self.log.error("Support for specifying --pylab on the command line has been removed.")
720 716 self.log.error(
721 717 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
722 718 )
723 719 self.exit(1)
724 720
725 721 notebook_dir = Unicode(config=True,
726 722 help="The directory to use for notebooks and kernels."
727 723 )
728 724
729 725 def _notebook_dir_default(self):
730 726 if self.file_to_run:
731 727 return os.path.dirname(os.path.abspath(self.file_to_run))
732 728 else:
733 729 return py3compat.getcwd()
734 730
735 731 def _notebook_dir_changed(self, name, old, new):
736 732 """Do a bit of validation of the notebook dir."""
737 733 if not os.path.isabs(new):
738 734 # If we receive a non-absolute path, make it absolute.
739 735 self.notebook_dir = os.path.abspath(new)
740 736 return
741 737 if not os.path.isdir(new):
742 738 raise TraitError("No such notebook dir: %r" % new)
743 739
744 740 # setting App.notebook_dir implies setting notebook and kernel dirs as well
745 741 self.config.FileContentsManager.root_dir = new
746 742 self.config.MappingKernelManager.root_dir = new
747 743
748 744 server_extensions = List(Unicode(), config=True,
749 745 help=("Python modules to load as notebook server extensions. "
750 746 "This is an experimental API, and may change in future releases.")
751 747 )
752 748
753 749 reraise_server_extension_failures = Bool(
754 750 False,
755 751 config=True,
756 752 help="Reraise exceptions encountered loading server extensions?",
757 753 )
758 754
759 755 def parse_command_line(self, argv=None):
760 756 super(NotebookApp, self).parse_command_line(argv)
761 757
762 758 if self.extra_args:
763 759 arg0 = self.extra_args[0]
764 760 f = os.path.abspath(arg0)
765 761 self.argv.remove(arg0)
766 762 if not os.path.exists(f):
767 763 self.log.critical("No such file or directory: %s", f)
768 764 self.exit(1)
769 765
770 766 # Use config here, to ensure that it takes higher priority than
771 767 # anything that comes from the profile.
772 768 c = Config()
773 769 if os.path.isdir(f):
774 770 c.NotebookApp.notebook_dir = f
775 771 elif os.path.isfile(f):
776 772 c.NotebookApp.file_to_run = f
777 773 self.update_config(c)
778 774
779 775 def init_configurables(self):
780 776 self.kernel_spec_manager = self.kernel_spec_manager_class(
781 777 parent=self,
782 778 ipython_dir=self.ipython_dir,
783 779 )
784 780 self.kernel_manager = self.kernel_manager_class(
785 781 parent=self,
786 782 log=self.log,
787 783 connection_dir=self.profile_dir.security_dir,
788 784 )
789 785 self.contents_manager = self.contents_manager_class(
790 786 parent=self,
791 787 log=self.log,
792 788 )
793 789 self.session_manager = self.session_manager_class(
794 790 parent=self,
795 791 log=self.log,
796 792 kernel_manager=self.kernel_manager,
797 793 contents_manager=self.contents_manager,
798 794 )
799 795 self.cluster_manager = self.cluster_manager_class(
800 796 parent=self,
801 797 log=self.log,
802 798 )
803 799
804 800 self.config_manager = self.config_manager_class(
805 801 parent=self,
806 802 log=self.log,
807 803 profile_dir=self.profile_dir.location,
808 804 )
809 805
810 806 def init_logging(self):
811 807 # This prevents double log messages because tornado use a root logger that
812 808 # self.log is a child of. The logging module dipatches log messages to a log
813 809 # and all of its ancenstors until propagate is set to False.
814 810 self.log.propagate = False
815 811
816 812 for log in app_log, access_log, gen_log:
817 813 # consistent log output name (NotebookApp instead of tornado.access, etc.)
818 814 log.name = self.log.name
819 815 # hook up tornado 3's loggers to our app handlers
820 816 logger = logging.getLogger('tornado')
821 817 logger.propagate = True
822 818 logger.parent = self.log
823 819 logger.setLevel(self.log.level)
824 820
825 821 def init_webapp(self):
826 822 """initialize tornado webapp and httpserver"""
827 823 self.tornado_settings['allow_origin'] = self.allow_origin
828 824 if self.allow_origin_pat:
829 825 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
830 826 self.tornado_settings['allow_credentials'] = self.allow_credentials
831 827 # ensure default_url starts with base_url
832 828 if not self.default_url.startswith(self.base_url):
833 829 self.default_url = url_path_join(self.base_url, self.default_url)
834 830
835 831 self.web_app = NotebookWebApplication(
836 832 self, self.kernel_manager, self.contents_manager,
837 833 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
838 834 self.config_manager,
839 835 self.log, self.base_url, self.default_url, self.tornado_settings,
840 836 self.jinja_environment_options
841 837 )
842 838 ssl_options = self.ssl_options
843 839 if self.certfile:
844 840 ssl_options['certfile'] = self.certfile
845 841 if self.keyfile:
846 842 ssl_options['keyfile'] = self.keyfile
847 843 if not ssl_options:
848 844 # None indicates no SSL config
849 845 ssl_options = None
850 846 else:
851 847 # Disable SSLv3, since its use is discouraged.
852 848 ssl_options['ssl_version']=ssl.PROTOCOL_TLSv1
853 849 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
854 850 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
855 851 xheaders=self.trust_xheaders)
856 852
857 853 success = None
858 854 for port in random_ports(self.port, self.port_retries+1):
859 855 try:
860 856 self.http_server.listen(port, self.ip)
861 857 except socket.error as e:
862 858 if e.errno == errno.EADDRINUSE:
863 859 self.log.info('The port %i is already in use, trying another random port.' % port)
864 860 continue
865 861 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
866 862 self.log.warn("Permission to listen on port %i denied" % port)
867 863 continue
868 864 else:
869 865 raise
870 866 else:
871 867 self.port = port
872 868 success = True
873 869 break
874 870 if not success:
875 871 self.log.critical('ERROR: the notebook server could not be started because '
876 872 'no available port could be found.')
877 873 self.exit(1)
878 874
879 875 @property
880 876 def display_url(self):
881 877 ip = self.ip if self.ip else '[all ip addresses on your system]'
882 878 return self._url(ip)
883 879
884 880 @property
885 881 def connection_url(self):
886 882 ip = self.ip if self.ip else 'localhost'
887 883 return self._url(ip)
888 884
889 885 def _url(self, ip):
890 886 proto = 'https' if self.certfile else 'http'
891 887 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
892 888
893 889 def init_terminals(self):
894 890 try:
895 891 from .terminal import initialize
896 892 initialize(self.web_app)
897 893 self.web_app.settings['terminals_available'] = True
898 894 except ImportError as e:
899 895 log = self.log.debug if sys.platform == 'win32' else self.log.warn
900 896 log("Terminals not available (error was %s)", e)
901 897
902 898 def init_signal(self):
903 899 if not sys.platform.startswith('win'):
904 900 signal.signal(signal.SIGINT, self._handle_sigint)
905 901 signal.signal(signal.SIGTERM, self._signal_stop)
906 902 if hasattr(signal, 'SIGUSR1'):
907 903 # Windows doesn't support SIGUSR1
908 904 signal.signal(signal.SIGUSR1, self._signal_info)
909 905 if hasattr(signal, 'SIGINFO'):
910 906 # only on BSD-based systems
911 907 signal.signal(signal.SIGINFO, self._signal_info)
912 908
913 909 def _handle_sigint(self, sig, frame):
914 910 """SIGINT handler spawns confirmation dialog"""
915 911 # register more forceful signal handler for ^C^C case
916 912 signal.signal(signal.SIGINT, self._signal_stop)
917 913 # request confirmation dialog in bg thread, to avoid
918 914 # blocking the App
919 915 thread = threading.Thread(target=self._confirm_exit)
920 916 thread.daemon = True
921 917 thread.start()
922 918
923 919 def _restore_sigint_handler(self):
924 920 """callback for restoring original SIGINT handler"""
925 921 signal.signal(signal.SIGINT, self._handle_sigint)
926 922
927 923 def _confirm_exit(self):
928 924 """confirm shutdown on ^C
929 925
930 926 A second ^C, or answering 'y' within 5s will cause shutdown,
931 927 otherwise original SIGINT handler will be restored.
932 928
933 929 This doesn't work on Windows.
934 930 """
935 931 info = self.log.info
936 932 info('interrupted')
937 933 print(self.notebook_info())
938 934 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
939 935 sys.stdout.flush()
940 936 r,w,x = select.select([sys.stdin], [], [], 5)
941 937 if r:
942 938 line = sys.stdin.readline()
943 939 if line.lower().startswith('y') and 'n' not in line.lower():
944 940 self.log.critical("Shutdown confirmed")
945 941 ioloop.IOLoop.current().stop()
946 942 return
947 943 else:
948 944 print("No answer for 5s:", end=' ')
949 945 print("resuming operation...")
950 946 # no answer, or answer is no:
951 947 # set it back to original SIGINT handler
952 948 # use IOLoop.add_callback because signal.signal must be called
953 949 # from main thread
954 950 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
955 951
956 952 def _signal_stop(self, sig, frame):
957 953 self.log.critical("received signal %s, stopping", sig)
958 954 ioloop.IOLoop.current().stop()
959 955
960 956 def _signal_info(self, sig, frame):
961 957 print(self.notebook_info())
962 958
963 959 def init_components(self):
964 960 """Check the components submodule, and warn if it's unclean"""
965 961 status = submodule.check_submodule_status()
966 962 if status == 'missing':
967 963 self.log.warn("components submodule missing, running `git submodule update`")
968 964 submodule.update_submodules(submodule.ipython_parent())
969 965 elif status == 'unclean':
970 966 self.log.warn("components submodule unclean, you may see 404s on static/components")
971 967 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
972 968
973 969 def init_server_extensions(self):
974 970 """Load any extensions specified by config.
975 971
976 972 Import the module, then call the load_jupyter_server_extension function,
977 973 if one exists.
978 974
979 975 The extension API is experimental, and may change in future releases.
980 976 """
981 977 for modulename in self.server_extensions:
982 978 try:
983 979 mod = importlib.import_module(modulename)
984 980 func = getattr(mod, 'load_jupyter_server_extension', None)
985 981 if func is not None:
986 982 func(self)
987 983 except Exception:
988 984 if self.reraise_server_extension_failures:
989 985 raise
990 986 self.log.warn("Error loading server extension %s", modulename,
991 987 exc_info=True)
992 988
993 989 @catch_config_error
994 990 def initialize(self, argv=None):
995 991 super(NotebookApp, self).initialize(argv)
996 992 self.init_logging()
997 993 self.init_configurables()
998 994 self.init_components()
999 995 self.init_webapp()
1000 996 self.init_terminals()
1001 997 self.init_signal()
1002 998 self.init_server_extensions()
1003 999
1004 1000 def cleanup_kernels(self):
1005 1001 """Shutdown all kernels.
1006 1002
1007 1003 The kernels will shutdown themselves when this process no longer exists,
1008 1004 but explicit shutdown allows the KernelManagers to cleanup the connection files.
1009 1005 """
1010 1006 self.log.info('Shutting down kernels')
1011 1007 self.kernel_manager.shutdown_all()
1012 1008
1013 1009 def notebook_info(self):
1014 1010 "Return the current working directory and the server url information"
1015 1011 info = self.contents_manager.info_string() + "\n"
1016 1012 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
1017 1013 return info + "The IPython Notebook is running at: %s" % self.display_url
1018 1014
1019 1015 def server_info(self):
1020 1016 """Return a JSONable dict of information about this server."""
1021 1017 return {'url': self.connection_url,
1022 1018 'hostname': self.ip if self.ip else 'localhost',
1023 1019 'port': self.port,
1024 1020 'secure': bool(self.certfile),
1025 1021 'base_url': self.base_url,
1026 1022 'notebook_dir': os.path.abspath(self.notebook_dir),
1027 1023 'pid': os.getpid()
1028 1024 }
1029 1025
1030 1026 def write_server_info_file(self):
1031 1027 """Write the result of server_info() to the JSON file info_file."""
1032 1028 with open(self.info_file, 'w') as f:
1033 1029 json.dump(self.server_info(), f, indent=2)
1034 1030
1035 1031 def remove_server_info_file(self):
1036 1032 """Remove the nbserver-<pid>.json file created for this server.
1037 1033
1038 1034 Ignores the error raised when the file has already been removed.
1039 1035 """
1040 1036 try:
1041 1037 os.unlink(self.info_file)
1042 1038 except OSError as e:
1043 1039 if e.errno != errno.ENOENT:
1044 1040 raise
1045 1041
1046 1042 def start(self):
1047 1043 """ Start the IPython Notebook server app, after initialization
1048 1044
1049 1045 This method takes no arguments so all configuration and initialization
1050 1046 must be done prior to calling this method."""
1051 1047 if self.subapp is not None:
1052 1048 return self.subapp.start()
1053 1049
1054 1050 info = self.log.info
1055 1051 for line in self.notebook_info().split("\n"):
1056 1052 info(line)
1057 1053 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1058 1054
1059 1055 self.write_server_info_file()
1060 1056
1061 1057 if self.open_browser or self.file_to_run:
1062 1058 try:
1063 1059 browser = webbrowser.get(self.browser or None)
1064 1060 except webbrowser.Error as e:
1065 1061 self.log.warn('No web browser found: %s.' % e)
1066 1062 browser = None
1067 1063
1068 1064 if self.file_to_run:
1069 1065 if not os.path.exists(self.file_to_run):
1070 1066 self.log.critical("%s does not exist" % self.file_to_run)
1071 1067 self.exit(1)
1072 1068
1073 1069 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1074 1070 uri = url_path_join('notebooks', *relpath.split(os.sep))
1075 1071 else:
1076 1072 uri = 'tree'
1077 1073 if browser:
1078 1074 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1079 1075 new=2)
1080 1076 threading.Thread(target=b).start()
1081 1077
1082 1078 self.io_loop = ioloop.IOLoop.current()
1083 1079 if sys.platform.startswith('win'):
1084 1080 # add no-op to wake every 5s
1085 1081 # to handle signals that may be ignored by the inner loop
1086 1082 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1087 1083 pc.start()
1088 1084 try:
1089 1085 self.io_loop.start()
1090 1086 except KeyboardInterrupt:
1091 1087 info("Interrupted...")
1092 1088 finally:
1093 1089 self.cleanup_kernels()
1094 1090 self.remove_server_info_file()
1095 1091
1096 1092 def stop(self):
1097 1093 def _stop():
1098 1094 self.http_server.stop()
1099 1095 self.io_loop.stop()
1100 1096 self.io_loop.add_callback(_stop)
1101 1097
1102 1098
1103 1099 def list_running_servers(profile='default'):
1104 1100 """Iterate over the server info files of running notebook servers.
1105 1101
1106 1102 Given a profile name, find nbserver-* files in the security directory of
1107 1103 that profile, and yield dicts of their information, each one pertaining to
1108 1104 a currently running notebook server instance.
1109 1105 """
1110 1106 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1111 1107 for file in os.listdir(pd.security_dir):
1112 1108 if file.startswith('nbserver-'):
1113 1109 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1114 1110 info = json.load(f)
1115 1111
1116 1112 # Simple check whether that process is really still running
1117 1113 # Also remove leftover files from IPython 2.x without a pid field
1118 1114 if ('pid' in info) and check_pid(info['pid']):
1119 1115 yield info
1120 1116 else:
1121 1117 # If the process has died, try to delete its info file
1122 1118 try:
1123 1119 os.unlink(file)
1124 1120 except OSError:
1125 1121 pass # TODO: This should warn or log or something
1126 1122 #-----------------------------------------------------------------------------
1127 1123 # Main entry point
1128 1124 #-----------------------------------------------------------------------------
1129 1125
1130 1126 launch_new_instance = NotebookApp.launch_instance
1131 1127
@@ -1,72 +1,56 b''
1 """The IPython ZMQ-based parallel computing interface.
1 """The IPython ZMQ-based parallel computing interface."""
2 2
3 Authors:
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 5
5 * MinRK
6 """
7 #-----------------------------------------------------------------------------
8 # Copyright (C) 2011 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-----------------------------------------------------------------------------
13
14 #-----------------------------------------------------------------------------
15 # Imports
16 #-----------------------------------------------------------------------------
17 6
18 7 import os
19 8 import warnings
20 9
21 10 import zmq
22 11
23 12 from IPython.config.configurable import MultipleInstanceError
24 from IPython.utils.zmqrelated import check_for_zmq
25
26 min_pyzmq = '2.1.11'
27
28 check_for_zmq(min_pyzmq, 'ipython_parallel')
29 13
30 14 from IPython.utils.pickleutil import Reference
31 15
32 16 from .client.asyncresult import *
33 17 from .client.client import Client
34 18 from .client.remotefunction import *
35 19 from .client.view import *
36 20 from .controller.dependency import *
37 21 from .error import *
38 22 from .util import interactive
39 23
40 24 #-----------------------------------------------------------------------------
41 25 # Functions
42 26 #-----------------------------------------------------------------------------
43 27
44 28
45 29 def bind_kernel(**kwargs):
46 30 """Bind an Engine's Kernel to be used as a full IPython kernel.
47 31
48 32 This allows a running Engine to be used simultaneously as a full IPython kernel
49 33 with the QtConsole or other frontends.
50 34
51 35 This function returns immediately.
52 36 """
53 37 from IPython.kernel.zmq.kernelapp import IPKernelApp
54 38 from ipython_parallel.apps.ipengineapp import IPEngineApp
55 39
56 40 # first check for IPKernelApp, in which case this should be a no-op
57 41 # because there is already a bound kernel
58 42 if IPKernelApp.initialized() and isinstance(IPKernelApp._instance, IPKernelApp):
59 43 return
60 44
61 45 if IPEngineApp.initialized():
62 46 try:
63 47 app = IPEngineApp.instance()
64 48 except MultipleInstanceError:
65 49 pass
66 50 else:
67 51 return app.bind_kernel(**kwargs)
68 52
69 53 raise RuntimeError("bind_kernel be called from an IPEngineApp instance")
70 54
71 55
72 56
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now