You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			638 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			638 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
"""An extension application."""
 | 
						|
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
import logging
 | 
						|
import re
 | 
						|
import sys
 | 
						|
import typing as t
 | 
						|
 | 
						|
from jinja2 import Environment, FileSystemLoader
 | 
						|
from jupyter_core.application import JupyterApp, NoStart
 | 
						|
from tornado.log import LogFormatter
 | 
						|
from tornado.web import RedirectHandler
 | 
						|
from traitlets import Any, Bool, Dict, HasTraits, List, Unicode, default
 | 
						|
from traitlets.config import Config
 | 
						|
 | 
						|
from jupyter_server.serverapp import ServerApp
 | 
						|
from jupyter_server.transutils import _i18n
 | 
						|
from jupyter_server.utils import is_namespace_package, url_path_join
 | 
						|
 | 
						|
from .handler import ExtensionHandlerMixin
 | 
						|
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
# Util functions and classes.
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
 | 
						|
 | 
						|
def _preparse_for_subcommand(application_klass, argv):
 | 
						|
    """Preparse command line to look for subcommands."""
 | 
						|
    # Read in arguments from command line.
 | 
						|
    if len(argv) == 0:
 | 
						|
        return None
 | 
						|
 | 
						|
    # Find any subcommands.
 | 
						|
    if application_klass.subcommands and len(argv) > 0:
 | 
						|
        # we have subcommands, and one may have been specified
 | 
						|
        subc, subargv = argv[0], argv[1:]
 | 
						|
        if re.match(r"^\w(\-?\w)*$", subc) and subc in application_klass.subcommands:
 | 
						|
            # it's a subcommand, and *not* a flag or class parameter
 | 
						|
            app = application_klass()
 | 
						|
            app.initialize_subcommand(subc, subargv)
 | 
						|
            return app.subapp
 | 
						|
 | 
						|
 | 
						|
def _preparse_for_stopping_flags(application_klass, argv):
 | 
						|
    """Looks for 'help', 'version', and 'generate-config; commands
 | 
						|
    in command line. If found, raises the help and version of
 | 
						|
    current Application.
 | 
						|
 | 
						|
    This is useful for traitlets applications that have to parse
 | 
						|
    the command line multiple times, but want to control when
 | 
						|
    when 'help' and 'version' is raised.
 | 
						|
    """
 | 
						|
    # Arguments after a '--' argument are for the script IPython may be
 | 
						|
    # about to run, not IPython iteslf. For arguments parsed here (help and
 | 
						|
    # version), we want to only search the arguments up to the first
 | 
						|
    # occurrence of '--', which we're calling interpreted_argv.
 | 
						|
    try:
 | 
						|
        interpreted_argv = argv[: argv.index("--")]
 | 
						|
    except ValueError:
 | 
						|
        interpreted_argv = argv
 | 
						|
 | 
						|
    # Catch any help calls.
 | 
						|
    if any(x in interpreted_argv for x in ("-h", "--help-all", "--help")):
 | 
						|
        app = application_klass()
 | 
						|
        app.print_help("--help-all" in interpreted_argv)
 | 
						|
        app.exit(0)
 | 
						|
 | 
						|
    # Catch version commands
 | 
						|
    if "--version" in interpreted_argv or "-V" in interpreted_argv:
 | 
						|
        app = application_klass()
 | 
						|
        app.print_version()
 | 
						|
        app.exit(0)
 | 
						|
 | 
						|
    # Catch generate-config commands.
 | 
						|
    if "--generate-config" in interpreted_argv:
 | 
						|
        app = application_klass()
 | 
						|
        app.write_default_config()
 | 
						|
        app.exit(0)
 | 
						|
 | 
						|
 | 
						|
