diff --git a/jupyter_server/extension/application.py b/jupyter_server/extension/application.py index 95dc883a15..c1b15a53e3 100644 --- a/jupyter_server/extension/application.py +++ b/jupyter_server/extension/application.py @@ -9,6 +9,7 @@ Unicode, List, Dict, + Bool, default ) from traitlets.config import Config @@ -147,9 +148,20 @@ class method. This method can be set as a entry_point in # 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 = { - "open_browser": True - } + serverapp_config = {} + + # Some subclasses will likely ovrride this trait to flip + # the default value to True if they offer a browser + # based frontend. + open_browser = Bool( + False, + 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. @@ -365,6 +377,8 @@ def initialize_server(cls, argv=[], load_other_extensions=True, **kwargs): config = Config(cls._jupyter_server_config()) serverapp = ServerApp.instance(**kwargs, argv=[], config=config) serverapp.initialize(argv=argv, find_extensions=load_other_extensions) + # Inform the serverapp that this extension app started the app. + serverapp._starter_app_name = cls.name return serverapp def initialize(self): diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 9335f239c3..5961fdfcb5 100755 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -887,6 +887,28 @@ def _default_allow_remote(self): """ ) + # The name of the app that started this server (if not started directly). + # It is sometimes important to know if + which another app (say a server extension) + # started the serverapp to properly configure some traits. + # This trait should not be configured by users. It will likely be set by ExtensionApp. + _starter_app_name = Unicode(None, allow_none=True) + + @validate('_starter_app_name') + def _validate_starter_app(self, proposal): + # Check that a previous server extension isn't named yet + value = proposal["value"] + if self._starter_app_name != None: + raise TraitError("Another extension was already named as the starter_server_extension.") + return value + + @property + def starter_app(self): + """Get the Extension that started this server.""" + name = self._starter_app_name + if name is None: + return + return self.extension_manager.extension_points.get(name, None).app + open_browser = Bool(False, config=True, help="""Whether to open in a browser after starting. The specific browser used is platform dependent and @@ -895,6 +917,31 @@ def _default_allow_remote(self): (ServerApp.browser) configuration option. """) + + def _handle_browser_opening(self): + """This method handles whether a browser should be opened. + By default, Jupyter Server doesn't try to open an browser. However, + it's many server extensions might want to open the browser by default. + This essentially toggles the default value for open_browser. + + From a UX perspective, this needs to be surfaced to the user. The default + behavior of Jupyter Server switches, which can be confusing. + """ + # If the server was started by another application, use that applications + # trait for the open_browser trait. If that trait is not given, ignore + if self.starter_app: + try: + if self.starter_app.open_browser: + self.launch_browser() + # If the starter_app doesn't have an open_browser trait, ignore + # move on and don't start a browser. + except AttributeError: + pass + else: + if self.open_browser: + self.launch_browser() + + browser = Unicode(u'', config=True, help="""Specify what command to use to invoke a web browser when starting the server. If not specified, the @@ -1807,8 +1854,8 @@ def start_app(self): self.write_server_info_file() self.write_browser_open_file() - if self.open_browser: - self.launch_browser() + # Handle the browser opening. + self._handle_browser_opening() if self.token and self._token_generated: # log full URL with generated token, so there's a copy/pasteable link