Example #1
0
    def initialize_settings(self):
        """Add settings to the tornado app."""
        if self.ignore_minified_js:
            self.log.warning(
                _("""The `ignore_minified_js` flag is deprecated and no longer works."""
                  ))
            self.log.warning(
                _("""Alternatively use `%s` when working on the notebook's Javascript and LESS"""
                  ) % 'npm run build:watch')
            warnings.warn(
                _("The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"
                  ), DeprecationWarning)

        settings = dict(
            static_custom_path=self.static_custom_path,
            static_handler_args={
                # don't cache custom.js
                'no_cache_paths': [
                    url_path_join(self.serverapp.base_url, 'static', self.name,
                                  'custom')
                ],
            },
            ignore_minified_js=self.ignore_minified_js,
            mathjax_url=self.mathjax_url,
            mathjax_config=self.mathjax_config,
            nbextensions_path=self.nbextensions_path,
        )
        self.settings.update(**settings)
Example #2
0
    def _autodetect_language_servers(self, only_installed: bool):
        entry_points = {}

        try:
            entry_points = entrypoints.get_group_named(EP_SPEC_V1)
        except Exception:  # pragma: no cover
            self.log.exception("Failed to load entry_points")

        skipped_servers = []

        for ep_name, ep in entry_points.items():
            try:
                spec_finder = ep.load()  # type: SpecMaker
            except Exception as err:  # pragma: no cover
                self.log.warning(
                    _("Failed to load language server spec finder `{}`: \n{}").
                    format(ep_name, err))
                continue

            try:
                if only_installed:
                    if hasattr(spec_finder, "is_installed"):
                        spec_finder_from_base = cast(SpecBase, spec_finder)
                        if not spec_finder_from_base.is_installed(self):
                            skipped_servers.append(ep.name)
                            continue
                specs = spec_finder(self) or {}
            except Exception as err:  # pragma: no cover
                self.log.warning(
                    _("Failed to fetch commands from language server spec finder"
                      " `{}`:\n{}").format(ep.name, err))
                traceback.print_exc()

                continue

            errors = list(LANGUAGE_SERVER_SPEC_MAP.iter_errors(specs))

            if errors:  # pragma: no cover
                self.log.warning(
                    _("Failed to validate commands from language server spec finder"
                      " `{}`:\n{}").format(ep.name, errors))
                continue

            for key, spec in specs.items():
                yield key, spec

        if skipped_servers:
            self.log.info(
                _("Skipped non-installed server(s): {}").format(
                    ", ".join(skipped_servers)))
Example #3
0
class Jupyterfs(Configurable):
    root_manager_class = Type(
        config=True,
        default_value=LargeFileManager,
        help=
        _("the root contents manager class to use. Used by the Jupyterlab default filebrowser and elsewhere"
          ),
        klass=ContentsManager,
    )

    resources = List(
        config=True,
        default_value=[],
        help=_("server-side definitions of fsspec resources for jupyter-fs"),
        # trait=Dict(traits={"name": Unicode, "url": Unicode}),
    )
class ExtensionAppJinjaMixin:
    """Use Jinja templates for HTML templates on top of an ExtensionApp."""

    jinja2_options = Dict(
        help=_("""Options to pass to the jinja2 environment for this
        extension.
        """)).tag(config=True)

    def _prepare_templates(self):
        # Add templates to web app settings if extension has templates.
        if len(self.template_paths) > 0:
            self.settings.update({
                "{}_template_paths".format(self.extension_name):
                self.template_paths
            })

        # Create a jinja environment for logging html templates.
        self.jinja2_env = Environment(loader=FileSystemLoader(
            self.template_paths),
                                      extensions=['jinja2.ext.i18n'],
                                      autoescape=True,
                                      **self.jinja2_options)

        # Get templates defined in a subclass.
        self.initialize_templates()

        # Add the jinja2 environment for this extension to the tornado settings.
        self.settings.update(
            {"{}_jinja2_env".format(self.extension_name): self.jinja2_env})
Example #5
0
 def _update_mathjax_url(self, change):
     new = change['new']
     if new and not self.enable_mathjax:
         # enable_mathjax=False overrides mathjax_url
         self.mathjax_url = u''
     else:
         self.log.info(_("Using MathJax: %s"), new)
Example #6
0
class AuthorizedServerApp(HubAuth, ServerApp):

    name = 'jhubshare'
    description = _("""The Jupyter Server with authorization""")
    port = 9999

    # Let's not serve any of jupyter's core services
    default_services = []

    # But let's add the new (patched) contents service.
    extra_services = [
        'jupyter_authorized_server.contents'
    ]

    # Files to pull authorization details from.
    model_file = Unicode('model.conf', config=True)
    policy_file = Unicode('policy.csv', config=True)

    # Setup URLs.
    default_url = "/services/jhubshare"
    base_url = Unicode(config=True)

    @default('base_url')
    def _default_base_url(self):
        try:
            base_url = os.environ['JUPYTERHUB_SERVICE_PREFIX']
        except KeyError:
            raise Exception("no url found.")
        return base_url        

    api_token = Unicode(config=True)

    @default('api_token')
    def _default_api_token(self):
        try:
            api_token = os.environ['JUPYTERHUB_API_TOKEN']
        except KeyError:
            raise Exception(
                "No API token was found in the environment variables. "
                "Are you running this as a hub-managed service?"
            )
        return api_token


    def initialize_enforcer(self):
        self.enforcer = casbin.Enforcer(self.model_file, self.policy_file)
        self.tornado_settings['enforcer'] = self.enforcer

    def initialize(self, argv=None, load_extensions=False):
        super(ServerApp, self).initialize(argv)
        self.init_logging()
        self.init_configurables()
        self.init_components()
        self.initialize_enforcer()
        self.init_webapp()
        self.init_signal()
        self.init_mime_overrides()
        self.init_shutdown_no_activity()
Example #7
0
    def initialize_settings(self):
        """Add settings to the tornado app."""
        if self.ignore_minified_js:
            self.log.warning(
                _("""The `ignore_minified_js` flag is deprecated and no longer works."""
                  ))
            self.log.warning(
                _("""Alternatively use `%s` when working on the notebook's Javascript and LESS"""
                  ) % 'npm run build:watch')
            warnings.warn(
                _("The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"
                  ), DeprecationWarning)

        settings = dict(
            ignore_minified_js=self.ignore_minified_js,
            mathjax_url=self.mathjax_url,
            mathjax_config=self.mathjax_config,
            nbextensions_path=self.nbextensions_path,
        )
        self.settings.update(**settings)
class MathJaxExtension(ExtensionApp):

    name = "jupyter_server_mathjax"

    # By listing the path to the assets here, jupyter_server
    # automatically creates a static file handler at
    # /static/jupyter_server_mathjax/...
    static_paths = [str(STATIC_ASSETS_PATH)]

    mathjax_config = Unicode(
        "TeX-AMS-MML_HTMLorMML-full,Safe",
        config=True,
        help=_("""The MathJax.js configuration file that is to be used."""),
    )

    @observe("mathjax_config")
    def _update_mathjax_config(self, change):
        self.log.info(_("Using MathJax configuration file: %s"), change["new"])

    def initialize_settings(self):
        # Add settings specific to this extension to the
        # tornado webapp settings.
        self.settings.update({
            "mathjax_config":
            self.mathjax_config,
            "mathjax_url":
            url_path_join(self.static_url_prefix, "MathJax.js"),
        })

    def initialize_handlers(self):
        webapp = self.serverapp.web_app
        base_url = self.serverapp.base_url
        host_pattern = ".*$"

        # Add a deprecated redirect for all MathJax paths from the classic
        # notebook to the static endpoint created for this extension.
        webapp.add_handlers(
            host_pattern,
            [(
                url_path_join(base_url, "/static/components/MathJax/(.*)"),
                DeprecatedRedirectHandler,
                {
                    "url":
                    url_path_join(
                        self.static_url_prefix,
                        "/{0}"  # {0} = group 0 in url path
                    )
                },
            )],
        )
Example #9
0
    def _autodetect_language_servers(self):
        entry_points = []

        try:
            entry_points = entrypoints.get_group_named(EP_SPEC_V1)
        except Exception:  # pragma: no cover
            self.log.exception("Failed to load entry_points")

        for ep_name, ep in entry_points.items():
            try:
                spec_finder = ep.load()  # type: SpecMaker
            except Exception as err:  # pragma: no cover
                self.log.warn(
                    _("Failed to load language server spec finder `{}`: \n{}").
                    format(ep_name, err))
                continue

            try:
                specs = spec_finder(self)
            except Exception as err:  # pragma: no cover
                self.log.warning(
                    _("Failed to fetch commands from language server spec finder"
                      "`{}`:\n{}").format(ep.name, err))
                traceback.print_exc()

                continue

            errors = list(LANGUAGE_SERVER_SPEC_MAP.iter_errors(specs))

            if errors:  # pragma: no cover
                self.log.warning(
                    _("Failed to validate commands from language server spec finder"
                      "`{}`:\n{}").format(ep.name, errors))
                continue

            for key, spec in specs.items():
                yield key, spec
def get_frontend_exporters():
    from nbconvert.exporters.base import get_export_names, get_exporter

    # name=exporter_name, display=export_from_notebook+extension
    ExporterInfo = namedtuple('ExporterInfo', ['name', 'display'])

    default_exporters = [
        ExporterInfo(name='html', display='HTML (.html)'),
        ExporterInfo(name='latex', display='LaTeX (.tex)'),
        ExporterInfo(name='markdown', display='Markdown (.md)'),
        ExporterInfo(name='notebook', display='Notebook (.ipynb)'),
        ExporterInfo(name='pdf', display='PDF via LaTeX (.pdf)'),
        ExporterInfo(name='rst', display='reST (.rst)'),
        ExporterInfo(name='script', display='Script (.txt)'),
        ExporterInfo(name='slides', display='Reveal.js slides (.slides.html)')
    ]

    frontend_exporters = []
    for name in get_export_names():
        exporter_class = get_exporter(name)
        exporter_instance = exporter_class()
        ux_name = getattr(exporter_instance, 'export_from_notebook', None)
        super_uxname = getattr(super(exporter_class, exporter_instance),
                               'export_from_notebook', None)

        # Ensure export_from_notebook is explicitly defined & not inherited
        if ux_name is not None and ux_name != super_uxname:
            display = _('{} ({})'.format(ux_name,
                                         exporter_instance.file_extension))
            frontend_exporters.append(ExporterInfo(name, display))

    # Ensure default_exporters are in frontend_exporters if not already
    # This protects against nbconvert versions lower than 5.5
    names = set(exporter.name.lower() for exporter in frontend_exporters)
    for exporter in default_exporters:
        if exporter.name not in names:
            frontend_exporters.append(exporter)

    # Protect against nbconvert 5.5.0
    python_exporter = ExporterInfo(name='python', display='python (.py)')
    if python_exporter in frontend_exporters:
        frontend_exporters.remove(python_exporter)

    # Protect against nbconvert 5.4.x
    template_exporter = ExporterInfo(name='custom', display='custom (.txt)')
    if template_exporter in frontend_exporters:
        frontend_exporters.remove(template_exporter)
    return sorted(frontend_exporters)
