Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate %U username substitution #748

Merged
merged 1 commit into from Sep 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/source/api/spawner.rst
Expand Up @@ -13,6 +13,6 @@ Module: :mod:`jupyterhub.spawner`
----------------

.. autoclass:: Spawner
:members: options_from_form, poll, start, stop, get_args, get_env, get_state
:members: options_from_form, poll, start, stop, get_args, get_env, get_state, template_namespace, format_string

.. autoclass:: LocalProcessSpawner
65 changes: 58 additions & 7 deletions jupyterhub/spawner.py
Expand Up @@ -21,6 +21,7 @@
from traitlets.config import LoggingConfigurable
from traitlets import (
Any, Bool, Dict, Instance, Integer, Float, List, Unicode,
validate,
)

from .traitlets import Command
Expand Down Expand Up @@ -147,7 +148,7 @@ def options_from_form(self, form_data):
help="""The notebook directory for the single-user server

`~` will be expanded to the user's home directory
`%U` will be expanded to the user's username
`{username}` will be expanded to the user's username
"""
).tag(config=True)

Expand All @@ -158,10 +159,22 @@ def options_from_form(self, form_data):
full filesystem traversal, while preserving user's homedir as
landing page for notebook

`%U` will be expanded to the user's username
`{username}` will be expanded to the user's username
"""
).tag(config=True)

@validate('notebook_dir', 'default_url')
def _deprecate_percent_u(self, proposal):
print(proposal)
v = proposal['value']
if '%U' in v:
self.log.warn("%%U for username in %s is deprecated in JupyterHub 0.7, use {username}",
proposal['trait'].name,
)
v = v.replace('%U', '{username}')
self.log.warn("Converting %r to %r", proposal['value'], v)
return v

disable_user_config = Bool(False,
help="""Disable per-user configuration of single-user servers.

Expand Down Expand Up @@ -243,7 +256,45 @@ def get_env(self):

env['JPY_API_TOKEN'] = self.api_token
return env


def template_namespace(self):
"""Return the template namespace for format-string formatting.

Currently used on default_url and notebook_dir.

Subclasses may add items to the available namespace.

The default implementation includes::

{
'username': user.name,
'base_url': users_base_url,
}

Returns:

ns (dict): namespace for string formatting.
"""
d = {'username': self.user.name}
if self.user.server:
d['base_url'] = self.user.server.base_url
return d

def format_string(self, s):
"""Render a Python format string

Uses :meth:`Spawner.template_namespace` to populate format namespace.

Args:

s (str): Python format-string to be formatted.

Returns:

str: Formatted string, rendered
"""
return s.format(**self.template_namespace())

def get_args(self):
"""Return the arguments to be passed after self.cmd"""
args = [
Expand All @@ -264,11 +315,11 @@ def get_args(self):
args.append('--port=%i' % self.user.server.port)

if self.notebook_dir:
self.notebook_dir = self.notebook_dir.replace("%U",self.user.name)
args.append('--notebook-dir=%s' % self.notebook_dir)
notebook_dir = self.format_string(self.notebook_dir)
args.append('--notebook-dir=%s' % notebook_dir)
if self.default_url:
self.default_url = self.default_url.replace("%U",self.user.name)
args.append('--NotebookApp.default_url=%s' % self.default_url)
default_url = self.format_string(self.default_url)
args.append('--NotebookApp.default_url=%s' % default_url)

if self.debug:
args.append('--debug')
Expand Down
14 changes: 14 additions & 0 deletions jupyterhub/tests/test_spawner.py
Expand Up @@ -41,6 +41,8 @@ def new_spawner(db, **kwargs):
kwargs.setdefault('cmd', [sys.executable, '-c', _echo_sleep])
kwargs.setdefault('user', db.query(orm.User).first())
kwargs.setdefault('hub', db.query(orm.Hub).first())
kwargs.setdefault('notebook_dir', os.getcwd())
kwargs.setdefault('default_url', '/user/{username}/lab')
kwargs.setdefault('INTERRUPT_TIMEOUT', 1)
kwargs.setdefault('TERM_TIMEOUT', 1)
kwargs.setdefault('KILL_TIMEOUT', 1)
Expand Down Expand Up @@ -128,6 +130,7 @@ def test_stop_spawner_stop_now(db, io_loop):
status = io_loop.run_sync(spawner.poll)
assert status == -signal.SIGTERM


def test_spawner_poll(db, io_loop):
first_spawner = new_spawner(db)
user = first_spawner.user
Expand Down Expand Up @@ -160,6 +163,7 @@ def test_spawner_poll(db, io_loop):
status = io_loop.run_sync(spawner.poll)
assert status is not None


def test_setcwd():
cwd = os.getcwd()
with tempfile.TemporaryDirectory() as td:
Expand All @@ -178,3 +182,13 @@ def raiser(path):
spawnermod._try_setcwd(cwd)
assert os.getcwd().startswith(temp_root)
os.chdir(cwd)


def test_string_formatting(db):
s = new_spawner(db, notebook_dir='user/%U/', default_url='/base/{username}')
name = s.user.name
assert s.notebook_dir == 'user/{username}/'
assert s.default_url == '/base/{username}'
assert s.format_string(s.notebook_dir) == 'user/%s/' % name
assert s.format_string(s.default_url) == '/base/%s' % name