Add more docs for spawner base class

This commit is contained in:
YuviPanda
2016-11-29 16:25:15 +08:00
parent 7dbe2425b8
commit 4970fe0a1c

View File

@@ -351,29 +351,31 @@ class Spawner(LoggingConfigurable):
self.load_state(self.user.state) self.load_state(self.user.state)
def load_state(self, state): def load_state(self, state):
"""load state from the database """
Restore state of spawner from database.
This is the extensible part of state. Called for each user's spawner after the hub process restarts.
Override in a subclass if there is state to load. `state` is a dict that'll contain the value returned by `get_state` of
Should call `super`. the spawner, or {} if the spawner hasn't persisted any state yet.
See Also Override in subclasses to restore any extra state that is needed to track
-------- the single-user server for that user. Subclasses should call super().
get_state, clear_state
""" """
pass pass
def get_state(self): def get_state(self):
"""store the state necessary for load_state """
Save state of spawner into database.
A black box of extra state for custom spawners. A black box of extra state for custom spawners. The returned value of this is
Subclasses should call `super`. passed to `load_state`.
Subclasses should call `super().get_state()`, augment the state returned from
there, and return that state.
Returns Returns
------- -------
state: dict state: dict
a JSONable dict of state a JSONable dict of state
""" """
@@ -381,20 +383,25 @@ class Spawner(LoggingConfigurable):
return state return state
def clear_state(self): def clear_state(self):
"""clear any state that should be cleared when the process stops """
Clear any state that should be cleared when the single-user server stops.
State that should be preserved across server instances should not be cleared. State that should be preserved across single-user server instances should not be cleared.
Subclasses should call super, to ensure that state is properly cleared. Subclasses should call super, to ensure that state is properly cleared.
""" """
self.api_token = '' self.api_token = ''
def get_env(self): def get_env(self):
"""Return the environment dict to use for the Spawner. """
Return the environment dict to use for the Spawner.
This applies things like `env_keep`, anything defined in `Spawner.environment`, This applies things like `env_keep`, anything defined in `Spawner.environment`,
and adds the API token to the env. and adds the API token to the env.
When overriding in subclasses, subclasses must call `super().get_env()`, extend the
returned dict and return it.
Use this to access the env in Spawner.start to allow extension in subclasses. Use this to access the env in Spawner.start to allow extension in subclasses.
""" """
env = {} env = {}
@@ -434,7 +441,8 @@ class Spawner(LoggingConfigurable):
return env return env
def template_namespace(self): def template_namespace(self):
"""Return the template namespace for format-string formatting. """
Return the template namespace for format-string formatting.
Currently used on default_url and notebook_dir. Currently used on default_url and notebook_dir.
@@ -457,7 +465,8 @@ class Spawner(LoggingConfigurable):
return d return d
def format_string(self, s): def format_string(self, s):
"""Render a Python format string """
Render a Python format string.
Uses :meth:`Spawner.template_namespace` to populate format namespace. Uses :meth:`Spawner.template_namespace` to populate format namespace.
@@ -472,7 +481,11 @@ class Spawner(LoggingConfigurable):
return s.format(**self.template_namespace()) return s.format(**self.template_namespace())
def get_args(self): def get_args(self):
"""Return the arguments to be passed after self.cmd""" """
Return the arguments to be passed after self.cmd.
Doesn't expect shell expansion to happen.
"""
args = [ args = [
'--user="%s"' % self.user.name, '--user="%s"' % self.user.name,
'--cookie-name="%s"' % self.user.server.cookie_name, '--cookie-name="%s"' % self.user.server.cookie_name,
@@ -506,10 +519,10 @@ class Spawner(LoggingConfigurable):
@gen.coroutine @gen.coroutine
def start(self): def start(self):
"""Start the single-user server """
Start the single-user server.
Returns: Returns:
(ip, port): the ip, port where the Hub can connect to the server. (ip, port): the ip, port where the Hub can connect to the server.
.. versionchanged:: 0.7 .. versionchanged:: 0.7
@@ -519,15 +532,22 @@ class Spawner(LoggingConfigurable):
@gen.coroutine @gen.coroutine
def stop(self, now=False): def stop(self, now=False):
"""Stop the single-user process""" """
Stop the single-user server.
If `now` is set to `False`, do not wait for the server to stop. Otherwise, wait for
the server to stop before returning.
Must be a Torando coroutine.
"""
raise NotImplementedError("Override in subclass. Must be a Tornado gen.coroutine.") raise NotImplementedError("Override in subclass. Must be a Tornado gen.coroutine.")
@gen.coroutine @gen.coroutine
def poll(self): def poll(self):
"""Check if the single-user process is running """
Check if the single-user process is running
returns: returns:
None, if single-user process is running. None, if single-user process is running.
Exit status (0 if unknown), if it is not running. Exit status (0 if unknown), if it is not running.
@@ -551,9 +571,8 @@ class Spawner(LoggingConfigurable):
raise NotImplementedError("Override in subclass. Must be a Tornado gen.coroutine.") raise NotImplementedError("Override in subclass. Must be a Tornado gen.coroutine.")
def add_poll_callback(self, callback, *args, **kwargs): def add_poll_callback(self, callback, *args, **kwargs):
"""add a callback to fire when the subprocess stops """
Add a callback to fire when the single-user server stops.
as noticed by periodic poll_and_notify()
""" """
if args or kwargs: if args or kwargs:
cb = callback cb = callback
@@ -561,17 +580,18 @@ class Spawner(LoggingConfigurable):
self._callbacks.append(callback) self._callbacks.append(callback)
def stop_polling(self): def stop_polling(self):
"""stop the periodic poll""" """
Stop polling for single-user server's running state.
"""
if self._poll_callback: if self._poll_callback:
self._poll_callback.stop() self._poll_callback.stop()
self._poll_callback = None self._poll_callback = None
def start_polling(self): def start_polling(self):
"""Start polling periodically """
Start polling periodically for single-user server's running state.
callbacks registered via `add_poll_callback` will fire
if/when the process stops.
Callbacks registered via `add_poll_callback` will fire if/when the server stops.
Explicit termination via the stop method will not trigger the callbacks. Explicit termination via the stop method will not trigger the callbacks.
""" """
if self.poll_interval <= 0: if self.poll_interval <= 0:
@@ -590,8 +610,8 @@ class Spawner(LoggingConfigurable):
@gen.coroutine @gen.coroutine
def poll_and_notify(self): def poll_and_notify(self):
"""Used as a callback to periodically poll the process, """
and notify any watchers Used as a callback to periodically poll the process and notify any watchers
""" """
status = yield self.poll() status = yield self.poll()
if status is None: if status is None:
@@ -610,7 +630,9 @@ class Spawner(LoggingConfigurable):
death_interval = Float(0.1) death_interval = Float(0.1)
@gen.coroutine @gen.coroutine
def wait_for_death(self, timeout=10): def wait_for_death(self, timeout=10):
"""wait for the process to die, up to timeout seconds""" """
Wait for the single-user server to die, up to timeout seconds
"""
for i in range(int(timeout / self.death_interval)): for i in range(int(timeout / self.death_interval)):
status = yield self.poll() status = yield self.poll()
if status is not None: if status is not None:
@@ -618,8 +640,13 @@ class Spawner(LoggingConfigurable):
else: else:
yield gen.sleep(self.death_interval) yield gen.sleep(self.death_interval)
def _try_setcwd(path): def _try_setcwd(path):
"""Try to set CWD, walking up and ultimately falling back to a temp dir""" """
Try to set CWD to path, walking up until a valid directory is found.
If no valid directory is found, a temp directory is created and cwd is set to that.
"""
while path != '/': while path != '/':
try: try:
os.chdir(path) os.chdir(path)
@@ -635,7 +662,12 @@ def _try_setcwd(path):
def set_user_setuid(username): def set_user_setuid(username):
"""return a preexec_fn for setting the user (via setuid) of a spawned process""" """
Return a preexec_fn for spawning a single-user server as a particular user.
Returned preexec_fn will set uid/gid, and attempt to chdir to the target user's
homedirectory.
"""
user = pwd.getpwnam(username) user = pwd.getpwnam(username)
uid = user.pw_uid uid = user.pw_uid
gid = user.pw_gid gid = user.pw_gid
@@ -643,7 +675,11 @@ def set_user_setuid(username):
gids = [ g.gr_gid for g in grp.getgrall() if username in g.gr_mem ] gids = [ g.gr_gid for g in grp.getgrall() if username in g.gr_mem ]
def preexec(): def preexec():
# set the user and group """
Set uid/gid of current process. Executed after fork but before exec by python.
Also try to chdir to the user's home directory.
"""
os.setgid(gid) os.setgid(gid)
try: try:
os.setgroups(gids) os.setgroups(gids)