mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 07:23:00 +00:00
Merge pull request #748 from minrk/string-formatting
Deprecate `%U` username substitution
This commit is contained in:
@@ -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
|
||||
|
@@ -21,6 +21,7 @@ from tornado.ioloop import PeriodicCallback
|
||||
from traitlets.config import LoggingConfigurable
|
||||
from traitlets import (
|
||||
Any, Bool, Dict, Instance, Integer, Float, List, Unicode,
|
||||
validate,
|
||||
)
|
||||
|
||||
from .traitlets import Command
|
||||
@@ -147,7 +148,7 @@ class Spawner(LoggingConfigurable):
|
||||
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)
|
||||
|
||||
@@ -158,10 +159,22 @@ class Spawner(LoggingConfigurable):
|
||||
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.
|
||||
|
||||
@@ -244,6 +257,44 @@ class Spawner(LoggingConfigurable):
|
||||
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 = [
|
||||
@@ -264,11 +315,11 @@ class Spawner(LoggingConfigurable):
|
||||
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')
|
||||
|
@@ -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)
|
||||
@@ -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
|
||||
@@ -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:
|
||||
@@ -178,3 +182,13 @@ def test_setcwd():
|
||||
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
|
||||
|
||||
|
Reference in New Issue
Block a user