class ExtensionApp(JupyterApp):
    """Base class for configurable Jupyter Server Extension Applications.

    ExtensionApp subclasses can be initialized two ways:
    1. Extension is listed as a jpserver_extension, and ServerApp calls
        its load_jupyter_server_extension classmethod. This is the
        classic way of loading a server extension.
    2. Extension is launched directly by calling its `launch_instance`
        class method. This method can be set as a entry_point in
        the extensions setup.py
    """
    # Subclasses should override this trait. Tells the server if
    # this extension allows other other extensions to be loaded
    # side-by-side when launched directly.
    load_other_extensions = True

    # A useful class property that subclasses can override to
    # configure the underlying Jupyter Server when this extension
    # is launched directly (using its `launch_instance` method).
    serverapp_config = {}

    # Some subclasses will likely override this trait to flip
    # the default value to False if they don't offer a browser
    # based frontend.
    open_browser = Bool(True,
                        help="""Whether to open in a browser after starting.
        The specific browser used is platform dependent and
        determined by the python standard library `webbrowser`
        module, unless it is overridden using the --browser
        (ServerApp.browser) configuration option.
        """).tag(config=True)

    # The extension name used to name the jupyter config
    # file, jupyter_{name}_config.
    # This should also match the jupyter subcommand used to launch
    # this extension from the CLI, e.g. `jupyter {name}`.
    name = None

    @classmethod
    def get_extension_package(cls):
        return cls.__module__.split('.')[0]

    @classmethod
    def get_extension_point(cls):
        return cls.__module__

    # Extension URL sets the default landing page for this extension.
    extension_url = "/"

    # Extension can configure the ServerApp from the command-line
    classes = [
        ServerApp,
    ]

    # A ServerApp is not defined yet, but will be initialized below.
    serverapp = None

    _log_formatter_cls = LogFormatter

    # Whether this app is the starter app
    _is_starter_app = False

    @default('log_level')
    def _default_log_level(self):
        return logging.INFO

    @default('log_format')
    def _default_log_format(self):
        """override default log format to include date & time"""
        return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"

    static_url_prefix = Unicode(
        help="""Url where the static assets for the extension are served."""
    ).tag(config=True)

    @default('static_url_prefix')
    def _default_static_url_prefix(self):
        static_url = "static/{name}/".format(name=self.name)
        return url_path_join(self.serverapp.base_url, static_url)

    static_paths = List(Unicode(),
                        help="""paths to search for serving static files.

        This allows adding javascript/css to be available from the notebook server machine,
        or overriding individual files in the IPython
        """).tag(config=True)

    template_paths = List(
        Unicode(),
        help=_("""Paths to search for serving jinja templates.

        Can be used to override templates from notebook.templates.""")).tag(
            config=True)

    settings = Dict(
        help=_("""Settings that will passed to the server.""")).tag(
            config=True)

    handlers = List(help=_("""Handlers appended to the server.""")).tag(
        config=True)

    def _config_file_name_default(self):
        """The default config file name."""
        if not self.name:
            return ''
        return 'jupyter_{}_config'.format(self.name.replace('-', '_'))

    def initialize_settings(self):
        """Override this method to add handling of settings."""
        pass

    def initialize_handlers(self):
        """Override this method to append handlers to a Jupyter Server."""
        pass

    def initialize_templates(self):
        """Override this method to add handling of template files."""
        pass

    def _prepare_config(self):
        """Builds a Config object from the extension's traits and passes
        the object to the webapp's settings as `<name>_config`.
        """
        traits = self.class_own_traits().keys()
        self.extension_config = Config({t: getattr(self, t) for t in traits})
        self.settings['{}_config'.format(self.name)] = self.extension_config

    def _prepare_settings(self):
        # Make webapp settings accessible to initialize_settings method
        webapp = self.serverapp.web_app
        self.settings.update(**webapp.settings)

        # Add static and template paths to settings.
        self.settings.update({
            "{}_static_paths".format(self.name): self.static_paths,
            "{}".format(self.name): self,
        })

        # Get setting defined by subclass using initialize_settings method.
        self.initialize_settings()

        # Update server settings with extension settings.
        webapp.settings.update(**self.settings)

    def _prepare_handlers(self):
        webapp = self.serverapp.web_app

        # Get handlers defined by extension subclass.
        self.initialize_handlers()

        # prepend base_url onto the patterns that we match
        new_handlers = []
        for handler_items in self.handlers:
            # Build url pattern including base_url
            pattern = url_path_join(webapp.settings['base_url'],
                                    handler_items[0])
            handler = handler_items[1]

            # Get handler kwargs, if given
            kwargs = {}
            if issubclass(handler, ExtensionHandlerMixin):
                kwargs['name'] = self.name

            try:
                kwargs.update(handler_items[2])
            except IndexError:
                pass

            new_handler = (pattern, handler, kwargs)
            new_handlers.append(new_handler)

        # Add static endpoint for this extension, if static paths are given.
        if len(self.static_paths) > 0:
            # Append the extension's static directory to server handlers.
            static_url = url_path_join(self.static_url_prefix, "(.*)")

            # Construct handler.
            handler = (static_url, webapp.settings['static_handler_class'], {
                'path': self.static_paths
            })
            new_handlers.append(handler)

        webapp.add_handlers('.*$', new_handlers)

    def _prepare_templates(self):
        # Add templates to web app settings if extension has templates.
        if len(self.template_paths) > 0:
            self.settings.update(
                {"{}_template_paths".format(self.name): self.template_paths})
        self.initialize_templates()

    @classmethod
    def _jupyter_server_config(cls):
        base_config = {
            "ServerApp": {
                "jpserver_extensions": {
                    cls.get_extension_package(): True
                },
                "default_url": cls.extension_url
            }
        }
        base_config["ServerApp"].update(cls.serverapp_config)
        return base_config

    def _link_jupyter_server_extension(self, serverapp):
        """Link the ExtensionApp to an initialized ServerApp.

        The ServerApp is stored as an attribute and config
        is exchanged between ServerApp and `self` in case
        the command line contains traits for the ExtensionApp
        or the ExtensionApp's config files have server
        settings.
        """
        self.serverapp = serverapp
        # Load config from an ExtensionApp's config files.
        self.load_config_file()
        # ServerApp's config might have picked up
        # config for the ExtensionApp. We call
        # update_config to update ExtensionApp's
        # traits with these values found in ServerApp's
        # config.
        # ServerApp config ---> ExtensionApp traits
        self.update_config(self.serverapp.config)
        # Use ExtensionApp's CLI parser to find any extra
        # args that passed through ServerApp and
        # now belong to ExtensionApp.
        self.parse_command_line(self.serverapp.extra_args)
        # If any config should be passed upstream to the
        # ServerApp, do it here.
        # i.e. ServerApp traits <--- ExtensionApp config
        self.serverapp.update_config(self.config)

    @classmethod
    def initialize_server(cls, argv=[], load_other_extensions=True, **kwargs):
        """Creates an instance of ServerApp where this extension is enabled
        (superceding disabling found in other config from files).

        This is necessary when launching the ExtensionApp directly from
        the `launch_instance` classmethod.
        """
        # The ExtensionApp needs to add itself as enabled extension
        # to the jpserver_extensions trait, so that the ServerApp
        # initializes it.
        config = Config(cls._jupyter_server_config())
        serverapp = ServerApp.instance(**kwargs, argv=[], config=config)
        cls._is_starter_app = True
        serverapp.initialize(argv=argv, find_extensions=load_other_extensions)
        return serverapp

    def initialize(self):
        """Initialize the extension app. The
        corresponding server app and webapp should already
        be initialized by this step.

        1) Appends Handlers to the ServerApp,
        2) Passes config and settings from ExtensionApp
        to the Tornado web application
        3) Points Tornado Webapp to templates and
        static assets.
        """
        if not self.serverapp:
            msg = ("This extension has no attribute `serverapp`. "
                   "Try calling `.link_to_serverapp()` before calling "
                   "`.initialize()`.")
            raise JupyterServerExtensionException(msg)

        self._prepare_config()
        self._prepare_templates()
        self._prepare_settings()
        self._prepare_handlers()

    def start(self):
        """Start the underlying Jupyter server.

        Server should be started after extension is initialized.
        """
        super(ExtensionApp, self).start()
        # Start the server.
        self.serverapp.start()

    def stop(self):
        """Stop the underlying Jupyter server.
        """
        self.serverapp.stop()
        self.serverapp.clear_instance()

    @classmethod
    def _load_jupyter_server_extension(cls, serverapp):
        """Initialize and configure this extension, then add the extension's
        settings and handlers to the server's web application.
        """
        extension_manager = serverapp.extension_manager
        try:
            # Get loaded extension from serverapp.
            point = extension_manager.extension_points[cls.name]
            extension = point.app
        except KeyError:
            extension = cls()
            extension._link_jupyter_server_extension(serverapp)
        if cls._is_starter_app:
            serverapp._starter_app = extension
        extension.initialize()
        return extension

    @classmethod
    def load_classic_server_extension(cls, serverapp):
        """Enables extension to be loaded as classic Notebook (jupyter/notebook) extension.
        """
        extension = cls()
        extension.serverapp = serverapp
        extension.load_config_file()
        extension.update_config(serverapp.config)
        extension.parse_command_line(serverapp.extra_args)
        # Add redirects to get favicons from old locations in the classic notebook server
        extension.handlers.extend([
            (r"/static/favicons/favicon.ico", RedirectHandler, {
                "url":
                url_path_join(serverapp.base_url,
                              "static/base/images/favicon.ico")
            }),
            (r"/static/favicons/favicon-busy-1.ico", RedirectHandler, {
                "url":
                url_path_join(serverapp.base_url,
                              "static/base/images/favicon-busy-1.ico")
            }),
            (r"/static/favicons/favicon-busy-2.ico", RedirectHandler, {
                "url":
                url_path_join(serverapp.base_url,
                              "static/base/images/favicon-busy-2.ico")
            }),
            (r"/static/favicons/favicon-busy-3.ico", RedirectHandler, {
                "url":
                url_path_join(serverapp.base_url,
                              "static/base/images/favicon-busy-3.ico")
            }),
            (r"/static/favicons/favicon-file.ico", RedirectHandler, {
                "url":
                url_path_join(serverapp.base_url,
                              "static/base/images/favicon-file.ico")
            }),
            (r"/static/favicons/favicon-notebook.ico", RedirectHandler, {
                "url":
                url_path_join(serverapp.base_url,
                              "static/base/images/favicon-notebook.ico")
            }),
            (r"/static/favicons/favicon-terminal.ico", RedirectHandler, {
                "url":
                url_path_join(serverapp.base_url,
                              "static/base/images/favicon-terminal.ico")
            }),
            (r"/static/logo/logo.png", RedirectHandler, {
                "url":
                url_path_join(serverapp.base_url,
                              "static/base/images/logo.png")
            }),
        ])
        extension.initialize()

    @classmethod
    def launch_instance(cls, argv=None, **kwargs):
        """Launch the extension like an application. Initializes+configs a stock server
        and appends the extension to the server. Then starts the server and routes to
        extension's landing page.
        """
        # Handle arguments.
        if argv is None:
            args = sys.argv[1:]  # slice out extension config.
        else:
            args = argv
        # Check for subcommands
        subapp = _preparse_for_subcommand(cls, args)
        if subapp:
            subapp.start()
        else:
            # Check for help, version, and generate-config arguments
            # before initializing server to make sure these
            # arguments trigger actions from the extension not the server.
            _preparse_for_stopping_flags(cls, args)
            # Get a jupyter server instance.
            serverapp = cls.initialize_server(
                argv=args, load_other_extensions=cls.load_other_extensions)
            # Log if extension is blocking other extensions from loading.
            if not cls.load_other_extensions:
                serverapp.log.info(
                    "{ext_name} is running without loading "
                    "other extensions.".format(ext_name=cls.name))
            serverapp.start()
