mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-13 04:53:01 +00:00
Instantiate Spawner on User init
shrinks `User.spawn` to take single argument, grants User more direct access to state.
This commit is contained in:
@@ -352,7 +352,8 @@ class JupyterHub(Application):
|
|||||||
|
|
||||||
users = Instance(UserDict)
|
users = Instance(UserDict)
|
||||||
def _users_default(self):
|
def _users_default(self):
|
||||||
return UserDict(db_factory=lambda : self.db)
|
assert self.tornado_settings
|
||||||
|
return UserDict(db_factory=lambda : self.db, settings=self.tornado_settings)
|
||||||
|
|
||||||
admin_access = Bool(False, config=True,
|
admin_access = Bool(False, config=True,
|
||||||
help="""Grant admin users permission to access single-user servers.
|
help="""Grant admin users permission to access single-user servers.
|
||||||
@@ -705,17 +706,14 @@ class JupyterHub(Application):
|
|||||||
yield user.stop()
|
yield user.stop()
|
||||||
|
|
||||||
for orm_user in db.query(orm.User):
|
for orm_user in db.query(orm.User):
|
||||||
self.users[orm_user.id] = user = User(orm_user)
|
self.users[orm_user.id] = user = User(orm_user, self.tornado_settings)
|
||||||
if not user.state:
|
if not user.state:
|
||||||
# without spawner state, server isn't valid
|
# without spawner state, server isn't valid
|
||||||
user.server = None
|
user.server = None
|
||||||
user_summaries.append(_user_summary(user))
|
user_summaries.append(_user_summary(user))
|
||||||
continue
|
continue
|
||||||
self.log.debug("Loading state for %s from db", user.name)
|
self.log.debug("Loading state for %s from db", user.name)
|
||||||
user.spawner = spawner = self.spawner_class(
|
spawner = user.spawner
|
||||||
user=user, hub=self.hub, config=self.config, db=self.db,
|
|
||||||
authenticator=self.authenticator,
|
|
||||||
)
|
|
||||||
status = yield spawner.poll()
|
status = yield spawner.poll()
|
||||||
if status is None:
|
if status is None:
|
||||||
self.log.info("%s still running", user.name)
|
self.log.info("%s still running", user.name)
|
||||||
@@ -860,7 +858,6 @@ class JupyterHub(Application):
|
|||||||
proxy=self.proxy,
|
proxy=self.proxy,
|
||||||
hub=self.hub,
|
hub=self.hub,
|
||||||
admin_users=self.authenticator.admin_users,
|
admin_users=self.authenticator.admin_users,
|
||||||
users=self.users,
|
|
||||||
admin_access=self.admin_access,
|
admin_access=self.admin_access,
|
||||||
authenticator=self.authenticator,
|
authenticator=self.authenticator,
|
||||||
spawner_class=self.spawner_class,
|
spawner_class=self.spawner_class,
|
||||||
@@ -879,6 +876,8 @@ class JupyterHub(Application):
|
|||||||
# allow configured settings to have priority
|
# allow configured settings to have priority
|
||||||
settings.update(self.tornado_settings)
|
settings.update(self.tornado_settings)
|
||||||
self.tornado_settings = settings
|
self.tornado_settings = settings
|
||||||
|
# constructing users requires access to tornado_settings
|
||||||
|
self.tornado_settings['users'] = self.users
|
||||||
|
|
||||||
def init_tornado_application(self):
|
def init_tornado_application(self):
|
||||||
"""Instantiate the tornado Application object"""
|
"""Instantiate the tornado Application object"""
|
||||||
@@ -915,9 +914,9 @@ class JupyterHub(Application):
|
|||||||
self.init_hub()
|
self.init_hub()
|
||||||
self.init_proxy()
|
self.init_proxy()
|
||||||
yield self.init_users()
|
yield self.init_users()
|
||||||
|
self.init_tornado_settings()
|
||||||
yield self.init_spawners()
|
yield self.init_spawners()
|
||||||
self.init_handlers()
|
self.init_handlers()
|
||||||
self.init_tornado_settings()
|
|
||||||
self.init_tornado_application()
|
self.init_tornado_application()
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
|
@@ -275,14 +275,8 @@ class BaseHandler(RequestHandler):
|
|||||||
raise RuntimeError("Spawn already pending for: %s" % user.name)
|
raise RuntimeError("Spawn already pending for: %s" % user.name)
|
||||||
tic = IOLoop.current().time()
|
tic = IOLoop.current().time()
|
||||||
|
|
||||||
f = user.spawn(
|
f = user.spawn(options)
|
||||||
spawner_class=self.spawner_class,
|
|
||||||
base_url=self.base_url,
|
|
||||||
hub=self.hub,
|
|
||||||
config=self.config,
|
|
||||||
authenticator=self.authenticator,
|
|
||||||
options=options,
|
|
||||||
)
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def finish_user_spawn(f=None):
|
def finish_user_spawn(f=None):
|
||||||
"""Finish the user spawn by registering listeners and notifying the proxy.
|
"""Finish the user spawn by registering listeners and notifying the proxy.
|
||||||
|
@@ -47,7 +47,7 @@ def add_user(db, app=None, **kwargs):
|
|||||||
db.add(orm_user)
|
db.add(orm_user)
|
||||||
db.commit()
|
db.commit()
|
||||||
if app:
|
if app:
|
||||||
user = app.users[orm_user.id] = User(orm_user)
|
user = app.users[orm_user.id] = User(orm_user, app.tornado_settings)
|
||||||
return user
|
return user
|
||||||
else:
|
else:
|
||||||
return orm_user
|
return orm_user
|
||||||
@@ -409,7 +409,7 @@ def test_slow_spawn(app, io_loop):
|
|||||||
|
|
||||||
|
|
||||||
def test_never_spawn(app, io_loop):
|
def test_never_spawn(app, io_loop):
|
||||||
app.tornado_application.settings['spawner_class'] = mocking.NeverSpawner
|
app.tornado_settings['spawner_class'] = mocking.NeverSpawner
|
||||||
app.tornado_application.settings['slow_spawn_timeout'] = 0
|
app.tornado_application.settings['slow_spawn_timeout'] = 0
|
||||||
|
|
||||||
db = app.db
|
db = app.db
|
||||||
|
@@ -98,15 +98,19 @@ def test_spawn_fails(db, io_loop):
|
|||||||
orm_user = orm.User(name='aeofel')
|
orm_user = orm.User(name='aeofel')
|
||||||
db.add(orm_user)
|
db.add(orm_user)
|
||||||
db.commit()
|
db.commit()
|
||||||
user = User(orm_user)
|
|
||||||
|
|
||||||
class BadSpawner(MockSpawner):
|
class BadSpawner(MockSpawner):
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def start(self):
|
def start(self):
|
||||||
raise RuntimeError("Split the party")
|
raise RuntimeError("Split the party")
|
||||||
|
|
||||||
|
user = User(orm_user, {
|
||||||
|
'spawner_class': BadSpawner,
|
||||||
|
'config': None,
|
||||||
|
})
|
||||||
|
|
||||||
with pytest.raises(Exception) as exc:
|
with pytest.raises(Exception) as exc:
|
||||||
io_loop.run_sync(lambda : user.spawn(BadSpawner))
|
io_loop.run_sync(user.spawn)
|
||||||
assert user.server is None
|
assert user.server is None
|
||||||
assert not user.running
|
assert not user.running
|
||||||
|
|
||||||
|
@@ -12,15 +12,18 @@ from sqlalchemy import inspect
|
|||||||
from .utils import url_path_join
|
from .utils import url_path_join
|
||||||
|
|
||||||
from . import orm
|
from . import orm
|
||||||
from traitlets import HasTraits, Any
|
from traitlets import HasTraits, Any, Dict
|
||||||
|
from .spawner import LocalProcessSpawner
|
||||||
|
|
||||||
|
|
||||||
class UserDict(dict):
|
class UserDict(dict):
|
||||||
"""Like defaultdict, but for users
|
"""Like defaultdict, but for users
|
||||||
|
|
||||||
Getting by a user id OR an orm.User instance returns a User wrapper around the orm user.
|
Getting by a user id OR an orm.User instance returns a User wrapper around the orm user.
|
||||||
"""
|
"""
|
||||||
def __init__(self, db_factory):
|
def __init__(self, db_factory, settings):
|
||||||
self.db_factory = db_factory
|
self.db_factory = db_factory
|
||||||
|
self.settings = settings
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -32,7 +35,7 @@ class UserDict(dict):
|
|||||||
# users[orm_user] returns User(orm_user)
|
# users[orm_user] returns User(orm_user)
|
||||||
orm_user = key
|
orm_user = key
|
||||||
if orm_user.id not in self:
|
if orm_user.id not in self:
|
||||||
user = self[orm_user.id] = User(orm_user)
|
user = self[orm_user.id] = User(orm_user, self.settings)
|
||||||
return user
|
return user
|
||||||
user = dict.__getitem__(self, orm_user.id)
|
user = dict.__getitem__(self, orm_user.id)
|
||||||
user.db = self.db
|
user.db = self.db
|
||||||
@@ -43,7 +46,7 @@ class UserDict(dict):
|
|||||||
orm_user = self.db.query(orm.User).filter(orm.User.id==id).first()
|
orm_user = self.db.query(orm.User).filter(orm.User.id==id).first()
|
||||||
if orm_user is None:
|
if orm_user is None:
|
||||||
raise KeyError("No such user: %s" % id)
|
raise KeyError("No such user: %s" % id)
|
||||||
user = self[id] = User(orm_user)
|
user = self[id] = User(orm_user, self.settings)
|
||||||
return dict.__getitem__(self, id)
|
return dict.__getitem__(self, id)
|
||||||
else:
|
else:
|
||||||
raise KeyError(repr(key))
|
raise KeyError(repr(key))
|
||||||
@@ -54,6 +57,8 @@ class User(HasTraits):
|
|||||||
def _log_default(self):
|
def _log_default(self):
|
||||||
return app_log
|
return app_log
|
||||||
|
|
||||||
|
settings = Dict()
|
||||||
|
|
||||||
db = Any(allow_none=True)
|
db = Any(allow_none=True)
|
||||||
def _db_default(self):
|
def _db_default(self):
|
||||||
if self.orm_user:
|
if self.orm_user:
|
||||||
@@ -71,9 +76,32 @@ class User(HasTraits):
|
|||||||
spawn_pending = False
|
spawn_pending = False
|
||||||
stop_pending = False
|
stop_pending = False
|
||||||
|
|
||||||
def __init__(self, orm_user, **kwargs):
|
@property
|
||||||
|
def authenticator(self):
|
||||||
|
return self.settings.get('authenticator', None)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def spawner_class(self):
|
||||||
|
return self.settings.get('spawner_class', LocalProcessSpawner)
|
||||||
|
|
||||||
|
def __init__(self, orm_user, settings, **kwargs):
|
||||||
self.orm_user = orm_user
|
self.orm_user = orm_user
|
||||||
|
self.settings = settings
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
hub = self.db.query(orm.Hub).first()
|
||||||
|
|
||||||
|
self.cookie_name = '%s-%s' % (hub.server.cookie_name, quote(self.name, safe=''))
|
||||||
|
self.base_url = url_path_join(
|
||||||
|
self.settings.get('base_url', '/'), 'user', self.escaped_name)
|
||||||
|
|
||||||
|
self.spawner = self.spawner_class(
|
||||||
|
user=self,
|
||||||
|
db=self.db,
|
||||||
|
hub=hub,
|
||||||
|
authenticator=self.authenticator,
|
||||||
|
config=self.settings.get('config'),
|
||||||
|
)
|
||||||
|
|
||||||
# pass get/setattr to ORM user
|
# pass get/setattr to ORM user
|
||||||
|
|
||||||
@@ -95,8 +123,6 @@ class User(HasTraits):
|
|||||||
@property
|
@property
|
||||||
def running(self):
|
def running(self):
|
||||||
"""property for whether a user has a running server"""
|
"""property for whether a user has a running server"""
|
||||||
if self.spawner is None:
|
|
||||||
return False
|
|
||||||
if self.server is None:
|
if self.server is None:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
@@ -107,16 +133,13 @@ class User(HasTraits):
|
|||||||
return quote(self.name, safe='@')
|
return quote(self.name, safe='@')
|
||||||
|
|
||||||
@gen.coroutine
|
@gen.coroutine
|
||||||
def spawn(self, spawner_class, base_url='/', hub=None, config=None,
|
def spawn(self, options=None):
|
||||||
authenticator=None, options=None):
|
|
||||||
"""Start the user's spawner"""
|
"""Start the user's spawner"""
|
||||||
db = self.db
|
db = self.db
|
||||||
if hub is None:
|
|
||||||
hub = db.query(orm.Hub).first()
|
|
||||||
|
|
||||||
self.server = orm.Server(
|
self.server = orm.Server(
|
||||||
cookie_name='%s-%s' % (hub.server.cookie_name, quote(self.name, safe='')),
|
cookie_name=self.cookie_name,
|
||||||
base_url=url_path_join(base_url, 'user', self.escaped_name),
|
base_url=self.base_url,
|
||||||
)
|
)
|
||||||
db.add(self.server)
|
db.add(self.server)
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -124,19 +147,14 @@ class User(HasTraits):
|
|||||||
api_token = self.new_api_token()
|
api_token = self.new_api_token()
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
spawner = self.spawner = spawner_class(
|
spawner = self.spawner
|
||||||
config=config,
|
spawner.user_options = options or {}
|
||||||
user=self,
|
|
||||||
hub=hub,
|
|
||||||
db=db,
|
|
||||||
authenticator=authenticator,
|
|
||||||
user_options=options or {},
|
|
||||||
)
|
|
||||||
# we are starting a new server, make sure it doesn't restore state
|
# we are starting a new server, make sure it doesn't restore state
|
||||||
spawner.clear_state()
|
spawner.clear_state()
|
||||||
spawner.api_token = api_token
|
spawner.api_token = api_token
|
||||||
|
|
||||||
# trigger pre-spawn hook on authenticator
|
# trigger pre-spawn hook on authenticator
|
||||||
|
authenticator = self.authenticator
|
||||||
if (authenticator):
|
if (authenticator):
|
||||||
yield gen.maybe_future(authenticator.pre_spawn_start(self, spawner))
|
yield gen.maybe_future(authenticator.pre_spawn_start(self, spawner))
|
||||||
|
|
||||||
@@ -207,8 +225,6 @@ class User(HasTraits):
|
|||||||
"""
|
"""
|
||||||
self.spawn_pending = False
|
self.spawn_pending = False
|
||||||
spawner = self.spawner
|
spawner = self.spawner
|
||||||
if spawner is None:
|
|
||||||
return
|
|
||||||
self.spawner.stop_polling()
|
self.spawner.stop_polling()
|
||||||
self.stop_pending = True
|
self.stop_pending = True
|
||||||
try:
|
try:
|
||||||
|
Reference in New Issue
Block a user