class ExtensionAppJinjaMixin(HasTraits):
 | 
						|
    """Use Jinja templates for HTML templates on top of an ExtensionApp."""
 | 
						|
 | 
						|
    jinja2_options = Dict(
 | 
						|
        help=_i18n(
 | 
						|
            """Options to pass to the jinja2 environment for this
 | 
						|
        """
 | 
						|
        )
 | 
						|
    ).tag(config=True)
 | 
						|
 | 
						|
    @t.no_type_check
 | 
						|
    def _prepare_templates(self):
 | 
						|
        """Get templates defined in a subclass."""
 | 
						|
        self.initialize_templates()
 | 
						|
        # Add templates to web app settings if extension has templates.
 | 
						|
        if len(self.template_paths) > 0:
 | 
						|
            self.settings.update({f"{self.name}_template_paths": 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,
 | 
						|
        )
 | 
						|
 | 
						|
        # Add the jinja2 environment for this extension to the tornado settings.
 | 
						|
        self.settings.update({f"{self.name}_jinja2_env": self.jinja2_env})
 | 
						|
 | 
						|
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
# ExtensionApp
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
 | 
						|
 | 
						|
class JupyterServerExtensionException(Exception):
 | 
						|
    """Exception class for raising for Server extensions errors."""
 | 
						|
 | 
						|
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
# ExtensionApp
 | 
						|
# -----------------------------------------------------------------------------
 | 
						|
 | 
						|
 | 
						|
class ExtensionApp(JupyterApp):
 | 
						|
    """Base class for configurable Jupyter Server Extension Applications.
 | 
						|
 | 
						|
    ExtensionApp subclasses can be initialized two ways:
 | 
						|
 | 
						|
    - 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.
 | 
						|
 | 
						|
    - 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: dict[str, t.Any] = {}
 | 
						|
 | 
						|
    # 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(
 | 
						|
        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)
 | 
						|
 | 
						|
    @default("open_browser")
 | 
						|
    def _default_open_browser(self):
 | 
						|
        assert self.serverapp is not None
 | 
						|
        return self.serverapp.config["ServerApp"].get("open_browser", True)
 | 
						|
 | 
						|
    @property
 | 
						|
    def config_file_paths(self):
 | 
						|
        """Look on the same path as our parent for config files"""
 | 
						|
        # rely on parent serverapp, which should control all config loading
 | 
						|
        assert self.serverapp is not None
 | 
						|
        return self.serverapp.config_file_paths
 | 
						|
 | 
						|
    # 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: str | Unicode[str, str] = "ExtensionApp"  # type:ignore[assignment]
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def get_extension_package(cls):
 | 
						|
        """Get an extension package."""
 | 
						|
        parts = cls.__module__.split(".")
 | 
						|
        if is_namespace_package(parts[0]):
 | 
						|
            # in this case the package name is `<namespace>.<package>`.
 | 
						|
            return ".".join(parts[0:2])
 | 
						|
        return parts[0]
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def get_extension_point(cls):
 | 
						|
        """Get an extension point."""
 | 
						|
        return cls.__module__
 | 
						|
 | 
						|
    # Extension URL sets the default landing page for this extension.
 | 
						|
    extension_url = "/"
 | 
						|
 | 
						|
    default_url = Unicode().tag(config=True)
 | 
						|
 | 
						|
    @default("default_url")
 | 
						|
    def _default_url(self):
 | 
						|
        return self.extension_url
 | 
						|
 | 
						|
    file_url_prefix = Unicode("notebooks")
 | 
						|
 | 
						|
    # Is this linked to a serverapp yet?
 | 
						|
    _linked = Bool(False)
 | 
						|
 | 
						|
    # Extension can configure the ServerApp from the command-line
 | 
						|
    classes = [
 | 
						|
        ServerApp,
 | 
						|
    ]
 | 
						|
 | 
						|
    # A ServerApp is not defined yet, but will be initialized below.
 | 
						|
    serverapp: ServerApp | None = Any()  # type:ignore[assignment]
 | 
						|
 | 
						|
    @default("serverapp")
 | 
						|
    def _default_serverapp(self):
 | 
						|
        # load the current global instance, if any
 | 
						|
        if ServerApp.initialized():
 | 
						|
            try:
 | 
						|
                return ServerApp.instance()
 | 
						|
            except Exception:
 | 
						|
                # error retrieving instance, e.g. MultipleInstanceError
 | 
						|
                pass
 | 
						|
 | 
						|
        # serverapp accessed before it was defined,
 | 
						|
        # declare an empty one
 | 
						|
        return ServerApp()
 | 
						|
 | 
						|
    _log_formatter_cls = LogFormatter  # type:ignore[assignment]
 | 
						|
 | 
						|
    @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 (
 | 
						|
            "%(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 = f"static/{self.name}/"
 | 
						|
        assert self.serverapp is not None
 | 
						|
        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=_i18n(
 | 
						|
            """Paths to search for serving jinja templates.
 | 
						|
 | 
						|
        Can be used to override templates from notebook.templates."""
 | 
						|
        ),
 | 
						|
    ).tag(config=True)
 | 
						|
 | 
						|
    settings = Dict(help=_i18n("""Settings that will passed to the server.""")).tag(config=True)
 | 
						|
 | 
						|
    handlers: List[tuple[t.Any, ...]] = List(
 | 
						|
        help=_i18n("""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."""
 | 
						|
 | 
						|
    def initialize_handlers(self):
 | 
						|
        """Override this method to append handlers to a Jupyter Server."""
 | 
						|
 | 
						|
    def initialize_templates(self):
 | 
						|
        """Override this method to add handling of template files."""
 | 
						|
 | 
						|
    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[f"{self.name}_config"] = self.extension_config
 | 
						|
 | 
						|
    def _prepare_settings(self):
 | 
						|
        """Prepare the settings."""
 | 
						|
        # Make webapp settings accessible to initialize_settings method
 | 
						|
        assert self.serverapp is not None
 | 
						|
        webapp = self.serverapp.web_app
 | 
						|
        self.settings.update(**webapp.settings)
 | 
						|
 | 
						|
        # Add static and template paths to settings.
 | 
						|
        self.settings.update(
 | 
						|
            {
 | 
						|
                f"{self.name}_static_paths": self.static_paths,
 | 
						|
                f"{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):
 | 
						|
        """Prepare the handlers."""
 | 
						|
        assert self.serverapp is not None
 | 
						|
        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: dict[str, t.Any] = {}
 | 
						|
            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({f"{self.name}_template_paths": self.template_paths})
 | 
						|
        self.initialize_templates()
 | 
						|
 | 
						|
    def _jupyter_server_config(self):
 | 
						|
        """The jupyter server config."""
 | 
						|
        base_config = {
 | 
						|
            "ServerApp": {
 | 
						|
                "default_url": self.default_url,
 | 
						|
                "open_browser": self.open_browser,
 | 
						|
                "file_url_prefix": self.file_url_prefix,
 | 
						|
            }
 | 
						|
        }
 | 
						|
        base_config["ServerApp"].update(self.serverapp_config)
 | 
						|
        return base_config
 | 
						|
 | 
						|
    def _link_jupyter_server_extension(self, serverapp: ServerApp) -> None:
 | 
						|
        """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.
 | 
						|
 | 
						|
        Note, the ServerApp has not initialized the Tornado
 | 
						|
        Web Application yet, so do not try to affect the
 | 
						|
        `web_app` attribute.
 | 
						|
        """
 | 
						|
        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)
 | 
						|
        # Acknowledge that this extension has been linked.
 | 
						|
        self._linked = True
 | 
						|
 | 
						|
    def initialize(self):
 | 
						|
        """Initialize the extension app. The
 | 
						|
        corresponding server app and webapp should already
 | 
						|
        be initialized by this step.
 | 
						|
 | 
						|
        - Appends Handlers to the ServerApp,
 | 
						|
        - Passes config and settings from ExtensionApp
 | 
						|
          to the Tornado web application
 | 
						|
        - 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().start()
 | 
						|
        # Start the server.
 | 
						|
        assert self.serverapp is not None
 | 
						|
        self.serverapp.start()
 | 
						|
 | 
						|
    def current_activity(self):
 | 
						|
        """Return a list of activity happening in this extension."""
 | 
						|
        return
 | 
						|
 | 
						|
    async def stop_extension(self):
 | 
						|
        """Cleanup any resources managed by this extension."""
 | 
						|
 | 
						|
    def stop(self):
 | 
						|
        """Stop the underlying Jupyter server."""
 | 
						|
        assert self.serverapp is not None
 | 
						|
        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)
 | 
						|
        extension.initialize()
 | 
						|
        return extension
 | 
						|
 | 
						|
    async def _start_jupyter_server_extension(self, serverapp):
 | 
						|
        """
 | 
						|
        An async hook to start e.g. tasks from the extension after
 | 
						|
        the server's event loop is running.
 | 
						|
 | 
						|
        Override this method (no need to call `super()`) to
 | 
						|
        start (async) tasks from an extension.
 | 
						|
 | 
						|
        This is useful for starting e.g. background tasks from
 | 
						|
        an 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()
 | 
						|
 | 
						|
    serverapp_class = ServerApp
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def make_serverapp(cls, **kwargs: t.Any) -> ServerApp:
 | 
						|
        """Instantiate the ServerApp
 | 
						|
 | 
						|
        Override to customize the ServerApp before it loads any configuration
 | 
						|
        """
 | 
						|
        return cls.serverapp_class.instance(**kwargs)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def initialize_server(cls, argv=None, load_other_extensions=True, **kwargs):
 | 
						|
        """Creates an instance of ServerApp and explicitly sets
 | 
						|
        this extension to enabled=True (i.e. superseding disabling
 | 
						|
        found in other config from files).
 | 
						|
 | 
						|
        The `launch_instance` method uses this method to initialize
 | 
						|
        and start a server.
 | 
						|
        """
 | 
						|
        jpserver_extensions = {cls.get_extension_package(): True}
 | 
						|
        find_extensions = cls.load_other_extensions
 | 
						|
        if "jpserver_extensions" in cls.serverapp_config:
 | 
						|
            jpserver_extensions.update(cls.serverapp_config["jpserver_extensions"])
 | 
						|
            cls.serverapp_config["jpserver_extensions"] = jpserver_extensions
 | 
						|
            find_extensions = False
 | 
						|
        serverapp = cls.make_serverapp(jpserver_extensions=jpserver_extensions, **kwargs)
 | 
						|
        serverapp.aliases.update(cls.aliases)  # type:ignore[has-type]
 | 
						|
        serverapp.initialize(
 | 
						|
            argv=argv or [],
 | 
						|
            starter_extension=cls.name,
 | 
						|
            find_extensions=find_extensions,
 | 
						|
        )
 | 
						|
        return serverapp
 | 
						|
 | 
						|
    @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:  # noqa: SIM108
 | 
						|
            args = sys.argv[1:]  # slice out extension config.
 | 
						|
        else:
 | 
						|
            args = argv
 | 
						|
 | 
						|
        # Handle all "stops" that could happen before
 | 
						|
        # continuing to launch a server+extension.
 | 
						|
        subapp = _preparse_for_subcommand(cls, args)
 | 
						|
        if subapp:
 | 
						|
            subapp.start()
 | 
						|
            return
 | 
						|
 | 
						|
        # 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)
 | 
						|
        serverapp = cls.initialize_server(argv=args)
 | 
						|
 | 
						|
        # Log if extension is blocking other extensions from loading.
 | 
						|
        if not cls.load_other_extensions:
 | 
						|
            serverapp.log.info(f"{cls.name} is running without loading other extensions.")
 | 
						|
        # Start the server.
 | 
						|
        try:
 | 
						|
            serverapp.start()
 | 
						|
        except NoStart:
 | 
						|
            pass
 |