Example #12
0
class ExtensionApp(JupyterApp):
    """Base class for configurable Jupyter Server Extension Applications.

    ExtensionApp subclasses can be initialized two ways:
    1. Extension is listed as a jpserver_extension, and ServerApp calls 
        its load_jupyter_server_extension classmethod. This is the 
        classic way of loading a server extension.
    2. Extension is launched directly by calling its `launch_instance`
        class method. This method can be set as a entry_point in 
        the extensions setup.py
    """
    load_other_extensions = True

    # Name of the extension
    extension_name = Unicode("", help="Name of extension.")

    @default("extension_name")
    def _default_extension_name(self):
        try:
            return self.name
        except AttributeError:
            raise ValueError("The extension must be given a `name`.")

    INVALID_EXTENSION_NAME_CHARS = [' ', '.', '+', '/']

    def _validate_extension_name(self):
        value = self.extension_name
        if isinstance(value, str):
            # Validate that extension_name doesn't contain any invalid characters.
            for c in ExtensionApp.INVALID_EXTENSION_NAME_CHARS:
                if c in value:
                    raise ValueError(
                        "Extension name '{name}' cannot contain any of the following characters: "
                        "{invalid_chars}.".format(
                            name=value,
                            invalid_chars=ExtensionApp.
                            INVALID_EXTENSION_NAME_CHARS))
            return value
        raise ValueError(
            "Extension name must be a string, found {type}.".format(
                type=type(value)))

    # Extension can configure the ServerApp from the command-line
    classes = [
        ServerApp,
    ]

    aliases = aliases
    flags = flags

    @property
    def static_url_prefix(self):
        return "/static/{extension_name}/".format(
            extension_name=self.extension_name)

    static_paths = List(Unicode(),
                        help="""paths to search for serving static files.
        
        This allows adding javascript/css to be available from the notebook server machine,
        or overriding individual files in the IPython
        """).tag(config=True)

    template_paths = List(
        Unicode(),
        help=_("""Paths to search for serving jinja templates.

        Can be used to override templates from notebook.templates.""")).tag(
            config=True)

    settings = Dict(
        help=_("""Settings that will passed to the server.""")).tag(
            config=True)

    handlers = List(help=_("""Handlers appended to the server.""")).tag(
        config=True)

    default_url = Unicode('/',
                          config=True,
                          help=_("The default URL to redirect to from `/`"))

    def initialize_settings(self):
        """Override this method to add handling of settings."""
        pass

    def initialize_handlers(self):
        """Override this method to append handlers to a Jupyter Server."""
        pass

    def initialize_templates(self):
        """Override this method to add handling of template files."""
        pass

    def _prepare_config(self):
        """Builds a Config object from the extension's traits and passes
        the object to the webapp's settings as `<extension_name>_config`.  
        """
        traits = self.class_own_traits().keys()
        self.extension_config = Config({t: getattr(self, t) for t in traits})
        self.settings['{}_config'.format(
            self.extension_name)] = self.extension_config

    def _prepare_settings(self):
        # Make webapp settings accessible to initialize_settings method
        webapp = self.serverapp.web_app
        self.settings.update(**webapp.settings)

        # Add static and template paths to settings.
        self.settings.update({
            "{}_static_paths".format(self.extension_name):
            self.static_paths,
        })

        # Get setting defined by subclass using initialize_settings method.
        self.initialize_settings()

        # Update server settings with extension settings.
        webapp.settings.update(**self.settings)

    def _prepare_handlers(self):
        webapp = self.serverapp.web_app

        # Get handlers defined by extension subclass.
        self.initialize_handlers()

        # prepend base_url onto the patterns that we match
        new_handlers = []
        for handler_items in self.handlers:
            # Build url pattern including base_url
            pattern = url_path_join(webapp.settings['base_url'],
                                    handler_items[0])
            handler = handler_items[1]

            # Get handler kwargs, if given
            kwargs = {}
            if issubclass(handler, ExtensionHandler):
                kwargs['extension_name'] = self.extension_name
            try:
                kwargs.update(handler_items[2])
            except IndexError:
                pass

            new_handler = (pattern, handler, kwargs)
            new_handlers.append(new_handler)

        # Add static endpoint for this extension, if static paths are given.
        if len(self.static_paths) > 0:
            # Append the extension's static directory to server handlers.
            static_url = url_path_join("/static", self.extension_name, "(.*)")

            # Construct handler.
            handler = (static_url, webapp.settings['static_handler_class'], {
                'path': self.static_paths
            })
            new_handlers.append(handler)

        webapp.add_handlers('.*$', new_handlers)

    def _prepare_templates(self):
        # Add templates to web app settings if extension has templates.
        if len(self.template_paths) > 0:
            self.settings.update({
                "{}_template_paths".format(self.extension_name):
                self.template_paths
            })
        self.initialize_templates()

    @staticmethod
    def initialize_server(argv=[], load_other_extensions=True, **kwargs):
        """Get an instance of the Jupyter Server."""
        # Get a jupyter server instance
        serverapp = ServerApp.instance(**kwargs)
        # Initialize ServerApp config.
        # Parses the command line looking for
        # ServerApp configuration.
        serverapp.initialize(argv=argv, load_extensions=load_other_extensions)
        return serverapp

    def initialize(self, serverapp, argv=[]):
        """Initialize the extension app.
        
        This method:
        - Loads the extension's config from file
        - Updates the extension's config from argv
        - Initializes templates environment
        - Passes settings to webapp
        - Appends handlers to webapp.
        """
        self._validate_extension_name()
        # Initialize the extension application
        super(ExtensionApp, self).initialize(argv=argv)
        self.serverapp = serverapp

        # Initialize config, settings, templates, and handlers.
        self._prepare_config()
        self._prepare_templates()
        self._prepare_settings()
        self._prepare_handlers()

    def start(self, **kwargs):
        """Start the underlying Jupyter server.
        
        Server should be started after extension is initialized.
        """
        # Start the browser at this extensions default_url.
        self.serverapp.default_url = self.default_url
        # Start the server.
        self.serverapp.start(**kwargs)

    def stop(self):
        """Stop the underlying Jupyter server.
        """
        self.serverapp.stop()
        self.serverapp.clear_instance()

    @classmethod
    def load_jupyter_server_extension(cls, serverapp, argv=[], **kwargs):
        """Initialize and configure this extension, then add the extension's
        settings and handlers to the server's web application.
        """
        # Configure and initialize extension.
        extension = cls()
        extension.initialize(serverapp, argv=argv)
        return extension

    @classmethod
    def launch_instance(cls, argv=None, **kwargs):
        """Launch the extension like an application. Initializes+configs a stock server 
        and appends the extension to the server. Then starts the server and routes to
        extension's landing page.
        """
        # Check for help, version, and generate-config arguments
        # before initializing server to make sure these
        # arguments trigger actions from the extension not the server.
        _preparse_command_line(cls)
        # Handle arguments.
        if argv is None:
            args = sys.argv[1:]  # slice out extension config.
        else:
            args = []
        # Get a jupyter server instance.
        serverapp = cls.initialize_server(
            argv=args, load_other_extensions=cls.load_other_extensions)
        # Log if extension is blocking other extensions from loading.
        if not cls.load_other_extensions:
            serverapp.log.info(
                "{ext_name} is running without loading "
                "other extensions.".format(ext_name=cls.extension_name))

        extension = cls.load_jupyter_server_extension(serverapp,
                                                      argv=args,
                                                      **kwargs)
        # Start the ioloop.
        extension.start()
Example #13
0
class LanguageServerManager(LanguageServerManagerAPI):
    """Manage language servers"""

    conf_d_language_servers = Schema(
        validator=LANGUAGE_SERVER_SPEC_MAP,
        help=_(
            "extra language server specs, keyed by implementation, from conf.d"
        ),
    )  # type: KeyedLanguageServerSpecs

    language_servers = Schema(
        validator=LANGUAGE_SERVER_SPEC_MAP,
        help=_("a dict of language server specs, keyed by implementation"),
    ).tag(config=True)  # type: KeyedLanguageServerSpecs

    autodetect = Bool(
        True,
        help=_(
            "try to find known language servers in sys.prefix (and elsewhere)")
    ).tag(config=True)  # type: bool

    sessions = Dict_(
        trait=Instance(LanguageServerSession),
        default_value={},
        help="sessions keyed by language server name",
    )  # type: Dict[Tuple[Text], LanguageServerSession]

    virtual_documents_dir = Unicode(
        help="""Path to virtual documents relative to the content manager root
        directory.

        Its default value can be set with JP_LSP_VIRTUAL_DIR and fallback to
        '.virtual_documents'.
        """).tag(config=True)

    all_listeners = List_(trait=LoadableCallable).tag(config=True)
    server_listeners = List_(trait=LoadableCallable).tag(config=True)
    client_listeners = List_(trait=LoadableCallable).tag(config=True)

    @default("language_servers")
    def _default_language_servers(self):
        return {}

    @default("virtual_documents_dir")
    def _default_virtual_documents_dir(self):
        return os.getenv("JP_LSP_VIRTUAL_DIR", ".virtual_documents")

    @default("conf_d_language_servers")
    def _default_conf_d_language_servers(self):
        language_servers = {}  # type: KeyedLanguageServerSpecs

        manager = ConfigManager(read_config_path=jupyter_config_path())

        for app in APP_CONFIG_D_SECTIONS:
            language_servers.update(**manager.get(f"jupyter{app}config").get(
                self.__class__.__name__, {}).get("language_servers", {}))

        return language_servers

    def __init__(self, **kwargs):
        """Before starting, perform all necessary configuration"""
        self.all_language_servers: KeyedLanguageServerSpecs = {}
        self._language_servers_from_config = {}
        super().__init__(**kwargs)

    def initialize(self, *args, **kwargs):
        self.init_language_servers()
        self.init_listeners()
        self.init_sessions()

    def init_language_servers(self) -> None:
        """determine the final language server configuration."""
        # copy the language servers before anybody monkeys with them
        self._language_servers_from_config = dict(self.language_servers)
        self.language_servers = self._collect_language_servers(
            only_installed=True)
        self.all_language_servers = self._collect_language_servers(
            only_installed=False)

    def _collect_language_servers(
            self, only_installed: bool) -> KeyedLanguageServerSpecs:
        language_servers: KeyedLanguageServerSpecs = {}

        language_servers_from_config = dict(self._language_servers_from_config)
        language_servers_from_config.update(self.conf_d_language_servers)

        if self.autodetect:
            language_servers.update(
                self._autodetect_language_servers(
                    only_installed=only_installed))

        # restore config
        language_servers.update(language_servers_from_config)

        # coalesce the servers, allowing a user to opt-out by specifying `[]`
        return {
            key: spec
            for key, spec in language_servers.items() if spec.get("argv")
        }

    def init_sessions(self):
        """create, but do not initialize all sessions"""
        sessions = {}
        for language_server, spec in self.language_servers.items():
            sessions[language_server] = LanguageServerSession(
                language_server=language_server, spec=spec, parent=self)
        self.sessions = sessions

    def init_listeners(self):
        """register traitlets-configured listeners"""

        scopes = {
            MessageScope.ALL: [self.all_listeners, EP_LISTENER_ALL_V1],
            MessageScope.CLIENT:
            [self.client_listeners, EP_LISTENER_CLIENT_V1],
            MessageScope.SERVER:
            [self.server_listeners, EP_LISTENER_SERVER_V1],
        }
        for scope, trt_ep in scopes.items():
            listeners, entry_point = trt_ep

            for ep_name, ept in entrypoints.get_group_named(
                    entry_point).items():  # pragma: no cover
                try:
                    listeners.append(ept.load())
                except Exception as err:
                    self.log.warning("Failed to load entry point %s: %s",
                                     ep_name, err)

            for listener in listeners:
                self.__class__.register_message_listener(
                    scope=scope.value)(listener)

    def subscribe(self, handler):
        """subscribe a handler to session, or sta"""
        session = self.sessions.get(handler.language_server)

        if session is None:
            self.log.error(
                "[{}] no session: handler subscription failed".format(
                    handler.language_server))
            return

        session.handlers = set([handler]) | session.handlers

    async def on_client_message(self, message, handler):
        await self.wait_for_listeners(MessageScope.CLIENT, message,
                                      handler.language_server)
        session = self.sessions.get(handler.language_server)

        if session is None:
            self.log.error("[{}] no session: client message dropped".format(
                handler.language_server))
            return

        session.write(message)

    async def on_server_message(self, message, session):
        language_servers = [
            ls_key for ls_key, sess in self.sessions.items() if sess == session
        ]

        for language_servers in language_servers:
            await self.wait_for_listeners(MessageScope.SERVER, message,
                                          language_servers)

        for handler in session.handlers:
            handler.write_message(message)

    def unsubscribe(self, handler):
        session = self.sessions.get(handler.language_server)

        if session is None:
            self.log.error(
                "[{}] no session: handler unsubscription failed".format(
                    handler.language_server))
            return

        session.handlers = [h for h in session.handlers if h != handler]

    def _autodetect_language_servers(self, only_installed: bool):
        entry_points = {}

        try:
            entry_points = entrypoints.get_group_named(EP_SPEC_V1)
        except Exception:  # pragma: no cover
            self.log.exception("Failed to load entry_points")

        skipped_servers = []

        for ep_name, ep in entry_points.items():
            try:
                spec_finder = ep.load()  # type: SpecMaker
            except Exception as err:  # pragma: no cover
                self.log.warning(
                    _("Failed to load language server spec finder `{}`: \n{}").
                    format(ep_name, err))
                continue

            try:
                if only_installed:
                    if hasattr(spec_finder, "is_installed"):
                        spec_finder_from_base = cast(SpecBase, spec_finder)
                        if not spec_finder_from_base.is_installed(self):
                            skipped_servers.append(ep.name)
                            continue
                specs = spec_finder(self) or {}
            except Exception as err:  # pragma: no cover
                self.log.warning(
                    _("Failed to fetch commands from language server spec finder"
                      " `{}`:\n{}").format(ep.name, err))
                traceback.print_exc()

                continue

            errors = list(LANGUAGE_SERVER_SPEC_MAP.iter_errors(specs))

            if errors:  # pragma: no cover
                self.log.warning(
                    _("Failed to validate commands from language server spec finder"
                      " `{}`:\n{}").format(ep.name, errors))
                continue

            for key, spec in specs.items():
                yield key, spec

        if skipped_servers:
            self.log.info(
                _("Skipped non-installed server(s): {}").format(
                    ", ".join(skipped_servers)))
