use traitlets-4.1 .tag(config=True) API

This commit is contained in:
Min RK
2016-03-27 10:29:36 -07:00
parent e4cfe01c4a
commit fa48620076
3 changed files with 131 additions and 134 deletions

View File

@@ -175,66 +175,65 @@ class JupyterHub(Application):
PAMAuthenticator, PAMAuthenticator,
]) ])
config_file = Unicode('jupyterhub_config.py', config=True, config_file = Unicode('jupyterhub_config.py',
help="The config file to load", help="The config file to load",
) ).tag(config=True)
generate_config = Bool(False, config=True, generate_config = Bool(False,
help="Generate default config file", help="Generate default config file",
) ).tag(config=True)
answer_yes = Bool(False, config=True, answer_yes = Bool(False,
help="Answer yes to any questions (e.g. confirm overwrite)" help="Answer yes to any questions (e.g. confirm overwrite)"
) ).tag(config=True)
pid_file = Unicode('', config=True, pid_file = Unicode('',
help="""File to write PID help="""File to write PID
Useful for daemonizing jupyterhub. Useful for daemonizing jupyterhub.
""" """
) ).tag(config=True)
cookie_max_age_days = Float(14, config=True, cookie_max_age_days = Float(14,
help="""Number of days for a login cookie to be valid. help="""Number of days for a login cookie to be valid.
Default is two weeks. Default is two weeks.
""" """
) ).tag(config=True)
last_activity_interval = Integer(300, config=True, last_activity_interval = Integer(300,
help="Interval (in seconds) at which to update last-activity timestamps." help="Interval (in seconds) at which to update last-activity timestamps."
) ).tag(config=True)
proxy_check_interval = Integer(30, config=True, proxy_check_interval = Integer(30,
help="Interval (in seconds) at which to check if the proxy is running." help="Interval (in seconds) at which to check if the proxy is running."
) ).tag(config=True)
data_files_path = Unicode(DATA_FILES_PATH, config=True, data_files_path = Unicode(DATA_FILES_PATH,
help="The location of jupyterhub data files (e.g. /usr/local/share/jupyter/hub)" help="The location of jupyterhub data files (e.g. /usr/local/share/jupyter/hub)"
) ).tag(config=True)
template_paths = List( template_paths = List(
config=True,
help="Paths to search for jinja templates.", help="Paths to search for jinja templates.",
) ).tag(config=True)
def _template_paths_default(self): def _template_paths_default(self):
return [os.path.join(self.data_files_path, 'templates')] return [os.path.join(self.data_files_path, 'templates')]
confirm_no_ssl = Bool(False, config=True, confirm_no_ssl = Bool(False,
help="""Confirm that JupyterHub should be run without SSL. help="""Confirm that JupyterHub should be run without SSL.
This is **NOT RECOMMENDED** unless SSL termination is being handled by another layer. This is **NOT RECOMMENDED** unless SSL termination is being handled by another layer.
""" """
) ).tag(config=True)
ssl_key = Unicode('', config=True, ssl_key = Unicode('',
help="""Path to SSL key file for the public facing interface of the proxy help="""Path to SSL key file for the public facing interface of the proxy
Use with ssl_cert Use with ssl_cert
""" """
) ).tag(config=True)
ssl_cert = Unicode('', config=True, ssl_cert = Unicode('',
help="""Path to SSL certificate file for the public facing interface of the proxy help="""Path to SSL certificate file for the public facing interface of the proxy
Use with ssl_key Use with ssl_key
""" """
) ).tag(config=True)
ip = Unicode('', config=True, ip = Unicode('',
help="The public facing ip of the whole application (the proxy)" help="The public facing ip of the whole application (the proxy)"
) ).tag(config=True)
subdomain_host = Unicode('', config=True, subdomain_host = Unicode('',
help="""Run single-user servers on subdomains of this host. help="""Run single-user servers on subdomains of this host.
This should be the full https://hub.domain.tld[:port] This should be the full https://hub.domain.tld[:port]
@@ -246,42 +245,45 @@ class JupyterHub(Application):
In general, this is most easily achieved with wildcard DNS. In general, this is most easily achieved with wildcard DNS.
When using SSL (i.e. always) this also requires a wildcard SSL certificate. When using SSL (i.e. always) this also requires a wildcard SSL certificate.
""") """
).tag(config=True)
def _subdomain_host_changed(self, name, old, new): def _subdomain_host_changed(self, name, old, new):
if new and '://' not in new: if new and '://' not in new:
# host should include '://' # host should include '://'
# if not specified, assume https: You have to be really explicit about HTTP! # if not specified, assume https: You have to be really explicit about HTTP!
self.subdomain_host = 'https://' + new self.subdomain_host = 'https://' + new
port = Integer(8000, config=True, port = Integer(8000,
help="The public facing port of the proxy" help="The public facing port of the proxy"
) ).tag(config=True)
base_url = URLPrefix('/', config=True, base_url = URLPrefix('/',
help="The base URL of the entire application" help="The base URL of the entire application"
) ).tag(config=True)
logo_file = Unicode('', config=True, logo_file = Unicode('',
help="Specify path to a logo image to override the Jupyter logo in the banner." help="Specify path to a logo image to override the Jupyter logo in the banner."
) ).tag(config=True)
def _logo_file_default(self): def _logo_file_default(self):
return os.path.join(self.data_files_path, 'static', 'images', 'jupyter.png') return os.path.join(self.data_files_path, 'static', 'images', 'jupyter.png')
jinja_environment_options = Dict(config=True, jinja_environment_options = Dict(
help="Supply extra arguments that will be passed to Jinja environment." help="Supply extra arguments that will be passed to Jinja environment."
) ).tag(config=True)
proxy_cmd = Command('configurable-http-proxy', config=True, proxy_cmd = Command('configurable-http-proxy',
help="""The command to start the http proxy. help="""The command to start the http proxy.
Only override if configurable-http-proxy is not on your PATH Only override if configurable-http-proxy is not on your PATH
""" """
) ).tag(config=True)
debug_proxy = Bool(False, config=True, help="show debug output in configurable-http-proxy") debug_proxy = Bool(False,
proxy_auth_token = Unicode(config=True, help="show debug output in configurable-http-proxy"
).tag(config=True)
proxy_auth_token = Unicode(
help="""The Proxy Auth token. help="""The Proxy Auth token.
Loaded from the CONFIGPROXY_AUTH_TOKEN env variable by default. Loaded from the CONFIGPROXY_AUTH_TOKEN env variable by default.
""" """
) ).tag(config=True)
def _proxy_auth_token_default(self): def _proxy_auth_token_default(self):
token = os.environ.get('CONFIGPROXY_AUTH_TOKEN', None) token = os.environ.get('CONFIGPROXY_AUTH_TOKEN', None)
if not token: if not token:
@@ -294,24 +296,24 @@ class JupyterHub(Application):
token = orm.new_token() token = orm.new_token()
return token return token
proxy_api_ip = Unicode('127.0.0.1', config=True, proxy_api_ip = Unicode('127.0.0.1',
help="The ip for the proxy API handlers" help="The ip for the proxy API handlers"
) ).tag(config=True)
proxy_api_port = Integer(config=True, proxy_api_port = Integer(
help="The port for the proxy API handlers" help="The port for the proxy API handlers"
) ).tag(config=True)
def _proxy_api_port_default(self): def _proxy_api_port_default(self):
return self.port + 1 return self.port + 1
hub_port = Integer(8081, config=True, hub_port = Integer(8081,
help="The port for this process" help="The port for this process"
) ).tag(config=True)
hub_ip = Unicode('127.0.0.1', config=True, hub_ip = Unicode('127.0.0.1',
help="The ip for this process" help="The ip for this process"
) ).tag(config=True)
hub_prefix = URLPrefix('/hub/', config=True, hub_prefix = URLPrefix('/hub/',
help="The prefix for the hub server. Must not be '/'" help="The prefix for the hub server. Must not be '/'"
) ).tag(config=True)
def _hub_prefix_default(self): def _hub_prefix_default(self):
return url_path_join(self.base_url, '/hub/') return url_path_join(self.base_url, '/hub/')
@@ -321,19 +323,18 @@ class JupyterHub(Application):
if not new.startswith(self.base_url): if not new.startswith(self.base_url):
self.hub_prefix = url_path_join(self.base_url, new) self.hub_prefix = url_path_join(self.base_url, new)
cookie_secret = Bytes(config=True, env='JPY_COOKIE_SECRET', cookie_secret = Bytes(env='JPY_COOKIE_SECRET',
help="""The cookie secret to use to encrypt cookies. help="""The cookie secret to use to encrypt cookies.
Loaded from the JPY_COOKIE_SECRET env variable by default. Loaded from the JPY_COOKIE_SECRET env variable by default.
""" """
) ).tag(config=True)
cookie_secret_file = Unicode('jupyterhub_cookie_secret', config=True, cookie_secret_file = Unicode('jupyterhub_cookie_secret',
help="""File in which to store the cookie secret.""" help="""File in which to store the cookie secret."""
) ).tag(config=True)
authenticator_class = Type(PAMAuthenticator, Authenticator, authenticator_class = Type(PAMAuthenticator, Authenticator,
config=True,
help="""Class for authenticating users. help="""Class for authenticating users.
This should be a class with the following form: This should be a class with the following form:
@@ -346,7 +347,7 @@ class JupyterHub(Application):
where `handler` is the calling web.RequestHandler, where `handler` is the calling web.RequestHandler,
and `data` is the POST form data from the login page. and `data` is the POST form data from the login page.
""" """
) ).tag(config=True)
authenticator = Instance(Authenticator) authenticator = Instance(Authenticator)
def _authenticator_default(self): def _authenticator_default(self):
@@ -354,33 +355,32 @@ class JupyterHub(Application):
# class for spawning single-user servers # class for spawning single-user servers
spawner_class = Type(LocalProcessSpawner, Spawner, spawner_class = Type(LocalProcessSpawner, Spawner,
config=True,
help="""The class to use for spawning single-user servers. help="""The class to use for spawning single-user servers.
Should be a subclass of Spawner. Should be a subclass of Spawner.
""" """
) ).tag(config=True)
db_url = Unicode('sqlite:///jupyterhub.sqlite', config=True, db_url = Unicode('sqlite:///jupyterhub.sqlite',
help="url for the database. e.g. `sqlite:///jupyterhub.sqlite`" help="url for the database. e.g. `sqlite:///jupyterhub.sqlite`"
) ).tag(config=True)
def _db_url_changed(self, name, old, new): def _db_url_changed(self, name, old, new):
if '://' not in new: if '://' not in new:
# assume sqlite, if given as a plain filename # assume sqlite, if given as a plain filename
self.db_url = 'sqlite:///%s' % new self.db_url = 'sqlite:///%s' % new
db_kwargs = Dict(config=True, db_kwargs = Dict(
help="""Include any kwargs to pass to the database connection. help="""Include any kwargs to pass to the database connection.
See sqlalchemy.create_engine for details. See sqlalchemy.create_engine for details.
""" """
) ).tag(config=True)
reset_db = Bool(False, config=True, reset_db = Bool(False,
help="Purge and reset the database." help="Purge and reset the database."
) ).tag(config=True)
debug_db = Bool(False, config=True, debug_db = Bool(False,
help="log all database transactions. This has A LOT of output" help="log all database transactions. This has A LOT of output"
) ).tag(config=True)
session_factory = Any() session_factory = Any()
users = Instance(UserDict) users = Instance(UserDict)
@@ -388,19 +388,21 @@ class JupyterHub(Application):
assert self.tornado_settings assert self.tornado_settings
return UserDict(db_factory=lambda : self.db, settings=self.tornado_settings) return UserDict(db_factory=lambda : self.db, settings=self.tornado_settings)
admin_access = Bool(False, config=True, admin_access = Bool(False,
help="""Grant admin users permission to access single-user servers. help="""Grant admin users permission to access single-user servers.
Users should be properly informed if this is enabled. Users should be properly informed if this is enabled.
""" """
) ).tag(config=True)
admin_users = Set(config=True, admin_users = Set(
help="""DEPRECATED, use Authenticator.admin_users instead.""" help="""DEPRECATED, use Authenticator.admin_users instead."""
) ).tag(config=True)
tornado_settings = Dict(config=True) tornado_settings = Dict(
help="Extra settings overrides to pass to the tornado application."
).tag(config=True)
cleanup_servers = Bool(True, config=True, cleanup_servers = Bool(True,
help="""Whether to shutdown single-user servers when the Hub shuts down. help="""Whether to shutdown single-user servers when the Hub shuts down.
Disable if you want to be able to teardown the Hub while leaving the single-user servers running. Disable if you want to be able to teardown the Hub while leaving the single-user servers running.
@@ -410,9 +412,9 @@ class JupyterHub(Application):
The Hub should be able to resume from database state. The Hub should be able to resume from database state.
""" """
) ).tag(config=True)
cleanup_proxy = Bool(True, config=True, cleanup_proxy = Bool(True,
help="""Whether to shutdown the proxy when the Hub shuts down. help="""Whether to shutdown the proxy when the Hub shuts down.
Disable if you want to be able to teardown the Hub while leaving the proxy running. Disable if you want to be able to teardown the Hub while leaving the proxy running.
@@ -424,7 +426,7 @@ class JupyterHub(Application):
The Hub should be able to resume from database state. The Hub should be able to resume from database state.
""" """
) ).tag(config=True)
handlers = List() handlers = List()
@@ -445,15 +447,12 @@ class JupyterHub(Application):
return "%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s %(module)s:%(lineno)d]%(end_color)s %(message)s" return "%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s %(module)s:%(lineno)d]%(end_color)s %(message)s"
extra_log_file = Unicode( extra_log_file = Unicode(
"",
config=True,
help="Set a logging.FileHandler on this file." help="Set a logging.FileHandler on this file."
) ).tag(config=True)
extra_log_handlers = List( extra_log_handlers = List(
Instance(logging.Handler), Instance(logging.Handler),
config=True,
help="Extra log handlers to set on JupyterHub logger", help="Extra log handlers to set on JupyterHub logger",
) ).tag(config=True)
def init_logging(self): def init_logging(self):
# This prevents double log messages because tornado use a root logger that # This prevents double log messages because tornado use a root logger that

View File

@@ -29,19 +29,19 @@ class Authenticator(LoggingConfigurable):
""" """
db = Any() db = Any()
admin_users = Set(config=True, admin_users = Set(
help="""set of usernames of admin users help="""set of usernames of admin users
If unspecified, only the user that launches the server will be admin. If unspecified, only the user that launches the server will be admin.
""" """
) ).tag(config=True)
whitelist = Set(config=True, whitelist = Set(
help="""Username whitelist. help="""Username whitelist.
Use this to restrict which users can login. Use this to restrict which users can login.
If empty, allow any user to attempt login. If empty, allow any user to attempt login.
""" """
) ).tag(config=True)
custom_html = Unicode('', custom_html = Unicode('',
help="""HTML login form for custom handlers. help="""HTML login form for custom handlers.
Override in form-based custom authenticators Override in form-based custom authenticators
@@ -55,12 +55,12 @@ class Authenticator(LoggingConfigurable):
""" """
) )
username_pattern = Unicode(config=True, username_pattern = Unicode(
help="""Regular expression pattern for validating usernames. help="""Regular expression pattern for validating usernames.
If not defined: allow any username. If not defined: allow any username.
""" """
) ).tag(config=True)
def _username_pattern_changed(self, name, old, new): def _username_pattern_changed(self, name, old, new):
if not new: if not new:
self.username_regex = None self.username_regex = None
@@ -77,14 +77,14 @@ class Authenticator(LoggingConfigurable):
return True return True
return bool(self.username_regex.match(username)) return bool(self.username_regex.match(username))
username_map = Dict(config=True, username_map = Dict(
help="""Dictionary mapping authenticator usernames to JupyterHub users. help="""Dictionary mapping authenticator usernames to JupyterHub users.
Can be used to map OAuth service names to local users, for instance. Can be used to map OAuth service names to local users, for instance.
Used in normalize_username. Used in normalize_username.
""" """
) ).tag(config=True)
def normalize_username(self, username): def normalize_username(self, username):
"""Normalize a username. """Normalize a username.
@@ -246,12 +246,12 @@ class LocalAuthenticator(Authenticator):
Checks for local users, and can attempt to create them if they exist. Checks for local users, and can attempt to create them if they exist.
""" """
create_system_users = Bool(False, config=True, create_system_users = Bool(False,
help="""If a user is added that doesn't exist on the system, help="""If a user is added that doesn't exist on the system,
should I try to create the system user? should I try to create the system user?
""" """
) ).tag(config=True)
add_user_cmd = Command(config=True, add_user_cmd = Command(
help="""The command to use for creating users as a list of strings. help="""The command to use for creating users as a list of strings.
For each element in the list, the string USERNAME will be replaced with For each element in the list, the string USERNAME will be replaced with
@@ -271,7 +271,7 @@ class LocalAuthenticator(Authenticator):
when the user 'river' is created. when the user 'river' is created.
""" """
) ).tag(config=True)
def _add_user_cmd_default(self): def _add_user_cmd_default(self):
if sys.platform == 'darwin': if sys.platform == 'darwin':
raise ValueError("I don't know how to create users on OS X") raise ValueError("I don't know how to create users on OS X")
@@ -283,9 +283,8 @@ class LocalAuthenticator(Authenticator):
return ['adduser', '-q', '--gecos', '""', '--disabled-password'] return ['adduser', '-q', '--gecos', '""', '--disabled-password']
group_whitelist = Set( group_whitelist = Set(
config=True,
help="Automatically whitelist anyone in this group.", help="Automatically whitelist anyone in this group.",
) ).tag(config=True)
def _group_whitelist_changed(self, name, old, new): def _group_whitelist_changed(self, name, old, new):
if self.whitelist: if self.whitelist:
@@ -351,13 +350,13 @@ class LocalAuthenticator(Authenticator):
class PAMAuthenticator(LocalAuthenticator): class PAMAuthenticator(LocalAuthenticator):
"""Authenticate local Linux/UNIX users with PAM""" """Authenticate local Linux/UNIX users with PAM"""
encoding = Unicode('utf8', config=True, encoding = Unicode('utf8',
help="""The encoding to use for PAM""" help="""The encoding to use for PAM"""
) ).tag(config=True)
service = Unicode('login', config=True, service = Unicode('login',
help="""The PAM service to use for authentication.""" help="""The PAM service to use for authentication."""
) ).tag(config=True)
open_sessions = Bool(True, config=True, open_sessions = Bool(True,
help="""Whether to open PAM sessions when spawners are started. help="""Whether to open PAM sessions when spawners are started.
This may trigger things like mounting shared filsystems, This may trigger things like mounting shared filsystems,
@@ -368,7 +367,7 @@ class PAMAuthenticator(LocalAuthenticator):
c.PAMAuthenticator.open_sessions = False c.PAMAuthenticator.open_sessions = False
""" """
) ).tag(config=True)
@gen.coroutine @gen.coroutine
def authenticate(self, handler, data): def authenticate(self, handler, data):

View File

@@ -41,39 +41,38 @@ class Spawner(LoggingConfigurable):
hub = Any() hub = Any()
authenticator = Any() authenticator = Any()
api_token = Unicode() api_token = Unicode()
ip = Unicode('127.0.0.1', config=True, ip = Unicode('127.0.0.1',
help="The IP address (or hostname) the single-user server should listen on" help="The IP address (or hostname) the single-user server should listen on"
) ).tag(config=True)
start_timeout = Integer(60, config=True, start_timeout = Integer(60,
help="""Timeout (in seconds) before giving up on the spawner. help="""Timeout (in seconds) before giving up on the spawner.
This is the timeout for start to return, not the timeout for the server to respond. This is the timeout for start to return, not the timeout for the server to respond.
Callers of spawner.start will assume that startup has failed if it takes longer than this. Callers of spawner.start will assume that startup has failed if it takes longer than this.
start should return when the server process is started and its location is known. start should return when the server process is started and its location is known.
""" """
) ).tag(config=True)
http_timeout = Integer( http_timeout = Integer(30,
30, config=True,
help="""Timeout (in seconds) before giving up on a spawned HTTP server help="""Timeout (in seconds) before giving up on a spawned HTTP server
Once a server has successfully been spawned, this is the amount of time Once a server has successfully been spawned, this is the amount of time
we wait before assuming that the server is unable to accept we wait before assuming that the server is unable to accept
connections. connections.
""" """
) ).tag(config=True)
poll_interval = Integer(30, config=True, poll_interval = Integer(30,
help="""Interval (in seconds) on which to poll the spawner.""" help="""Interval (in seconds) on which to poll the spawner."""
) ).tag(config=True)
_callbacks = List() _callbacks = List()
_poll_callback = Any() _poll_callback = Any()
debug = Bool(False, config=True, debug = Bool(False,
help="Enable debug-logging of the single-user server" help="Enable debug-logging of the single-user server"
) ).tag(config=True)
options_form = Unicode("", config=True, help=""" options_form = Unicode("", help="""
An HTML form for options a user can specify on launching their server. An HTML form for options a user can specify on launching their server.
The surrounding `<form>` element and the submit button are already provided. The surrounding `<form>` element and the submit button are already provided.
@@ -87,7 +86,7 @@ class Spawner(LoggingConfigurable):
<option value="A">The letter A</option> <option value="A">The letter A</option>
<option value="B">The letter B</option> <option value="B">The letter B</option>
</select> </select>
""") """).tag(config=True)
def options_from_form(self, form_data): def options_from_form(self, form_data):
"""Interpret HTTP form data """Interpret HTTP form data
@@ -113,35 +112,35 @@ class Spawner(LoggingConfigurable):
'VIRTUAL_ENV', 'VIRTUAL_ENV',
'LANG', 'LANG',
'LC_ALL', 'LC_ALL',
], config=True, ],
help="Whitelist of environment variables for the subprocess to inherit" help="Whitelist of environment variables for the subprocess to inherit"
) ).tag(config=True)
env = Dict(help="""Deprecated: use Spawner.get_env or Spawner.environment env = Dict(help="""Deprecated: use Spawner.get_env or Spawner.environment
- extend Spawner.get_env for adding required env in Spawner subclasses - extend Spawner.get_env for adding required env in Spawner subclasses
- Spawner.environment for config-specified env - Spawner.environment for config-specified env
""") """)
environment = Dict(config=True, environment = Dict(
help="Environment variables to load for the Spawner." help="Environment variables to load for the Spawner."
) ).tag(config=True)
cmd = Command(['jupyterhub-singleuser'], config=True, cmd = Command(['jupyterhub-singleuser'],
help="""The command used for starting notebooks.""" help="""The command used for starting notebooks."""
) ).tag(config=True)
args = List(Unicode, config=True, args = List(Unicode,
help="""Extra arguments to be passed to the single-user server""" help="""Extra arguments to be passed to the single-user server"""
) ).tag(config=True)
notebook_dir = Unicode('', config=True, notebook_dir = Unicode('',
help="""The notebook directory for the single-user server help="""The notebook directory for the single-user server
`~` will be expanded to the user's home directory `~` will be expanded to the user's home directory
`%U` will be expanded to the user's username `%U` will be expanded to the user's username
""" """
) ).tag(config=True)
default_url = Unicode('', config=True, default_url = Unicode('',
help="""The default URL for the single-user server. help="""The default URL for the single-user server.
Can be used in conjunction with --notebook-dir=/ to enable Can be used in conjunction with --notebook-dir=/ to enable
@@ -150,15 +149,15 @@ class Spawner(LoggingConfigurable):
`%U` will be expanded to the user's username `%U` will be expanded to the user's username
""" """
) ).tag(config=True)
disable_user_config = Bool(False, config=True, disable_user_config = Bool(False,
help="""Disable per-user configuration of single-user servers. help="""Disable per-user configuration of single-user servers.
This prevents any config in users' $HOME directories This prevents any config in users' $HOME directories
from having an effect on their server. from having an effect on their server.
""" """
) ).tag(config=True)
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(Spawner, self).__init__(**kwargs) super(Spawner, self).__init__(**kwargs)
@@ -385,15 +384,15 @@ class LocalProcessSpawner(Spawner):
This is the default spawner for JupyterHub. This is the default spawner for JupyterHub.
""" """
INTERRUPT_TIMEOUT = Integer(10, config=True, INTERRUPT_TIMEOUT = Integer(10,
help="Seconds to wait for process to halt after SIGINT before proceeding to SIGTERM" help="Seconds to wait for process to halt after SIGINT before proceeding to SIGTERM"
) ).tag(config=True)
TERM_TIMEOUT = Integer(5, config=True, TERM_TIMEOUT = Integer(5,
help="Seconds to wait for process to halt after SIGTERM before proceeding to SIGKILL" help="Seconds to wait for process to halt after SIGTERM before proceeding to SIGKILL"
) ).tag(config=True)
KILL_TIMEOUT = Integer(5, config=True, KILL_TIMEOUT = Integer(5,
help="Seconds to wait for process to halt after SIGKILL before giving up" help="Seconds to wait for process to halt after SIGKILL before giving up"
) ).tag(config=True)
proc = Instance(Popen, allow_none=True) proc = Instance(Popen, allow_none=True)
pid = Integer(0) pid = Integer(0)