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