Example #14
0
class ExtensionApp(JupyterApp):
    """Base class for configurable Jupyter Server Extension Applications.

    ExtensionApp subclasses can be initialized two ways:
    1. Extension is listed as a jpserver_extension, and ServerApp calls 
        its load_jupyter_server_extension classmethod. This is the 
        classic way of loading a server extension.
    2. Extension is launched directly by calling its `launch_instance`
        class method. This method can be set as a entry_point in 
        the extensions setup.py
    """
    # Name of the extension
    extension_name = Unicode("", help="Name of extension.")

    @default("extension_name")
    def _default_extension_name(self):
        raise Exception("The extension must be given a `name`.")

    @validate("extension_name")
    def _default_extension_name(self, obj, value):
        if isinstance(name, str):
            # Validate that extension_name doesn't contain any invalid characters.
            for char in [' ', '.', '+', '/']:
                self.error(obj, value)
            return value
        self.error(obj, value)

    # Extension can configure the ServerApp from the command-line
    classes = [
        ServerApp,
    ]

    aliases = aliases
    flags = flags

    @property
    def static_url_prefix(self):
        return "/static/{extension_name}/".format(
            extension_name=self.extension_name)

    static_paths = List(Unicode(),
                        help="""paths to search for serving static files.
        
        This allows adding javascript/css to be available from the notebook server machine,
        or overriding individual files in the IPython
        """).tag(config=True)

    template_paths = List(
        Unicode(),
        help=_("""Paths to search for serving jinja templates.

        Can be used to override templates from notebook.templates.""")).tag(
            config=True)

    settings = Dict(
        help=_("""Settings that will passed to the server.""")).tag(
            config=True)

    handlers = List(help=_("""Handlers appended to the server.""")).tag(
        config=True)

    default_url = Unicode('/',
                          config=True,
                          help=_("The default URL to redirect to from `/`"))

    def initialize_settings(self):
        """Override this method to add handling of settings."""
        pass

    def initialize_handlers(self):
        """Override this method to append handlers to a Jupyter Server."""
        pass

    def initialize_templates(self):
        """Override this method to add handling of template files."""
        pass

    def _prepare_config(self):
        """Builds a Config object from the extension's traits and passes
        the object to the webapp's settings as `<extension_name>_config`.  
        """
        traits = self.class_own_traits().keys()
        self.config = Config({t: getattr(self, t) for t in traits})
        self.settings['{}_config'.format(self.extension_name)] = self.config

    def _prepare_settings(self):
        # Make webapp settings accessible to initialize_settings method
        webapp = self.serverapp.web_app
        self.settings.update(**webapp.settings)

        # Add static and template paths to settings.
        self.settings.update({
            "{}_static_paths".format(self.extension_name):
            self.static_paths,
        })

        # Get setting defined by subclass using initialize_settings method.
        self.initialize_settings()

        # Update server settings with extension settings.
        webapp.settings.update(**self.settings)

    def _prepare_handlers(self):
        webapp = self.serverapp.web_app

        # Get handlers defined by extension subclass.
        self.initialize_handlers()

        # prepend base_url onto the patterns that we match
        new_handlers = []
        for handler_items in self.handlers:
            # Build url pattern including base_url
            pattern = url_path_join(webapp.settings['base_url'],
                                    handler_items[0])
            handler = handler_items[1]

            # Get handler kwargs, if given
            kwargs = {}
            try:
                kwargs.update(handler_items[2])
            except IndexError:
                pass
            kwargs['extension_name'] = self.extension_name

            new_handler = (pattern, handler, kwargs)
            new_handlers.append(new_handler)

        # Add static endpoint for this extension, if static paths are given.
        if len(self.static_paths) > 0:
            # Append the extension's static directory to server handlers.
            static_url = url_path_join("/static", self.extension_name, "(.*)")

            # Construct handler.
            handler = (static_url, webapp.settings['static_handler_class'], {
                'path': self.static_paths
            })
            new_handlers.append(handler)

        webapp.add_handlers('.*$', new_handlers)

    def _prepare_templates(self):
        # Add templates to web app settings if extension has templates.
        if len(self.template_paths) > 0:
            self.settings.update({
                "{}_template_paths".format(self.extension_name):
                self.template_paths
            })
        self.initialize_templates()

    @staticmethod
    def initialize_server():
        """Get an instance of the Jupyter Server."""
        # Get a jupyter server instance
        serverapp = ServerApp()
        # Initialize ServerApp config.
        # Parses the command line looking for
        # ServerApp configuration.
        serverapp.initialize()
        return serverapp

    def initialize(self, serverapp, argv=None):
        """Initialize the extension app."""
        super(ExtensionApp, self).initialize(argv=argv)
        self.serverapp = serverapp

    def start(self, **kwargs):
        """Start the extension app.
        
        Also starts the server. This allows extensions to add settings to the 
        server before it starts.
        """
        # Start the server.
        self.serverapp.start()

    @classmethod
    def launch_instance(cls, argv=None, **kwargs):
        """Launch the ServerApp and Server Extension Application. 
        
        Properly orders the steps to initialize and start the server and extension.
        """
        # Check for help, version, and generate-config arguments
        # before initializing server to make sure these
        # arguments trigger actions from the extension not the server.
        _preparse_command_line(cls)

        # Initialize the server
        serverapp = cls.initialize_server()

        # Load the extension
        args = sys.argv[1:]  # slice out extension config.
        extension = cls.load_jupyter_server_extension(serverapp,
                                                      argv=args,
                                                      **kwargs)

        # Start the browser at this extensions default_url, unless user
        # configures ServerApp.default_url on command line.
        try:
            server_config = extension.config['ServerApp']
            if 'default_url' not in server_config:
                serverapp.default_url = extension.default_url
        except KeyError:
            pass

        # Start the application.
        extension.start()

    @classmethod
    def load_jupyter_server_extension(cls, serverapp, argv=None, **kwargs):
        """Enables loading this extension application via the documented
        `load_jupyter_server_extension` mechanism.

        This method:
        - Initializes the ExtensionApp 
        - Loads the extension's config from file
        - Loads the extension's config from argv
        - Initializes templates environment
        - Passes settings to webapp
        - Appends handlers to webapp.
        """
        # Get webapp from the server.
        webapp = serverapp.web_app

        # Create an instance and initialize extension.
        extension = cls()
        extension.initialize(serverapp, argv=argv)

        # Initialize extension template, settings, and handlers.
        extension._prepare_config()
        extension._prepare_templates()
        extension._prepare_settings()
        extension._prepare_handlers()
        return extension
Example #15
0
class NotebookAppTraits(HasTraits):

    ignore_minified_js = Bool(
        False,
        config=True,
        help=
        _('Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation'
          ),
    )

    jinja_environment_options = Dict(
        config=True,
        help=_(
            "Supply extra arguments that will be passed to Jinja environment.")
    )

    jinja_template_vars = Dict(
        config=True,
        help=_("Extra variables to supply to jinja templates when rendering."),
    )

    enable_mathjax = Bool(
        True,
        config=True,
        help="""Whether to enable MathJax for typesetting math/TeX

        MathJax is the javascript library Jupyter uses to render math/LaTeX. It is
        very large, so you may want to disable it if you have a slow internet
        connection, or for offline use of the notebook.

        When disabled, equations etc. will appear as their untransformed TeX source.
        """)

    @observe('enable_mathjax')
    def _update_enable_mathjax(self, change):
        """set mathjax url to empty if mathjax is disabled"""
        if not change['new']:
            self.mathjax_url = u''

    extra_static_paths = List(
        Unicode(),
        config=True,
        help="""Extra paths to search for serving static files.

        This allows adding javascript/css to be available from the notebook server machine,
        or overriding individual files in the IPython""")

    @property
    def static_file_path(self):
        """return extra paths + the default location"""
        return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]

    static_custom_path = List(Unicode(),
                              help=_("""Path to search for custom.js, css"""))

    @default('static_custom_path')
    def _default_static_custom_path(self):
        return [
            os.path.join(d, 'custom')
            for d in (self.config_dir, DEFAULT_STATIC_FILES_PATH)
        ]

    extra_template_paths = List(
        Unicode(),
        config=True,
        help=_("""Extra paths to search for serving jinja templates.

        Can be used to override templates from notebook.templates."""))

    @property
    def template_file_path(self):
        """return extra paths + the default locations"""
        return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST

    extra_nbextensions_path = List(
        Unicode(),
        config=True,
        help=_("""extra paths to look for Javascript notebook extensions"""))

    @property
    def nbextensions_path(self):
        """The path to look for Javascript notebook extensions"""
        path = self.extra_nbextensions_path + jupyter_path('nbextensions')
        # FIXME: remove IPython nbextensions path after a migration period
        try:
            from IPython.paths import get_ipython_dir
        except ImportError:
            pass
        else:
            path.append(os.path.join(get_ipython_dir(), 'nbextensions'))
        return path

    mathjax_url = Unicode("",
                          config=True,
                          help="""A custom url for MathJax.js.
        Should be in the form of a case-sensitive url to MathJax,
        for example:  /static/components/MathJax/MathJax.js
        """)

    @property
    def static_url_prefix(self):
        """Get the static url prefix for serving static files."""
        return super(NotebookAppTraits, self).static_url_prefix

    @default('mathjax_url')
    def _default_mathjax_url(self):
        if not self.enable_mathjax:
            return u''
        static_url_prefix = self.static_url_prefix
        return url_path_join(static_url_prefix, 'components', 'MathJax',
                             'MathJax.js')

    @observe('mathjax_url')
    def _update_mathjax_url(self, change):
        new = change['new']
        if new and not self.enable_mathjax:
            # enable_mathjax=False overrides mathjax_url
            self.mathjax_url = u''
        else:
            self.log.info(_("Using MathJax: %s"), new)

    mathjax_config = Unicode(
        "TeX-AMS-MML_HTMLorMML-full,Safe",
        config=True,
        help=_("""The MathJax.js configuration file that is to be used."""))

    @observe('mathjax_config')
    def _update_mathjax_config(self, change):
        self.log.info(_("Using MathJax configuration file: %s"), change['new'])

    quit_button = Bool(
        True,
        config=True,
        help="""If True, display a button in the dashboard to quit
        (shutdown the notebook server).""")
