#!/usr/bin/env python3 # -*- coding: utf-8; mode: python -*- import argparse import logging import os import sys from contextlib import contextmanager logging.basicConfig() logger = logging.getLogger() from sage.misc.banner import banner _system_jupyter_url = "https://doc.sagemath.org/html/en/installation/launching.html#setting-up-sagemath-as-a-jupyter-kernel-in-an-existing-jupyter-notebook-or-jupyterlab-installation" class NotebookJupyter(): def print_banner(self): print('Please wait while the Sage Jupyter Notebook server starts...') @classmethod def print_help(cls): cls(['help']) def __init__(self, argv): self.print_banner() try: try: # notebook 6 from notebook.notebookapp import main except ImportError: # notebook 7 from notebook.app import main except ImportError: import traceback traceback.print_exc() print("The Jupyter notebook is not installed (at least not in this Sage installation).") print("You can install it by running") print(" sage -i notebook") print("Alternatively, you can follow the instructions at") print(" " + _system_jupyter_url) print("to use Sage with an existing Jupyter notebook installation.") raise SystemExit(1) main(argv) class NotebookJupyterlab(): def print_banner(self): print('Please wait while the Jupyterlab server starts...') @classmethod def print_help(cls): cls(['help']) def __init__(self, argv): try: from jupyterlab.labapp import main except ImportError: import traceback traceback.print_exc() print("Jupyterlab is not installed (at least not in this Sage installation).") print("You can install it by running") print(" sage -i jupyterlab") print("Alternatively, you can follow the instructions at") print(" " + _system_jupyter_url) print("to use Sage with an existing Jupyterlab installation.") raise SystemExit(1) self.print_banner() main(argv) class SageNBExport(NotebookJupyter): def print_banner(self): print('Please wait while the SageNB export server starts...') @classmethod def print_help(cls): cls(['--help']) def __init__(self, argv): if argv: SAGENB_EXPORT = 'sagenb-export' os.execvp(SAGENB_EXPORT, [SAGENB_EXPORT] + argv) argv += [ "--NotebookApp.nbserver_extensions={'sagenb_export.nbextension':True}", "--NotebookApp.default_url='/sagenb'", ] super(SageNBExport, self).__init__(argv) description = \ """ The Sage notebook launcher is used to start the notebook, and allows you to choose between different implementations. Any further command line options are passed to the respective notebook. """ help_help = \ """ show this help message and exit. Can be combined with "--notebook=[...]" to see notebook-specific options """ epilog = \ """ EXAMPLES: * Run default notebook on port 1234. sage -n default --port=1234 sage -n --port=1234 # equivalent * Run Jupyter notebook in custom directory: sage --notebook=jupyter --notebook-dir=/home/foo/bar * List available legacy Sage notebooks: sage --notebook=export --list * Export a legacy Sage notebook as a Jupyter notebook: sage --notebook=export --ipynb=Output.ipynb admin:10 """ notebook_launcher = { 'default': NotebookJupyter, # change this to change the default 'ipython': NotebookJupyter, 'jupyter': NotebookJupyter, 'jupyterlab': NotebookJupyterlab, 'export': SageNBExport, } notebook_names = ', '.join(notebook_launcher.keys()) def make_parser(): """ The main parser handling the selection of the notebook. Any arguments that are not parsed here are supposed to be handled by the notebook implementation. """ parser = argparse.ArgumentParser( description=description, epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter, add_help=False) parser.add_argument('-h', '--help', dest='option_help', action='store_true', default=False, help=help_help) parser.add_argument('--log', dest='log', default=None, help='one of [DEBUG, INFO, ERROR, WARNING, CRITICAL]') default = None for name, launcher in notebook_launcher.items(): if launcher == notebook_launcher['default'] and name != 'default': default = name if default is None: raise RuntimeError('default launcher is defined but not known under a specific name') parser.add_argument('--notebook', # long style '-n', # short style '-notebook', # wtf style, we can't decide (legacy support) dest='notebook', type=str, nargs='?', const='default', help='The notebook to run [one of: {0}]. Default is {1}'.format( notebook_names, default)) return parser def trac_23428_browser_workaround(): """ Running 'sage -n" with the Jupyter notebook on Darwin fails to open a browser automatically. See :trac:`23428`. """ if sys.platform != 'darwin': return if not os.environ.get('BROWSER', False): os.environ['BROWSER'] = 'open' @contextmanager def sage_doc_server(): from functools import partial from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer from threading import Thread from sage.env import SAGE_DOC from sage.env import SAGE_DOC_LOCAL_PORT as port server = ThreadingHTTPServer(('127.0.0.1', int(port)), partial(SimpleHTTPRequestHandler, directory=SAGE_DOC)) if port == '0': port = str(server.server_address[1]) os.environ['SAGE_DOC_LOCAL_PORT'] = port server_thread = Thread(target=server.serve_forever, name="sage_doc_server") server_thread.start() print(f'Sage doc server started running at http://127.0.0.1:{port}') try: yield finally: server.shutdown() server_thread.join() print(f'Sage doc server stopped runnning at http://127.0.0.1:{port}') if __name__ == '__main__': parser = make_parser() args, unknown = parser.parse_known_args(sys.argv[1:]) if unknown and unknown[0] == '--': unknown = unknown[1:] trac_23428_browser_workaround() if args.log is not None: import logging level = getattr(logging, args.log.upper()) logger.setLevel(level=level) logger.info('Main parser got arguments %s', args) logger.info('Passing on to notebook implementation: %s', unknown) if args.notebook == "sagenb": logger.critical('cannot use the legacy notebook SageNB with Python 3') print('The legacy notebook does not work under Python 3; ' 'use the Jupyter notebook.') print('See https://wiki.sagemath.org/Python3-Switch') print('Use \"sage --notebook=export\" to export SageNB notebooks ' 'to Jupyter') sys.exit(1) from sage.repl.ipython_kernel.install import SageKernelSpec SageKernelSpec.check() try: launcher = notebook_launcher[args.notebook] except KeyError: logger.critical('unknown notebook: %s', args.notebook) print('Error, notebook must be one of {0} but got {1}'. format(notebook_names, args.notebook)) sys.exit(1) if args.option_help: if args.notebook == 'default': parser.print_help() else: launcher.print_help() sys.exit(0) banner() # Start a Sage doc server if the Sage documentation is available locally. # See the corresponding code in src/sage/repl/ipython_kernel/kernel.py. from sage.env import SAGE_DOC_SERVER_URL from sage.features.sagemath import sagemath_doc_html if SAGE_DOC_SERVER_URL: print(f'Sage doc server is running at {SAGE_DOC_SERVER_URL}') launcher(unknown) elif sagemath_doc_html().is_present(): with sage_doc_server(): launcher(unknown) else: launcher(unknown)