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 _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)))
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})
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)
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()
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 ) }, )], )
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()
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()
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)))
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
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).""")
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)
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': {
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()
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)
def info_string(self): return _("Serving notebooks from local directory: %s") % self.root_dir
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
def _update_mathjax_config(self, change): self.log.info(_("Using MathJax configuration file: %s"), change['new'])
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()