Example #16
0
class NotebookApp(
        shim.NBClassicConfigShimMixin,
        ExtensionAppJinjaMixin,
        ExtensionApp,
        traits.NotebookAppTraits,
):

    name = 'notebook'
    version = __version__
    description = _("""The Jupyter HTML Notebook.

    This launches a Tornado based HTML Notebook Server that serves up an HTML5/Javascript Notebook client."""
                    )

    aliases = aliases
    flags = flags
    extension_url = "/tree"
    subcommands = {}

    default_url = Unicode("/tree").tag(config=True)

    # Override the default open_Browser trait in ExtensionApp,
    # setting it to True.
    open_browser = Bool(True,
                        help="""Whether to open in a browser after starting.
        The specific browser used is platform dependent and
        determined by the python standard library `webbrowser`
        module, unless it is overridden using the --browser
        (ServerApp.browser) configuration option.
        """).tag(config=True)

    static_custom_path = List(Unicode(),
                              help=_("""Path to search for custom.js, css"""))

    @default('static_custom_path')
    def _default_static_custom_path(self):
        return [
            os.path.join(d, 'custom')
            for d in (self.config_dir, DEFAULT_STATIC_FILES_PATH)
        ]

    extra_nbextensions_path = List(
        Unicode(),
        config=True,
        help=_("""extra paths to look for Javascript notebook extensions"""))

    @property
    def nbextensions_path(self):
        """The path to look for Javascript notebook extensions"""
        path = self.extra_nbextensions_path + jupyter_path('nbextensions')
        # FIXME: remove IPython nbextensions path after a migration period
        try:
            from IPython.paths import get_ipython_dir
        except ImportError:
            pass
        else:
            path.append(os.path.join(get_ipython_dir(), 'nbextensions'))
        return path

    @property
    def static_paths(self):
        """Rename trait in jupyter_server."""
        return self.static_file_path

    @property
    def template_paths(self):
        """Rename trait for Jupyter Server."""
        return self.template_file_path

    def _prepare_templates(self):
        super(NotebookApp, self)._prepare_templates()

        # Get translations from notebook package.
        base_dir = os.path.dirname(notebook.__file__)

        nbui = gettext.translation('nbui',
                                   localedir=os.path.join(
                                       base_dir, 'notebook/i18n'),
                                   fallback=True)
        self.jinja2_env.install_gettext_translations(nbui, newstyle=False)

    def initialize_settings(self):
        """Add settings to the tornado app."""
        if self.ignore_minified_js:
            self.log.warning(
                _("""The `ignore_minified_js` flag is deprecated and no longer works."""
                  ))
            self.log.warning(
                _("""Alternatively use `%s` when working on the notebook's Javascript and LESS"""
                  ) % 'npm run build:watch')
            warnings.warn(
                _("The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"
                  ), DeprecationWarning)

        settings = dict(
            static_custom_path=self.static_custom_path,
            static_handler_args={
                # don't cache custom.js
                'no_cache_paths': [
                    url_path_join(self.serverapp.base_url, 'static', self.name,
                                  'custom')
                ],
            },
            ignore_minified_js=self.ignore_minified_js,
            mathjax_url=self.mathjax_url,
            mathjax_config=self.mathjax_config,
            nbextensions_path=self.nbextensions_path,
        )
        self.settings.update(**settings)

    def initialize_handlers(self):
        """Load the (URL pattern, handler) tuples for each component."""
        # Order matters. The first handler to match the URL will handle the request.
        handlers = []

        # Add a redirect from /notebooks to /edit
        # for opening non-ipynb files in edit mode.
        handlers.append((rf"/{self.file_url_prefix}/((?!.*\.ipynb($|\?)).*)",
                         RedirectHandler, {
                             "url": "/edit/{0}"
                         }))

        # load extra services specified by users before default handlers
        for service in self.settings['extra_services']:
            handlers.extend(load_handlers(service))

        handlers.extend(load_handlers('nbclassic.tree.handlers'))
        handlers.extend(load_handlers('nbclassic.notebook.handlers'))
        handlers.extend(load_handlers('nbclassic.edit.handlers'))

        # Add terminal handlers
        handlers.append((r"/terminals/(\w+)", TerminalHandler))

        handlers.append(
            (
                r"/nbextensions/(.*)",
                FileFindHandler,
                {
                    'path': self.settings['nbextensions_path'],
                    'no_cache_paths':
                    ['/'],  # don't cache anything in nbextensions
                }), )
        handlers.append(
            (
                r"/custom/(.*)",
                FileFindHandler,
                {
                    'path': self.settings['static_custom_path'],
                    'no_cache_paths':
                    ['/'],  # don't cache anything in nbextensions
                }), )
        # Add new handlers to Jupyter server handlers.
        self.handlers.extend(handlers)
Example #17
0
jupyter nbclassic                       # start the notebook
jupyter nbclassic --certfile=mycert.pem # use SSL/TLS certificate
jupyter nbclassic password              # enter a password to protect the server
"""

#-----------------------------------------------------------------------------
# Aliases and Flags
#-----------------------------------------------------------------------------

flags = {}
aliases = {}
flags['no-browser'] = ({
    'ServerApp': {
        'open_browser': False
    }
}, _("Don't open the notebook in a browser after startup."))
flags['no-mathjax'] = ({
    'NotebookApp': {
        'enable_mathjax': False
    }
}, """Disable MathJax

    MathJax is the javascript library Jupyter uses to render math/LaTeX. It is
    very large, so you may want to disable it if you have a slow internet
    connection, or for offline use of the notebook.

    When disabled, equations etc. will appear as their untransformed TeX source.
    """)

flags['allow-root'] = ({
    'ServerApp': {
Example #18
0
class ContentsManager(LoggingConfigurable):
    """Base class for serving files and directories.

    This serves any text or binary file,
    as well as directories,
    with special handling for JSON notebook documents.

    Most APIs take a path argument,
    which is always an API-style unicode path,
    and always refers to a directory.

    - unicode, not url-escaped
    - '/'-separated
    - leading and trailing '/' will be stripped
    - if unspecified, path defaults to '',
      indicating the root path.

    """

    root_dir = Unicode("/", config=True)

    allow_hidden = Bool(False,
                        config=True,
                        help="Allow access to hidden files")

    notary = Instance(sign.NotebookNotary)

    def _notary_default(self):
        return sign.NotebookNotary(parent=self)

    hide_globs = List(
        Unicode(),
        [
            u"__pycache__",
            "*.pyc",
            "*.pyo",
            ".DS_Store",
            "*.so",
            "*.dylib",
            "*~",
        ],
        config=True,
        help="""
        Glob patterns to hide in file and directory listings.
    """,
    )

    untitled_notebook = Unicode(
        _("Untitled"),
        config=True,
        help="The base name used when creating untitled notebooks.",
    )

    untitled_file = Unicode(
        "untitled",
        config=True,
        help="The base name used when creating untitled files.")

    untitled_directory = Unicode(
        "Untitled Folder",
        config=True,
        help="The base name used when creating untitled directories.",
    )

    pre_save_hook = Any(
        None,
        config=True,
        allow_none=True,
        help="""Python callable or importstring thereof

        To be called on a contents model prior to save.

        This can be used to process the structure,
        such as removing notebook outputs or other side effects that
        should not be saved.

        It will be called as (all arguments passed by keyword)::

            hook(path=path, model=model, contents_manager=self)

        - model: the model to be saved. Includes file contents.
          Modifying this dict will affect the file that is stored.
        - path: the API path of the save destination
        - contents_manager: this ContentsManager instance
        """,
    )

    @validate("pre_save_hook")
    def _validate_pre_save_hook(self, proposal):
        value = proposal["value"]
        if isinstance(value, string_types):
            value = import_item(self.pre_save_hook)
        if not callable(value):
            raise TraitError("pre_save_hook must be callable")
        return value

    def run_pre_save_hook(self, model, path, **kwargs):
        """Run the pre-save hook if defined, and log errors"""
        if self.pre_save_hook:
            try:
                self.log.debug("Running pre-save hook on %s", path)
                self.pre_save_hook(model=model,
                                   path=path,
                                   contents_manager=self,
                                   **kwargs)
            except Exception:
                self.log.error("Pre-save hook failed on %s",
                               path,
                               exc_info=True)

    checkpoints_class = Type(Checkpoints, config=True)
    checkpoints = Instance(Checkpoints, config=True)
    checkpoints_kwargs = Dict(config=True)

    @default("checkpoints")
    def _default_checkpoints(self):
        return self.checkpoints_class(**self.checkpoints_kwargs)

    @default("checkpoints_kwargs")
    def _default_checkpoints_kwargs(self):
        return dict(
            parent=self,
            log=self.log,
        )

    files_handler_class = Type(
        FilesHandler,
        klass=RequestHandler,
        allow_none=True,
        config=True,
        help="""handler class to use when serving raw file requests.

        Default is a fallback that talks to the ContentsManager API,
        which may be inefficient, especially for large files.

        Local files-based ContentsManagers can use a StaticFileHandler subclass,
        which will be much more efficient.

        Access to these files should be Authenticated.
        """,
    )

    files_handler_params = Dict(
        config=True,
        help="""Extra parameters to pass to files_handler_class.

        For example, StaticFileHandlers generally expect a `path` argument
        specifying the root directory from which to serve files.
        """,
    )

    def get_extra_handlers(self):
        """Return additional handlers

        Default: self.files_handler_class on /files/.*
        """
        handlers = []
        if self.files_handler_class:
            handlers.append((r"/files/(.*)", self.files_handler_class,
                             self.files_handler_params))
        return handlers

    # ContentsManager API part 1: methods that must be
    # implemented in subclasses.

    def dir_exists(self, path):
        """Does a directory exist at the given path?

        Like os.path.isdir

        Override this method in subclasses.

        Parameters
        ----------
        path : string
            The path to check

        Returns
        -------
        exists : bool
            Whether the path does indeed exist.
        """
        raise NotImplementedError

    def is_hidden(self, path):
        """Is path a hidden directory or file?

        Parameters
        ----------
        path : string
            The path to check. This is an API path (`/` separated,
            relative to root dir).

        Returns
        -------
        hidden : bool
            Whether the path is hidden.

        """
        raise NotImplementedError

    def file_exists(self, path=""):
        """Does a file exist at the given path?

        Like os.path.isfile

        Override this method in subclasses.

        Parameters
        ----------
        path : string
            The API path of a file to check for.

        Returns
        -------
        exists : bool
            Whether the file exists.
        """
        raise NotImplementedError("must be implemented in a subclass")

    def exists(self, path):
        """Does a file or directory exist at the given path?

        Like os.path.exists

        Parameters
        ----------
        path : string
            The API path of a file or directory to check for.

        Returns
        -------
        exists : bool
            Whether the target exists.
        """
        return self.file_exists(path) or self.dir_exists(path)

    def get(self, path, content=True, type=None, format=None):
        """Get a file or directory model."""
        raise NotImplementedError("must be implemented in a subclass")

    def save(self, model, path):
        """
        Save a file or directory model to path.

        Should return the saved model with no content.  Save implementations
        should call self.run_pre_save_hook(model=model, path=path) prior to
        writing any data.
        """
        raise NotImplementedError("must be implemented in a subclass")

    def delete_file(self, path):
        """Delete the file or directory at path."""
        raise NotImplementedError("must be implemented in a subclass")

    def rename_file(self, old_path, new_path):
        """Rename a file or directory."""
        raise NotImplementedError("must be implemented in a subclass")

    # ContentsManager API part 2: methods that have useable default
    # implementations, but can be overridden in subclasses.

    def delete(self, path):
        """Delete a file/directory and any associated checkpoints."""
        path = path.strip("/")
        if not path:
            raise HTTPError(400, "Can't delete root")
        self.delete_file(path)
        self.checkpoints.delete_all_checkpoints(path)

    def rename(self, old_path, new_path):
        """Rename a file and any checkpoints associated with that file."""
        self.rename_file(old_path, new_path)
        self.checkpoints.rename_all_checkpoints(old_path, new_path)

    def update(self, model, path):
        """Update the file's path

        For use in PATCH requests, to enable renaming a file without
        re-uploading its contents. Only used for renaming at the moment.
        """
        path = path.strip("/")
        new_path = model.get("path", path).strip("/")
        if path != new_path:
            self.rename(path, new_path)
        model = self.get(new_path, content=False)
        return model

    def info_string(self):
        return "Serving contents"

    def get_kernel_path(self, path, model=None):
        """Return the API path for the kernel

        KernelManagers can turn this value into a filesystem path,
        or ignore it altogether.

        The default value here will start kernels in the directory of the
        notebook server. FileContentsManager overrides this to use the
        directory containing the notebook.
        """
        return ""

    def increment_filename(self, filename, path="", insert=""):
        """Increment a filename until it is unique.

        Parameters
        ----------
        filename : unicode
            The name of a file, including extension
        path : unicode
            The API path of the target's directory
        insert: unicode
            The characters to insert after the base filename

        Returns
        -------
        name : unicode
            A filename that is unique, based on the input filename.
        """
        # Extract the full suffix from the filename (e.g. .tar.gz)
        path = path.strip("/")
        basename, dot, ext = filename.rpartition(".")
        if ext != "ipynb":
            basename, dot, ext = filename.partition(".")

        suffix = dot + ext

        for i in itertools.count():
            if i:
                insert_i = "{}{}".format(insert, i)
            else:
                insert_i = ""
            name = u"{basename}{insert}{suffix}".format(basename=basename,
                                                        insert=insert_i,
                                                        suffix=suffix)
            if not self.exists(u"{}/{}".format(path, name)):
                break
        return name

    def validate_notebook_model(self, model):
        """Add failed-validation message to model"""
        try:
            validate_nb(model["content"])
        except ValidationError as e:
            model["message"] = u"Notebook validation failed: {}:\n{}".format(
                e.message,
                json.dumps(e.instance,
                           indent=1,
                           default=lambda obj: "<UNKNOWN>"),
            )
        return model

    def new_untitled(self, path="", type="", ext=""):
        """Create a new untitled file or directory in path

        path must be a directory

        File extension can be specified.

        Use `new` to create files with a fully specified path (including filename).
        """
        path = path.strip("/")
        if not self.dir_exists(path):
            raise HTTPError(404, "No such directory: %s" % path)

        model = {}
        if type:
            model["type"] = type

        if ext == ".ipynb":
            model.setdefault("type", "notebook")
        else:
            model.setdefault("type", "file")

        insert = ""
        if model["type"] == "directory":
            untitled = self.untitled_directory
            insert = " "
        elif model["type"] == "notebook":
            untitled = self.untitled_notebook
            ext = ".ipynb"
        elif model["type"] == "file":
            untitled = self.untitled_file
        else:
            raise HTTPError(400, "Unexpected model type: %r" % model["type"])

        name = self.increment_filename(untitled + ext, path, insert=insert)
        path = u"{0}/{1}".format(path, name)
        return self.new(model, path)

    def new(self, model=None, path=""):
        """Create a new file or directory and return its model with no content.

        To create a new untitled entity in a directory, use `new_untitled`.
        """
        path = path.strip("/")
        if model is None:
            model = {}

        if path.endswith(".ipynb"):
            model.setdefault("type", "notebook")
        else:
            model.setdefault("type", "file")

        # no content, not a directory, so fill out new-file model
        if "content" not in model and model["type"] != "directory":
            if model["type"] == "notebook":
                model["content"] = new_notebook()
                model["format"] = "json"
            else:
                model["content"] = ""
                model["type"] = "file"
                model["format"] = "text"

        model = self.save(model, path)
        return model

    def copy(self, from_path, to_path=None):
        """Copy an existing file and return its new model.

        If to_path not specified, it will be the parent directory of from_path.
        If to_path is a directory, filename will increment `from_path-Copy#.ext`.
        Considering multi-part extensions, the Copy# part will be placed before the first dot for all the extensions except `ipynb`.
        For easier manual searching in case of notebooks, the Copy# part will be placed before the last dot. 

        from_path must be a full path to a file.
        """
        path = from_path.strip("/")
        if to_path is not None:
            to_path = to_path.strip("/")

        if "/" in path:
            from_dir, from_name = path.rsplit("/", 1)
        else:
            from_dir = ""
            from_name = path

        model = self.get(path)
        model.pop("path", None)
        model.pop("name", None)
        if model["type"] == "directory":
            raise HTTPError(400, "Can't copy directories")

        if to_path is None:
            to_path = from_dir
        if self.dir_exists(to_path):
            name = copy_pat.sub(u".", from_name)
            to_name = self.increment_filename(name, to_path, insert="-Copy")
            to_path = u"{0}/{1}".format(to_path, to_name)

        model = self.save(model, to_path)
        return model

    def log_info(self):
        self.log.info(self.info_string())

    def trust_notebook(self, path):
        """Explicitly trust a notebook

        Parameters
        ----------
        path : string
            The path of a notebook
        """
        model = self.get(path)
        nb = model["content"]
        self.log.warning("Trusting notebook %s", path)
        self.notary.mark_cells(nb, True)
        self.check_and_sign(nb, path)

    def check_and_sign(self, nb, path=""):
        """Check for trusted cells, and sign the notebook.

        Called as a part of saving notebooks.

        Parameters
        ----------
        nb : dict
            The notebook dict
        path : string
            The notebook's path (for logging)
        """
        if self.notary.check_cells(nb):
            self.notary.sign(nb)
        else:
            self.log.warning("Notebook %s is not trusted", path)

    def mark_trusted_cells(self, nb, path=""):
        """Mark cells as trusted if the notebook signature matches.

        Called as a part of loading notebooks.

        Parameters
        ----------
        nb : dict
            The notebook object (in current nbformat)
        path : string
            The notebook's path (for logging)
        """
        trusted = self.notary.check_signature(nb)
        if not trusted:
            self.log.warning("Notebook %s is not trusted", path)
        self.notary.mark_cells(nb, trusted)

    def should_list(self, name):
        """Should this file/directory name be displayed in a listing?"""
        return not any(fnmatch(name, glob) for glob in self.hide_globs)

    # Part 3: Checkpoints API
    def create_checkpoint(self, path):
        """Create a checkpoint."""
        return self.checkpoints.create_checkpoint(self, path)

    def restore_checkpoint(self, checkpoint_id, path):
        """
        Restore a checkpoint.
        """
        self.checkpoints.restore_checkpoint(self, checkpoint_id, path)

    def list_checkpoints(self, path):
        return self.checkpoints.list_checkpoints(path)

    def delete_checkpoint(self, checkpoint_id, path):
        return self.checkpoints.delete_checkpoint(checkpoint_id, path)
        self.initialize_templates()

        # Add the jinja2 environment for this extension to the tornado settings.
        self.settings.update(
            {"{}_jinja2_env".format(self.extension_name): self.jinja2_env})


#-----------------------------------------------------------------------------
# Aliases and Flags
#-----------------------------------------------------------------------------

flags['no-browser'] = ({
    'ExtensionApp': {
        'open_browser': True
    }
}, _("Prevent the opening of the default url in the browser."))

#-----------------------------------------------------------------------------
# ExtensionApp
#-----------------------------------------------------------------------------


class ExtensionApp(JupyterApp):
    """Base class for configurable Jupyter Server Extension Applications.

    ExtensionApp subclasses can be initialized two ways:
    1. Extension is listed as a jpserver_extension, and ServerApp calls 
        its load_jupyter_server_extension classmethod. This is the 
        classic way of loading a server extension.
    2. Extension is launched directly by calling its `launch_instance`
        class method. This method can be set as a entry_point in 
