diff --git a/jupyterhub/app.py b/jupyterhub/app.py index 8e5b3df1..2088aae7 100644 --- a/jupyterhub/app.py +++ b/jupyterhub/app.py @@ -315,6 +315,7 @@ class JupyterHub(Application): should be accessed by users. .. deprecated: 0.9 + Use JupyterHub.bind_url """ ).tag(config=True) @@ -330,26 +331,6 @@ class JupyterHub(Application): """ ).tag(config=True) - @observe('ip', 'port') - def _ip_port_changed(self, change): - urlinfo = urlparse(self.bind_url) - urlinfo = urlinfo._replace(netloc='%s:%i' % (self.ip, self.port)) - self.bind_url = urlunparse(urlinfo) - - bind_url = Unicode( - "http://127.0.0.1:8000", - help="""The public facing URL of the whole JupyterHub application. - - This is the address on which the proxy will bind. - Sets protocol, ip, base_url - """ - ).tag(config=True) - - @observe('bind_url') - def _bind_url_changed(self, change): - urlinfo = urlparse(change.new) - self.base_url = urlinfo.path - base_url = URLPrefix('/', help="""The base URL of the entire application. @@ -366,6 +347,25 @@ class JupyterHub(Application): # call validate to ensure leading/trailing slashes return JupyterHub.base_url.validate(self, urlparse(self.bind_url).path) + @observe('ip', 'port', 'base_url') + def _url_part_changed(self, change): + """propagate deprecated ip/port/base_url config to the bind_url""" + urlinfo = urlparse(self.bind_url) + urlinfo = urlinfo._replace(netloc='%s:%i' % (self.ip, self.port)) + urlinfo = urlinfo._replace(path=self.base_url) + bind_url = urlunparse(urlinfo) + if bind_url != self.bind_url: + self.bind_url = bind_url + + bind_url = Unicode( + "http://127.0.0.1:8000", + help="""The public facing URL of the whole JupyterHub application. + + This is the address on which the proxy will bind. + Sets protocol, ip, base_url + """ + ).tag(config=True) + subdomain_host = Unicode('', help="""Run single-user servers on subdomains of this host. diff --git a/jupyterhub/tests/test_app.py b/jupyterhub/tests/test_app.py index 8637ce2f..d18adde4 100644 --- a/jupyterhub/tests/test_app.py +++ b/jupyterhub/tests/test_app.py @@ -8,19 +8,22 @@ from subprocess import check_output, Popen, PIPE from tempfile import NamedTemporaryFile, TemporaryDirectory from unittest.mock import patch -from tornado import gen import pytest +from tornado import gen +from traitlets.config import Config from .mocking import MockHub from .test_api import add_user from .. import orm -from ..app import COOKIE_SECRET_BYTES +from ..app import COOKIE_SECRET_BYTES, JupyterHub + def test_help_all(): out = check_output([sys.executable, '-m', 'jupyterhub', '--help-all']).decode('utf8', 'replace') assert '--ip' in out assert '--JupyterHub.ip' in out + def test_token_app(): cmd = [sys.executable, '-m', 'jupyterhub', 'token'] out = check_output(cmd + ['--help-all']).decode('utf8', 'replace') @@ -30,6 +33,7 @@ def test_token_app(): out = check_output(cmd + ['user'], cwd=td).decode('utf8', 'replace').strip() assert re.match(r'^[a-z0-9]+$', out) + def test_generate_config(): with NamedTemporaryFile(prefix='jupyterhub_config', suffix='.py') as tf: cfg_file = tf.name @@ -218,3 +222,41 @@ def test_resume_spawners(tmpdir, request): assert not user.running assert user.spawner.server is None assert list(db.query(orm.Server)) == [] + + +@pytest.mark.parametrize( + 'hub_config, expected', + [ + ( + {'ip': '0.0.0.0'}, + {'bind_url': 'http://0.0.0.0:8000/'}, + ), + ( + {'port': 123, 'base_url': '/prefix'}, + { + 'bind_url': 'http://:123/prefix/', + 'base_url': '/prefix/', + }, + ), + ( + {'bind_url': 'http://0.0.0.0:12345/sub'}, + {'base_url': '/sub/'}, + ), + ] +) +def test_url_config(hub_config, expected): + # construct the config object + cfg = Config() + for key, value in hub_config.items(): + cfg.JupyterHub[key] = value + + # instantiate the Hub and load config + app = JupyterHub(config=cfg) + # validate config + for key, value in hub_config.items(): + if key not in expected: + assert getattr(app, key) == value + + # validate additional properties + for key, value in expected.items(): + assert getattr(app, key) == value