diff --git a/jupyter_server_proxy/__init__.py b/jupyter_server_proxy/__init__.py index cbce0d5f..addae684 100644 --- a/jupyter_server_proxy/__init__.py +++ b/jupyter_server_proxy/__init__.py @@ -2,6 +2,7 @@ from ._version import __version__ # noqa from .api import IconHandler, ServersInfoHandler +from .config import ServerProcess, ServerProcessEntryPoint from .config import ServerProxy as ServerProxyConfig from .config import get_entrypoint_server_processes, make_handlers, make_server_process from .handlers import setup_handlers @@ -45,7 +46,9 @@ def _load_jupyter_server_extension(nbapp): make_server_process(name, server_process_config, serverproxy_config) for name, server_process_config in serverproxy_config.servers.items() ] - server_processes += get_entrypoint_server_processes(serverproxy_config) + server_processes += get_entrypoint_server_processes( + serverproxy_config, parent=nbapp + ) server_handlers = make_handlers(base_url, server_processes) nbapp.web_app.add_handlers(".*", server_handlers) @@ -81,3 +84,5 @@ def _load_jupyter_server_extension(nbapp): # For backward compatibility load_jupyter_server_extension = _load_jupyter_server_extension _jupyter_server_extension_paths = _jupyter_server_extension_points + +__all__ = ["ServerProcess", "ServerProcessEntryPoint"] diff --git a/jupyter_server_proxy/config.py b/jupyter_server_proxy/config.py index 4b21cf70..d157fbc0 100644 --- a/jupyter_server_proxy/config.py +++ b/jupyter_server_proxy/config.py @@ -76,7 +76,7 @@ def _default_path_info(self): ) -class ServerProcess(Configurable): +class _ServerProcess(Configurable): name = Unicode(help="Name of the server").tag(config=True) command = List( @@ -264,6 +264,22 @@ def cats_only(response, path): ).tag(config=True) +class ServerProcess(_ServerProcess): + """ + A configurable server process for single standalone servers. + This is separate from ServerProcessEntryPoint so that we can configure it + independently of ServerProcessEntryPoint + """ + + +class ServerProcessEntryPoint(_ServerProcess): + """ + A ServeProcess entrypoint that is a Configurable. + This is separate from ServerProcess so that we can configure it + independently of ServerProcess + """ + + def _make_proxy_handler(sp: ServerProcess): """ Create an appropriate handler with given parameters @@ -319,16 +335,23 @@ def get_timeout(self): return _Proxy -def get_entrypoint_server_processes(serverproxy_config): +def get_entrypoint_server_processes(serverproxy_config, parent): sps = [] for entry_point in entry_points(group="jupyter_serverproxy_servers"): name = entry_point.name try: - server_process_config = entry_point.load()() + server_process_callable = entry_point.load() + if issubclass(server_process_callable, ServerProcessEntryPoint): + server_process = server_process_callable(name=name, parent=parent) + sps.append(server_process) + else: + server_process_config = server_process_callable() + sps.append( + make_server_process(name, server_process_config, serverproxy_config) + ) except Exception as e: warn(f"entry_point {name} was unable to be loaded: {str(e)}") continue - sps.append(make_server_process(name, server_process_config, serverproxy_config)) return sps diff --git a/tests/resources/jupyter_server_config.py b/tests/resources/jupyter_server_config.py index ac1e0dfe..06ed03c9 100644 --- a/tests/resources/jupyter_server_config.py +++ b/tests/resources/jupyter_server_config.py @@ -51,6 +51,12 @@ def my_env(): return {"MYVAR": "String with escaped {{var}}"} +# Traitlets configuration for test-serverprocessentrypoint in the dummyentrypoint package +c.CustomServerProcessEntryPoint.request_headers_override = { + "X-Custom-Header": "custom-configurable" +} + + c.ServerProxy.servers = { "python-http": { "command": [sys.executable, _get_path("httpinfo.py"), "--port={port}"], diff --git a/tests/test_proxies.py b/tests/test_proxies.py index 4573517c..3afdb3fd 100644 --- a/tests/test_proxies.py +++ b/tests/test_proxies.py @@ -528,3 +528,11 @@ async def test_server_proxy_rawsocket( await conn.write_message(msg) res = await conn.read_message() assert res == msg.swapcase() + + +def test_server_configurable_class(a_server_port_and_token: Tuple[int, str]) -> None: + PORT, TOKEN = a_server_port_and_token + r = request_get(PORT, "/test-serverprocessentrypoint/", TOKEN, host="127.0.0.1") + assert r.code == 200 + s = r.read().decode("ascii") + assert "X-Custom-Header: custom-configurable\n" in s