class ExtensionApp(JupyterApp):
    """Base class for configurable Jupyter Server Extension Applications.

    ExtensionApp subclasses can be initialized two ways:
    1. Extension is listed as a jpserver_extension, and ServerApp calls 
        its load_jupyter_server_extension classmethod. This is the 
        classic way of loading a server extension.
    2. Extension is launched directly by calling its `launch_instance`
        class method. This method can be set as a entry_point in 
        the extensions setup.py
    """
    # Subclasses should override this trait. Tells the server if
    # this extension allows other other extensions to be loaded
    # side-by-side when launched directly.
    load_other_extensions = True

    # Name of the extension
    extension_name = Unicode(help="Name of extension.")

    @default('extension_name')
    def _extension_name_default(self):
        try:
            return self.name
        except AttributeError:
            raise ValueError("The extension must be given a `name`.")

    INVALID_EXTENSION_NAME_CHARS = [' ', '.', '+', '/']

    @validate('extension_name')
    def _validate_extension_name(self, proposal):
        value = proposal['value']
        if isinstance(value, str):
            # Validate that extension_name doesn't contain any invalid characters.
            for c in ExtensionApp.INVALID_EXTENSION_NAME_CHARS:
                if c in value:
                    raise ValueError(
                        "Extension name '{name}' cannot contain any of the following characters: "
                        "{invalid_chars}.".format(
                            name=value,
                            invalid_chars=ExtensionApp.
                            INVALID_EXTENSION_NAME_CHARS))
            return value
        raise ValueError(
            "Extension name must be a string, found {type}.".format(
                type=type(value)))

    # Extension can configure the ServerApp from the command-line
    classes = [
        ServerApp,
    ]

    aliases = aliases
    flags = flags

    subcommands = {}

    @property
    def static_url_prefix(self):
        return "/static/{extension_name}/".format(
            extension_name=self.extension_name)

    static_paths = List(Unicode(),
                        help="""paths to search for serving static files.
        
        This allows adding javascript/css to be available from the notebook server machine,
        or overriding individual files in the IPython
        """).tag(config=True)

    template_paths = List(
        Unicode(),
        help=_("""Paths to search for serving jinja templates.

        Can be used to override templates from notebook.templates.""")).tag(
            config=True)

    settings = Dict(
        help=_("""Settings that will passed to the server.""")).tag(
            config=True)

    handlers = List(help=_("""Handlers appended to the server.""")).tag(
        config=True)

    def _config_dir_default(self):
        """Point the config directory at the server's config_dir by default."""
        try:
            return self.serverapp.config_dir
        except AttributeError:
            raise AttributeError("The ExtensionApp has not ServerApp "
                                 "initialized. Try `.initialize_server()`.")

    def _config_file_name_default(self):
        """The default config file name."""
        if not self.extension_name:
            return ''
        return 'jupyter_{}_config'.format(self.extension_name.replace(
            '-', '_'))

    default_url = Unicode('/',
                          config=True,
                          help=_("The default URL to redirect to from `/`"))

    open_browser = Bool(True,
                        help=_("Should the extension open a browser window?"))

    custom_display_url = Unicode(u'',
                                 config=True,
                                 help=_("""Override URL shown to users.

        Replace actual URL, including protocol, address, port and base URL,
        with the given value when displaying URL to the users. Do not change
        the actual connection URL. If authentication token is enabled, the
        token is added to the custom URL automatically.

        This option is intended to be used when the URL to display to the user
        cannot be determined reliably by the Jupyter server (proxified
        or containerized setups for example)."""))

    @default('custom_display_url')
    def _default_custom_display_url(self):
        """URL to display to the user."""
        # Get url from server.
        url = url_path_join(self.serverapp.base_url, self.default_url)
        return self.serverapp.get_url(self.serverapp.ip, url)

    def _write_browser_open_file(self, url, fh):
        """Use to hijacks the server's browser-open file and open at 
        the extension's homepage.
        """
        # Ignore server's url
        del url
        path = url_path_join(self.serverapp.base_url, self.default_url)
        url = self.serverapp.get_url(path=path, token=self.serverapp.token)
        jinja2_env = self.serverapp.web_app.settings['jinja2_env']
        template = jinja2_env.get_template('browser-open.html')
        fh.write(template.render(open_url=url))

    def initialize_settings(self):
        """Override this method to add handling of settings."""
        pass

    def initialize_handlers(self):
        """Override this method to append handlers to a Jupyter Server."""
        pass

    def initialize_templates(self):
        """Override this method to add handling of template files."""
        pass

    def _prepare_config(self):
        """Builds a Config object from the extension's traits and passes
        the object to the webapp's settings as `<extension_name>_config`.  
        """
        traits = self.class_own_traits().keys()
        self.extension_config = Config({t: getattr(self, t) for t in traits})
        self.settings['{}_config'.format(
            self.extension_name)] = self.extension_config

    def _prepare_settings(self):
        # Make webapp settings accessible to initialize_settings method
        webapp = self.serverapp.web_app
        self.settings.update(**webapp.settings)

        # Add static and template paths to settings.
        self.settings.update({
            "{}_static_paths".format(self.extension_name):
            self.static_paths,
        })

        # Get setting defined by subclass using initialize_settings method.
        self.initialize_settings()

        # Update server settings with extension settings.
        webapp.settings.update(**self.settings)

    def _prepare_handlers(self):
        webapp = self.serverapp.web_app

        # Get handlers defined by extension subclass.
        self.initialize_handlers()

        # prepend base_url onto the patterns that we match
        new_handlers = []
        for handler_items in self.handlers:
            # Build url pattern including base_url
            pattern = url_path_join(webapp.settings['base_url'],
                                    handler_items[0])
            handler = handler_items[1]

            # Get handler kwargs, if given
            kwargs = {}
            if issubclass(handler, ExtensionHandlerMixin):
                kwargs['extension_name'] = self.extension_name
            try:
                kwargs.update(handler_items[2])
            except IndexError:
                pass

            new_handler = (pattern, handler, kwargs)
            new_handlers.append(new_handler)

        # Add static endpoint for this extension, if static paths are given.
        if len(self.static_paths) > 0:
            # Append the extension's static directory to server handlers.
            static_url = url_path_join("/static", self.extension_name, "(.*)")

            # Construct handler.
            handler = (static_url, webapp.settings['static_handler_class'], {
                'path': self.static_paths
            })
            new_handlers.append(handler)

        webapp.add_handlers('.*$', new_handlers)

    def _prepare_templates(self):
        # Add templates to web app settings if extension has templates.
        if len(self.template_paths) > 0:
            self.settings.update({
                "{}_template_paths".format(self.extension_name):
                self.template_paths
            })
        self.initialize_templates()

    @staticmethod
    def initialize_server(argv=[], load_other_extensions=True, **kwargs):
        """Get an instance of the Jupyter Server."""
        # Get a jupyter server instance
        serverapp = ServerApp.instance(**kwargs)
        # Initialize ServerApp config.
        # Parses the command line looking for
        # ServerApp configuration.
        serverapp.initialize(argv=argv, load_extensions=load_other_extensions)
        return serverapp

    def initialize(self, serverapp, argv=[]):
        """Initialize the extension app.
        
        This method:
        - Loads the extension's config from file
        - Updates the extension's config from argv
        - Initializes templates environment
        - Passes settings to webapp
        - Appends handlers to webapp.
        """
        # Initialize ServerApp.
        self.serverapp = serverapp

        # Initialize the extension application
        super(ExtensionApp, self).initialize(argv=argv)

        # Initialize config, settings, templates, and handlers.
        self._prepare_config()
        self._prepare_templates()
        self._prepare_settings()
        self._prepare_handlers()

    def start(self):
        """Start the underlying Jupyter server.
        
        Server should be started after extension is initialized.
        """
        super(ExtensionApp, self).start()
        # Override the browser open file to
        # Override the server's display url to show extension's display URL.
        self.serverapp.custom_display_url = self.custom_display_url
        # Override the server's default option and open a broswer window.
        self.serverapp.open_browser = self.open_browser
        # Hijack the server's browser-open file to land on
        # the extensions home page.
        self.serverapp._write_browser_open_file = self._write_browser_open_file
        # Start the server.
        self.serverapp.start()

    def stop(self):
        """Stop the underlying Jupyter server.
        """
        self.serverapp.stop()
        self.serverapp.clear_instance()

    @classmethod
    def load_jupyter_server_extension(cls, serverapp, argv=[], **kwargs):
        """Initialize and configure this extension, then add the extension's
        settings and handlers to the server's web application.
        """
        # Configure and initialize extension.
        extension = cls()
        extension.initialize(serverapp, argv=argv)
        return extension

    @classmethod
    def launch_instance(cls, argv=None, **kwargs):
        """Launch the extension like an application. Initializes+configs a stock server 
        and appends the extension to the server. Then starts the server and routes to
        extension's landing page.
        """
        # Handle arguments.
        if argv is None:
            args = sys.argv[1:]  # slice out extension config.
        else:
            args = []
        # Check for subcommands
        subapp = _preparse_for_subcommand(cls, args)
        if subapp:
            subapp.start()
        else:
            # Check for help, version, and generate-config arguments
            # before initializing server to make sure these
            # arguments trigger actions from the extension not the server.
            _preparse_for_stopping_flags(cls, args)
            # Get a jupyter server instance.
            serverapp = cls.initialize_server(
                argv=args, load_other_extensions=cls.load_other_extensions)
            # Log if extension is blocking other extensions from loading.
            if not cls.load_other_extensions:
                serverapp.log.info(
                    "{ext_name} is running without loading "
                    "other extensions.".format(ext_name=cls.extension_name))

            extension = cls.load_jupyter_server_extension(serverapp,
                                                          argv=args,
                                                          **kwargs)
            # Start the ioloop.
            extension.start()
