From e07a61533ea0e5d89dd0cfec9ee6717fbc9c0b1a Mon Sep 17 00:00:00 2001 From: Jonathan Windgassen Date: Tue, 7 Jan 2025 13:11:07 +0100 Subject: [PATCH] Allow configuration via traitlets --- docs/source/standalone.md | 23 +++++++++++++++++++++++ jupyter_server_proxy/standalone/app.py | 24 +++++++++++++++++------- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/docs/source/standalone.md b/docs/source/standalone.md index a0a9158c..a0a8ed03 100644 --- a/docs/source/standalone.md +++ b/docs/source/standalone.md @@ -58,6 +58,29 @@ not trigger the login process when accessing the application. especially on multi-user systems. ``` +### Configuration via traitlets + +Instead of using the commandline, a standalone proxy can also be configured via a `traitlets` configuration file. +The configuration file can be loaded by running `jupyter standaloneproxy --config path/to/config.py`. + +The options mentioned above can also be configured in the config file: + +```python +# Specify the command to execute +c.StandaloneProxyServer.command = [ + "voila", "--no-browser", "--port={port}", "/path/to/some/Notebook.ipynb" +] + +# Specify address and port +c.StandaloneProxyServer.address = "localhost" +c.StandaloneProxyServer.port = 8000 + +# Disable authentication +c.StandaloneProxyServer.skip_authentication = True +``` + +A default config file can be emitted by running `jupyter standaloneproxy --generate-config` + ## Usage with JupyterHub To launch a standalone proxy with JupyterHub, you need to customize the `Spawner` inside the configuration diff --git a/jupyter_server_proxy/standalone/app.py b/jupyter_server_proxy/standalone/app.py index 8239ad94..ebdd2d40 100644 --- a/jupyter_server_proxy/standalone/app.py +++ b/jupyter_server_proxy/standalone/app.py @@ -7,11 +7,11 @@ from textwrap import dedent from urllib.parse import urlparse +from jupyter_core.application import JupyterApp from jupyterhub.services.auth import HubOAuthCallbackHandler from jupyterhub.utils import make_ssl_context from tornado import httpclient, httpserver, ioloop, web from tornado.web import RedirectHandler -from traitlets.config import Application as TraitletsApplication from traitlets.traitlets import Bool, Int, Unicode, default, validate from ..config import ServerProcess @@ -19,7 +19,7 @@ from .proxy import make_standalone_proxy -class StandaloneProxyServer(TraitletsApplication, ServerProcess): +class StandaloneProxyServer(JupyterApp, ServerProcess): name = "jupyter-standalone-proxy" description = """ Wrap an arbitrary web service so it can be used in place of 'jupyterhub-singleuser' @@ -27,12 +27,9 @@ class StandaloneProxyServer(TraitletsApplication, ServerProcess): Usage: jupyter standaloneproxy [options] -- - The will be executed to start the web service once the proxy receives the first request. The command can - contain the placeholders '{{port}}', '{{unix_socket}}' and '{{base_url}}', which will be replaced with the - appropriate values once the application starts. - For more details, see the jupyter-server-proxy documentation. """ + examples = "jupyter standaloneproxy -- voila --port={port} --no-browser /path/to/notebook.ipynb" base_url = Unicode( help=""" @@ -152,6 +149,7 @@ def __init__(self, **kwargs): # exeptions we do not need, for easier use of the CLI # We don't need "command" here, as we will take it from the extra_args ignore_traits = [ + "name", "launcher_entry", "new_browser_tab", "rewrite_response", @@ -159,12 +157,13 @@ def __init__(self, **kwargs): "command", ] server_process_aliases = { - trait: f"ServerProcess.{trait}" + trait: f"StandaloneProxyServer.{trait}" for trait in ServerProcess.class_traits(config=True) if trait not in ignore_traits and trait not in self.flags } self.aliases = { + **super().aliases, **server_process_aliases, "base_url": "StandaloneProxyServer.base_url", "address": "StandaloneProxyServer.address", @@ -174,6 +173,17 @@ def __init__(self, **kwargs): "websocket_max_message_size": "StandaloneProxyServer.websocket_max_message_size", } + def emit_alias_help(self): + yield from super().emit_alias_help() + yield "" + + # Manually yield the help for command, which we will get from extra_args + command_help = StandaloneProxyServer.class_get_trait_help( + ServerProcess.command + ).split("\n") + yield command_help[0].replace("--StandaloneProxyServer.command", "command") + yield from command_help[1:] + def get_proxy_base_class(self) -> tuple[type | None, dict]: cls, kwargs = super().get_proxy_base_class() if cls is None: