From 8e1d67e866e0ab887bc8b6b684b5462533ec5b4d Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 10 Mar 2022 10:17:15 +0100 Subject: [PATCH 1/3] Use app.config_file_path to find server extensions This property in the base class already has the same logic to add config_dir to jupyter_config_path JupyterHub overrides this to have slightly different logic, and the same should apply here --- jupyter_server/serverapp.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 1ee4cf64b4..422f4ffb13 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -104,7 +104,6 @@ base_flags, base_aliases, ) -from jupyter_core.paths import jupyter_config_path from jupyter_client import KernelManager from jupyter_client.kernelspec import KernelSpecManager from jupyter_client.session import Session @@ -2154,11 +2153,7 @@ def find_server_extensions(self): # This enables merging on keys, which we want for extension enabling. # Regular config loading only merges at the class level, # so each level clobbers the previous. - config_paths = jupyter_config_path() - if self.config_dir not in config_paths: - # add self.config_dir to the front, if set manually - config_paths.insert(0, self.config_dir) - manager = ExtensionConfigManager(read_config_path=config_paths) + manager = ExtensionConfigManager(read_config_path=self.config_file_paths) extensions = manager.get_jpserver_extensions() for modulename, enabled in sorted(extensions.items()): From 717f22fc62bbeccfee0129975fee1f0a5e9e12b7 Mon Sep 17 00:00:00 2001 From: Min RK Date: Thu, 10 Mar 2022 10:57:20 +0100 Subject: [PATCH 2/3] extensions should inherit parent's config_file_path base ServerApp should have total control over config file loading --- jupyter_server/extension/application.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/jupyter_server/extension/application.py b/jupyter_server/extension/application.py index ea18d687d4..0b32ef4904 100644 --- a/jupyter_server/extension/application.py +++ b/jupyter_server/extension/application.py @@ -162,6 +162,11 @@ class method. This method can be set as a entry_point in def _default_open_browser(self): return self.serverapp.config["ServerApp"].get("open_browser", True) + @property + def config_file_paths(self): + """Look on the same path as our parent for config files""" + return self.serverapp.config_file_paths + # The extension name used to name the jupyter config # file, jupyter_{name}_config. # This should also match the jupyter subcommand used to launch From 3192e14421db99ec0d1e018c7fc3b32b856e68b3 Mon Sep 17 00:00:00 2001 From: Min RK Date: Mon, 14 Mar 2022 13:39:51 +0100 Subject: [PATCH 3/3] ensure ExtensionApp.serverapp is never None Shouldn't ever be accessed without assignment, but may in cases where ExtensionApps skip initialization and loads config directly --- jupyter_server/extension/application.py | 16 ++++++++++++++-- tests/extension/test_app.py | 8 ++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/jupyter_server/extension/application.py b/jupyter_server/extension/application.py index 0b32ef4904..932263e702 100644 --- a/jupyter_server/extension/application.py +++ b/jupyter_server/extension/application.py @@ -12,6 +12,7 @@ from traitlets import default from traitlets import Dict from traitlets import HasTraits +from traitlets import Instance from traitlets import List from traitlets import Unicode from traitlets.config import Config @@ -103,7 +104,7 @@ def _prepare_templates(self): loader=FileSystemLoader(self.template_paths), extensions=["jinja2.ext.i18n"], autoescape=True, - **self.jinja2_options + **self.jinja2_options, ) # Add the jinja2 environment for this extension to the tornado settings. @@ -165,6 +166,7 @@ def _default_open_browser(self): @property def config_file_paths(self): """Look on the same path as our parent for config files""" + # rely on parent serverapp, which should control all config loading return self.serverapp.config_file_paths # The extension name used to name the jupyter config @@ -205,7 +207,17 @@ def _default_url(self): ] # A ServerApp is not defined yet, but will be initialized below. - serverapp = None + serverapp = Instance(ServerApp) + + @default("serverapp") + def _default_serverapp(self): + # load the current global instance, if any + if ServerApp.initialized(): + return ServerApp.instance() + else: + # serverapp accessed before it was defined, + # declare an empty one + return ServerApp() _log_formatter_cls = LogFormatter diff --git a/tests/extension/test_app.py b/tests/extension/test_app.py index dc045631a2..c550e4c75e 100644 --- a/tests/extension/test_app.py +++ b/tests/extension/test_app.py @@ -69,6 +69,14 @@ def test_extensionapp_load_config_file( assert mock_extension.mock_trait == "config from file" +def test_extensionapp_no_parent(): + # make sure we can load config files, even when serverapp is not passed + # relevant for e.g. shortcuts to config-loading + app = MockExtensionApp() + assert isinstance(app.config_file_paths, list) + assert app.serverapp is not None + + OPEN_BROWSER_COMBINATIONS = ( (True, {}), (True, {"ServerApp": {"open_browser": True}}),