Example #21
0
class NotebookApp(ExtensionApp):

    name = 'jupyter-notebook'
    version = __version__
    description = _("""The Jupyter HTML Notebook.
    
    This launches a Tornado based HTML Notebook Server that serves up an HTML5/Javascript Notebook client."""
                    )

    extension_name = 'nbserver_shim'

    ignore_minified_js = Bool(
        False,
        config=True,
        help=
        _('Deprecated: Use minified JS file or not, mainly use during dev to avoid JS recompilation'
          ),
    )

    max_body_size = Integer(512 * 1024 * 1024,
                            config=True,
                            help="""
        Sets the maximum allowed size of the client request body, specified in 
        the Content-Length request header field. If the size in a request 
        exceeds the configured value, a malformed HTTP message is returned to
        the client.

        Note: max_body_size is applied even in streaming mode.
        """)

    max_buffer_size = Integer(512 * 1024 * 1024,
                              config=True,
                              help="""
        Gets or sets the maximum amount of memory, in bytes, that is allocated 
        for use by the buffer manager.
        """)

    jinja_environment_options = Dict(
        config=True,
        help=_(
            "Supply extra arguments that will be passed to Jinja environment.")
    )

    jinja_template_vars = Dict(
        config=True,
        help=_("Extra variables to supply to jinja templates when rendering."),
    )

    enable_mathjax = Bool(
        True,
        config=True,
        help="""Whether to enable MathJax for typesetting math/TeX

        MathJax is the javascript library Jupyter uses to render math/LaTeX. It is
        very large, so you may want to disable it if you have a slow internet
        connection, or for offline use of the notebook.

        When disabled, equations etc. will appear as their untransformed TeX source.
        """)

    @observe('enable_mathjax')
    def _update_enable_mathjax(self, change):
        """set mathjax url to empty if mathjax is disabled"""
        if not change['new']:
            self.mathjax_url = u''

    extra_static_paths = List(
        Unicode(),
        config=True,
        help="""Extra paths to search for serving static files.
        
        This allows adding javascript/css to be available from the notebook server machine,
        or overriding individual files in the IPython""")

    @property
    def static_file_path(self):
        """return extra paths + the default location"""
        return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]

    static_custom_path = List(Unicode(),
                              help=_("""Path to search for custom.js, css"""))

    @default('static_custom_path')
    def _default_static_custom_path(self):
        return [
            os.path.join(d, 'custom')
            for d in (self.config_dir, DEFAULT_STATIC_FILES_PATH)
        ]

    extra_template_paths = List(
        Unicode(),
        config=True,
        help=_("""Extra paths to search for serving jinja templates.

        Can be used to override templates from notebook.templates."""))

    @property
    def template_file_path(self):
        """return extra paths + the default locations"""
        return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST

    extra_nbextensions_path = List(
        Unicode(),
        config=True,
        help=_("""extra paths to look for Javascript notebook extensions"""))

    extra_services = List(
        Unicode(),
        config=True,
        help=
        _("""handlers that should be loaded at higher priority than the default services"""
          ))

    @property
    def nbextensions_path(self):
        """The path to look for Javascript notebook extensions"""
        path = self.extra_nbextensions_path + jupyter_path('nbextensions')
        # FIXME: remove IPython nbextensions path after a migration period
        try:
            from IPython.paths import get_ipython_dir
        except ImportError:
            pass
        else:
            path.append(os.path.join(get_ipython_dir(), 'nbextensions'))
        return path

    mathjax_url = Unicode("",
                          config=True,
                          help="""A custom url for MathJax.js.
        Should be in the form of a case-sensitive url to MathJax,
        for example:  /static/components/MathJax/MathJax.js
        """)

    @property
    def static_url_prefix(self):
        """Get the static url prefix for serving static files."""
        return super(NotebookApp, self).static_url_prefix

    @default('mathjax_url')
    def _default_mathjax_url(self):
        if not self.enable_mathjax:
            return u''
        static_url_prefix = self.static_url_prefix
        return url_path_join(static_url_prefix, 'components', 'MathJax',
                             'MathJax.js')

    @observe('mathjax_url')
    def _update_mathjax_url(self, change):
        new = change['new']
        if new and not self.enable_mathjax:
            # enable_mathjax=False overrides mathjax_url
            self.mathjax_url = u''
        else:
            self.log.info(_("Using MathJax: %s"), new)

    mathjax_config = Unicode(
        "TeX-AMS-MML_HTMLorMML-full,Safe",
        config=True,
        help=_("""The MathJax.js configuration file that is to be used."""))

    @observe('mathjax_config')
    def _update_mathjax_config(self, change):
        self.log.info(_("Using MathJax configuration file: %s"), change['new'])

    quit_button = Bool(
        True,
        config=True,
        help="""If True, display a button in the dashboard to quit
        (shutdown the notebook server).""")

    # ------------------------------------------------------------------------
    # traits and methods for Jupyter Server
    # ------------------------------------------------------------------------

    default_url = Unicode("/tree", config=True)

    @property
    def static_paths(self):
        """Rename trait in jupyter_server."""
        return self.static_file_path

    @property
    def template_paths(self):
        """Rename trait for Jupyter Server."""
        return self.template_file_path

    def initialize_templates(self):
        """Initialize the jinja templates for the notebook application."""
        _template_path = self.template_paths
        if isinstance(_template_path, py3compat.string_types):
            _template_path = (_template_path, )
        template_path = [os.path.expanduser(path) for path in _template_path]

        jenv_opt = {"autoescape": True}
        jenv_opt.update(self.jinja_environment_options if self.
                        jinja_environment_options else {})

        env = Environment(loader=FileSystemLoader(template_path),
                          extensions=['jinja2.ext.i18n'],
                          **jenv_opt)

        # If the user is running the notebook in a git directory, make the assumption
        # that this is a dev install and suggest to the developer `npm run build:watch`.
        base_dir = os.path.realpath(os.path.join(__file__, '..', '..'))
        dev_mode = os.path.exists(os.path.join(base_dir, '.git'))

        nbui = gettext.translation('nbui',
                                   localedir=os.path.join(
                                       base_dir, 'notebook/i18n'),
                                   fallback=True)
        env.install_gettext_translations(nbui, newstyle=False)

        #     if dev_mode:
        #         DEV_NOTE_NPM = """It looks like you're running the notebook from source.
        # If you're working on the Javascript of the notebook, try running
        # %s
        # in another terminal window to have the system incrementally
        # watch and build the notebook's JavaScript for you, as you make changes.""" % 'npm run build:watch'
        #         self.log.info(DEV_NOTE_NPM)

        template_settings = dict(
            nbserver_shim_template_paths=template_path,
            nbserver_shim_jinja_template_vars=self.jinja_template_vars,
            nbserver_shim_jinja2_env=env,
        )
        self.settings.update(**template_settings)

    def initialize_settings(self):
        """Add settings to the tornado app."""
        if self.ignore_minified_js:
            self.log.warning(
                _("""The `ignore_minified_js` flag is deprecated and no longer works."""
                  ))
            self.log.warning(
                _("""Alternatively use `%s` when working on the notebook's Javascript and LESS"""
                  ) % 'npm run build:watch')
            warnings.warn(
                _("The `ignore_minified_js` flag is deprecated and will be removed in Notebook 6.0"
                  ), DeprecationWarning)

        settings = dict(
            ignore_minified_js=self.ignore_minified_js,
            mathjax_url=self.mathjax_url,
            mathjax_config=self.mathjax_config,
            nbextensions_path=self.nbextensions_path,
        )
        self.settings.update(**settings)

    def initialize_handlers(self):
        """Load the (URL pattern, handler) tuples for each component."""
        # Order matters. The first handler to match the URL will handle the request.
        handlers = []
        # load extra services specified by users before default handlers
        for service in self.settings['extra_services']:
            handlers.extend(load_handlers(service))

        handlers.extend(load_handlers('notebook_shim.handlers'))

        handlers.append(
            (
                r"/nbextensions/(.*)",
                FileFindHandler,
                {
                    'path': self.settings['nbextensions_path'],
                    'no_cache_paths':
                    ['/'],  # don't cache anything in nbextensions
                }), )
        handlers.append((
            r"/custom/(.*)",
            FileFindHandler,
            {
                'path': self.settings['static_custom_path'],
                'no_cache_paths': ['/'],  # don't cache anything in custom
            }))

        # Add new handlers to Jupyter server handlers.
        self.handlers.extend(handlers)
Example #22
0
 def info_string(self):
     return _("Serving notebooks from local directory: %s") % self.root_dir
Example #23
0
class LanguageServerManagerAPI(LoggingConfigurable, HasListeners):
    """Public API that can be used for python-based spec finders and listeners"""

    nodejs = Unicode(help=_("path to nodejs executable")).tag(config=True)

    node_roots = List_(
        [], help=_("absolute paths in which to seek node_modules")).tag(
            config=True)

    extra_node_roots = List_(
        [],
        help=_("additional absolute paths to seek node_modules first")).tag(
            config=True)

    def find_node_module(self, *path_frag):
        """look through the node_module roots to find the given node module"""
        all_roots = self.extra_node_roots + self.node_roots
        found = None

        for candidate_root in all_roots:
            candidate = pathlib.Path(candidate_root, "node_modules",
                                     *path_frag)
            self.log.debug("Checking for %s", candidate)
            if candidate.exists():
                found = str(candidate)
                break

        if found is None:  # pragma: no cover
            self.log.debug("{} not found in node_modules of {}".format(
                pathlib.Path(*path_frag), all_roots))

        return found

    @default("nodejs")
    def _default_nodejs(self):
        return (shutil.which("node") or shutil.which("nodejs")
                or shutil.which("nodejs.exe"))

    @default("node_roots")
    def _default_node_roots(self):
        """get the "usual suspects" for where `node_modules` may be found

        - where this was launch (usually the same as NotebookApp.notebook_dir)
        - the JupyterLab staging folder (if available)
        - wherever conda puts it
        - wherever some other conventions put it
        """

        # check where the server was started first
        roots = [pathlib.Path.cwd()]

        # try jupyterlab staging next
        try:
            from jupyterlab import commands

            roots += [pathlib.Path(commands.get_app_dir()) / "staging"]
        except ImportError:  # pragma: no cover
            pass

        # conda puts stuff in $PREFIX/lib on POSIX systems
        roots += [pathlib.Path(sys.prefix) / "lib"]

        # ... but right in %PREFIX% on nt
        roots += [pathlib.Path(sys.prefix)]

        return roots
Example #24
0
 def _update_mathjax_config(self, change):
     self.log.info(_("Using MathJax configuration file: %s"), change['new'])
Example #25
0
class ExtensionApp(JupyterApp):
    """Base class for configurable Jupyter Server Extension Applications.

    ExtensionApp subclasses can be initialized two ways:
    1. Extension is listed as a jpserver_extension, and ServerApp calls
        its load_jupyter_server_extension classmethod. This is the
        classic way of loading a server extension.
    2. Extension is launched directly by calling its `launch_instance`
        class method. This method can be set as a entry_point in
        the extensions setup.py
    """
    # Subclasses should override this trait. Tells the server if
    # this extension allows other other extensions to be loaded
    # side-by-side when launched directly.
    load_other_extensions = True

    # The extension name used to name the jupyter config
    # file, jupyter_{name}_config.
    # This should also match the jupyter subcommand used to launch
    # this extension from the CLI, e.g. `jupyter {name}`.
    name = None

    # Extension URL sets the default landing page for this extension.
    extension_url = "/"

    # Extension can configure the ServerApp from the command-line
    classes = [
        ServerApp,
    ]

    @property
    def static_url_prefix(self):
        return "/static/{name}/".format(
            name=self.name)

    static_paths = List(Unicode(),
        help="""paths to search for serving static files.

        This allows adding javascript/css to be available from the notebook server machine,
        or overriding individual files in the IPython
        """
    ).tag(config=True)

    template_paths = List(Unicode(),
        help=_("""Paths to search for serving jinja templates.

        Can be used to override templates from notebook.templates.""")
    ).tag(config=True)

    settings = Dict(
        help=_("""Settings that will passed to the server.""")
    ).tag(config=True)

    handlers = List(
        help=_("""Handlers appended to the server.""")
    ).tag(config=True)

    # Whether to open in a browser after starting.
    open_browser = True

    def _config_file_name_default(self):
        """The default config file name."""
        if not self.name:
            return ''
        return 'jupyter_{}_config'.format(self.name.replace('-','_'))

    def initialize_settings(self):
        """Override this method to add handling of settings."""
        pass

    def initialize_handlers(self):
        """Override this method to append handlers to a Jupyter Server."""
        pass

    def initialize_templates(self):
        """Override this method to add handling of template files."""
        pass

    def _prepare_config(self):
        """Builds a Config object from the extension's traits and passes
        the object to the webapp's settings as `<name>_config`.
        """
        traits = self.class_own_traits().keys()
        self.extension_config = Config({t: getattr(self, t) for t in traits})
        self.settings['{}_config'.format(self.name)] = self.extension_config

    def _prepare_settings(self):
        # Make webapp settings accessible to initialize_settings method
        webapp = self.serverapp.web_app
        self.settings.update(**webapp.settings)

        # Add static and template paths to settings.
        self.settings.update({
            "{}_static_paths".format(self.name): self.static_paths,
            "{}".format(self.name): self
        })

        # Get setting defined by subclass using initialize_settings method.
        self.initialize_settings()

        # Update server settings with extension settings.
        webapp.settings.update(**self.settings)

    def _prepare_handlers(self):
        webapp = self.serverapp.web_app

        # Get handlers defined by extension subclass.
        self.initialize_handlers()

        # prepend base_url onto the patterns that we match
        new_handlers = []
        for handler_items in self.handlers:
            # Build url pattern including base_url
            pattern = url_path_join(webapp.settings['base_url'], handler_items[0])
            handler = handler_items[1]

            # Get handler kwargs, if given
            kwargs = {}
            if issubclass(handler, ExtensionHandlerMixin):
                kwargs['name'] = self.name

            try:
                kwargs.update(handler_items[2])
            except IndexError:
                pass

            new_handler = (pattern, handler, kwargs)
            new_handlers.append(new_handler)

        # Add static endpoint for this extension, if static paths are given.
        if len(self.static_paths) > 0:
            # Append the extension's static directory to server handlers.
            static_url = url_path_join(self.static_url_prefix, "(.*)")

            # Construct handler.
            handler = (
                static_url,
                webapp.settings['static_handler_class'],
                {'path': self.static_paths}
            )
            new_handlers.append(handler)

        webapp.add_handlers('.*$', new_handlers)

    def _prepare_templates(self):
        # Add templates to web app settings if extension has templates.
        if len(self.template_paths) > 0:
            self.settings.update({
                "{}_template_paths".format(self.name): self.template_paths
            })
        self.initialize_templates()

    @classmethod
    def initialize_server(cls, argv=[], load_other_extensions=True, **kwargs):
        """Creates an instance of ServerApp where this extension is enabled
        (superceding disabling found in other config from files).

        This is necessary when launching the ExtensionApp directly from
        the `launch_instance` classmethod.
        """
        # The ExtensionApp needs to add itself as enabled extension
        # to the jpserver_extensions trait, so that the ServerApp
        # initializes it.
        config = Config({
            "ServerApp": {
                "jpserver_extensions": {cls.name: True},
                "open_browser": cls.open_browser,
                "default_url": cls.extension_url
            }
        })
        serverapp = ServerApp.instance(**kwargs, argv=[], config=config)
        serverapp.initialize(argv=argv, find_extensions=load_other_extensions)
        return serverapp

    def link_to_serverapp(self, serverapp):
        """Link the ExtensionApp to an initialized ServerApp.

        The ServerApp is stored as an attribute and config
        is exchanged between ServerApp and `self` in case
        the command line contains traits for the ExtensionApp
        or the ExtensionApp's config files have server
        settings.
        """
        self.serverapp = serverapp
        # Load config from an ExtensionApp's config files.
        self.load_config_file()
        # ServerApp's config might have picked up
        # CLI config for the ExtensionApp. We call
        # update_config to update ExtensionApp's
        # traits with these values found in ServerApp's
        # config.
        # ServerApp config ---> ExtensionApp traits
        self.update_config(self.serverapp.config)
        # Use ExtensionApp's CLI parser to find any extra
        # args that passed through ServerApp and
        # now belong to ExtensionApp.
        self.parse_command_line(self.serverapp.extra_args)
        # If any config should be passed upstream to the
        # ServerApp, do it here.
        # i.e. ServerApp traits <--- ExtensionApp config
        self.serverapp.update_config(self.config)

    def initialize(self):
        """Initialize the extension app. The
        corresponding server app and webapp should already
        be initialized by this step.

        1) Appends Handlers to the ServerApp,
        2) Passes config and settings from ExtensionApp
        to the Tornado web application
        3) Points Tornado Webapp to templates and
        static assets.
        """
        if not hasattr(self, 'serverapp'):
            msg = (
                "This extension has no attribute `serverapp`. "
                "Try calling `.link_to_serverapp()` before calling "
                "`.initialize()`."
            )
            raise JupyterServerExtensionException(msg)

        self._prepare_config()
        self._prepare_templates()
        self._prepare_settings()
        self._prepare_handlers()

    def start(self):
        """Start the underlying Jupyter server.

        Server should be started after extension is initialized.
        """
        super(ExtensionApp, self).start()
        # Start the server.
        self.serverapp.start()

    def stop(self):
        """Stop the underlying Jupyter server.
        """
        self.serverapp.stop()
        self.serverapp.clear_instance()

    @classmethod
    def _load_jupyter_server_extension(cls, serverapp):
        """Initialize and configure this extension, then add the extension's
        settings and handlers to the server's web application.
        """
        try:
            # Get loaded extension from serverapp.
            extension = serverapp._enabled_extensions[cls.name]
        except KeyError:
            extension = cls()
            extension.link_to_serverapp(serverapp)
        extension.initialize()
        return extension

    @classmethod
    def launch_instance(cls, argv=None, **kwargs):
        """Launch the extension like an application. Initializes+configs a stock server
        and appends the extension to the server. Then starts the server and routes to
        extension's landing page.
        """
        # Handle arguments.
        if argv is None:
            args = sys.argv[1:]  # slice out extension config.
        else:
            args = []
        # Check for subcommands
        subapp = _preparse_for_subcommand(cls, args)
        if subapp:
            subapp.start()
        else:
            # Check for help, version, and generate-config arguments
            # before initializing server to make sure these
            # arguments trigger actions from the extension not the server.
            _preparse_for_stopping_flags(cls, args)
            # Get a jupyter server instance.
            serverapp = cls.initialize_server(
                argv=args,
                load_other_extensions=cls.load_other_extensions
            )
            # Log if extension is blocking other extensions from loading.
            if not cls.load_other_extensions:
                serverapp.log.info(
                    "{ext_name} is running without loading "
                    "other extensions.".format(ext_name=cls.name)
                )
            serverapp.start()