Refactored the configuration system.

Some preliminary work on refactoring the configuration system to
allow for some backwards compatibility with Python 2.4.
This commit is contained in:
benoitc 2010-06-18 00:51:27 +02:00 committed by Paul J. Davis
parent a28da3bd70
commit d5f2b5358a
3 changed files with 284 additions and 233 deletions

View File

@ -332,7 +332,7 @@ temporary directory.</p>
<li><tt class="docutils literal"><span class="pre">--log-level</span> LEVEL</tt></li> <li><tt class="docutils literal"><span class="pre">--log-level</span> LEVEL</tt></li>
<li><tt class="docutils literal">info</tt></li> <li><tt class="docutils literal">info</tt></li>
</ul> </ul>
<p>The granularity of log output</p> <p>The granularity of log outputs.</p>
<p>Valid level names are:</p> <p>Valid level names are:</p>
<ul class="simple"> <ul class="simple">
<li>debug</li> <li>debug</li>

View File

@ -3,8 +3,6 @@
# This file is part of gunicorn released under the MIT license. # This file is part of gunicorn released under the MIT license.
# See the NOTICE for more information. # See the NOTICE for more information.
from __future__ import with_statement
import copy import copy
import grp import grp
import inspect import inspect
@ -19,10 +17,19 @@ from gunicorn import util
KNOWN_SETTINGS = [] KNOWN_SETTINGS = []
def wrap_method(func):
def _wrapped(instance, *args, **kwargs):
return func(*args, **kwargs)
return _wrapped
class Config(object): class Config(object):
def __init__(self, usage=None): def __init__(self, usage=None):
self.settings = dict((s.name, s.copy()) for s in KNOWN_SETTINGS) self.settings = {}
for s in KNOWN_SETTINGS:
setting = s()
self.settings[setting.name] = setting.copy()
self.usage = usage self.usage = usage
def __getattr__(self, name): def __getattr__(self, name):
@ -102,34 +109,44 @@ class Config(object):
else: else:
return self.settings['default_proc_name'] return self.settings['default_proc_name']
class Setting(object): class SettingMeta(type):
def __init__(self, name): def __new__(cls, name, bases, attrs):
self.name = name super_new = super(SettingMeta, cls).__new__
self.value = None parents = [b for b in bases if isinstance(b, SettingMeta)]
self.section = None if not parents:
self.order = len(KNOWN_SETTINGS) return super_new(cls, name, bases, attrs)
self.cli = None
self.validator = None
self.type = None
self.meta = None
self.action = None
self.default = None
self.short = None
self.desc = None
def __enter__(self): attrs["order"] = len(KNOWN_SETTINGS)
return self attrs["validator"] = wrap_method(attrs["validator"])
def __exit__(self, exc_type, exc_val, traceback): new_class = super_new(cls, name, bases, attrs)
if exc_type is None: new_class.fmt_desc(attrs.get("desc", ""))
KNOWN_SETTINGS.append(self) KNOWN_SETTINGS.append(new_class)
if self.default is not None: return new_class
self.set(self.default)
def fmt_desc(self, desc): def fmt_desc(cls, desc):
desc = textwrap.dedent(desc).strip() desc = textwrap.dedent(desc).strip()
self.desc = desc setattr(cls, "desc", desc)
self.short = desc.splitlines()[0] setattr(cls, "short", desc.splitlines()[0])
class Setting(object):
__metaclass__ = SettingMeta
name = None
value = None
section = None
cli = None
validator = None
type = None
meta = None
action = None
default = None
short = None
desc = None
def __init__(self):
if self.default is not None:
self.set(self.default)
def add_option(self, parser): def add_option(self, parser):
if not self.cli: if not self.cli:
@ -175,7 +192,6 @@ def validate_pos_int(val):
else: else:
# Booleans are ints! # Booleans are ints!
val = int(val) val = int(val)
#print "Setting: %s" % val
if val < 0: if val < 0:
raise ValueError("Value must be positive: %s" % val) raise ValueError("Value must be positive: %s" % val)
return val return val
@ -196,40 +212,44 @@ def validate_callable(arity):
return val return val
return _validate_callable return _validate_callable
with Setting("config") as s:
s.section = "Config File" class ConfigFile(Setting):
s.cli = ["-c", "--config"] name = "config"
s.meta = "FILE" section = "Config File"
s.validator = validate_string cli = ["-c", "--config"]
s.default = None meta = "FILE"
s.fmt_desc("""\ validator = validate_string
default = None
desc = """\
The path to a Gunicorn config file. The path to a Gunicorn config file.
Only has an effect when specified on the command line or as part of an Only has an effect when specified on the command line or as part of an
application specific configuration. application specific configuration.
""") """
with Setting("bind") as s: class Bind(Setting):
s.section = "Server Socket" name = "bind"
s.cli = ["-b", "--bind"] section = "Server Socket"
s.meta = "ADDRESS" cli = ["-b", "--bind"]
s.validator = validate_string meta = "ADDRESS"
s.default = "127.0.0.1:8000" validator = validate_string
s.fmt_desc("""\ default = "127.0.0.1:8000"
desc = """\
The socket to bind. The socket to bind.
A string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'. An IP is a valid A string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'. An IP is a valid
HOST. HOST.
""") """
with Setting("backlog") as s: class Backlog(Setting):
s.section = "Server Socket" name = "backlog"
s.cli = ["--backlog"] section = "Server Socket"
s.meta = "INT" cli = ["--backlog"]
s.validator = validate_pos_int meta = "INT"
s.type = "int" validator = validate_pos_int
s.default = 2048 type = "int"
s.fmt_desc("""\ default = 2048
desc = """\
The maximum number of pending connections. The maximum number of pending connections.
This refers to the number of clients that can be waiting to be served. This refers to the number of clients that can be waiting to be served.
@ -238,30 +258,32 @@ with Setting("backlog") as s:
load. load.
Must be a positive integer. Generally set in the 64-2048 range. Must be a positive integer. Generally set in the 64-2048 range.
""") """
with Setting("workers") as s: class Workers(Setting):
s.section = "Worker Processes" name = "workers"
s.cli = ["-w", "--workers"] section = "Worker Processes"
s.meta = "INT" cli = ["-w", "--workers"]
s.validator = validate_pos_int meta = "INT"
s.type = "int" validator = validate_pos_int
s.default = 1 type = "int"
s.fmt_desc("""\ default = 1
desc = """\
The number of worker process for handling requests. The number of worker process for handling requests.
A positive integer generally in the 2-4 x $(NUM_CORES) range. You'll A positive integer generally in the 2-4 x $(NUM_CORES) range. You'll
want to vary this a bit to find the best for your particular want to vary this a bit to find the best for your particular
application's work load. application's work load.
""") """
with Setting("worker_class") as s: class WorkerClass(Setting):
s.section = "Worker Processes" name = "worker_class"
s.cli = ["-k", "--worker-class"] section = "Worker Processes"
s.meta = "STRING" cli = ["-k", "--worker-class"]
s.validator = validate_string meta = "STRING"
s.default = "egg:gunicorn#sync" validator = validate_string
s.fmt_desc("""\ default = "egg:gunicorn#sync"
desc = """\
The type of workers to use. The type of workers to use.
The default async class should handle most 'normal' types of work loads. The default async class should handle most 'normal' types of work loads.
@ -278,151 +300,162 @@ with Setting("worker_class") as s:
* ``egg:gunicorn#eventlet`` - Requires eventlet >= 0.9.7 * ``egg:gunicorn#eventlet`` - Requires eventlet >= 0.9.7
* ``egg:gunicorn#gevent`` - Requires gevent >= 0.12.2 (?) * ``egg:gunicorn#gevent`` - Requires gevent >= 0.12.2 (?)
* ``egg:gunicorn#tornado`` - Requires tornado >= 0.2 * ``egg:gunicorn#tornado`` - Requires tornado >= 0.2
""") """
with Setting("worker_connections") as s: class WorkerConnections(Setting):
s.section = "Worker Processes" name = "worker_connections"
s.cli = ["--worker-connections"] section = "Worker Processes"
s.meta = "INT" cli = ["--worker-connections"]
s.validator = validate_pos_int meta = "INT"
s.type = "int" validator = validate_pos_int
s.default = 1000 type = "int"
s.fmt_desc("""\ default = 1000
desc = """\
The maximum number of simultaneous clients. The maximum number of simultaneous clients.
This setting only affects the Eventlet and Gevent worker types. This setting only affects the Eventlet and Gevent worker types.
""") """
with Setting("timeout") as s: class Timeout(Setting):
s.section = "Worker Processes" name = "timeout"
s.cli = ["-t", "--timeout"] section = "Worker Processes"
s.meta = "INT" cli = ["-t", "--timeout"]
s.validator = validate_pos_int meta = "INT"
s.type = "int" validator = validate_pos_int
s.default = 30 type = "int"
s.fmt_desc("""\ default = 30
desc = """\
Workers silent for more than this many seconds are killed and restarted. Workers silent for more than this many seconds are killed and restarted.
Generally set to thirty seconds. Only set this noticeably higher if Generally set to thirty seconds. Only set this noticeably higher if
you're sure of the repercussions for sync workers. For the non sync you're sure of the repercussions for sync workers. For the non sync
workers it just means that the worker process is still communicating and workers it just means that the worker process is still communicating and
is not tied to the length of time required to handle a single request. is not tied to the length of time required to handle a single request.
""") """
with Setting("keepalive") as s: class Keepalive(Setting):
s.section = "Worker Processes" name = "keepalive"
s.cli = ["--keep-alive"] section = "Worker Processes"
s.meta = "INT" cli = ["--keep-alive"]
s.validator = validate_pos_int meta = "INT"
s.type = "int" validator = validate_pos_int
s.default = 2 type = "int"
s.fmt_desc("""\ default = 2
desc = """\
The number of seconds to wait for requests on a Keep-Alive connection. The number of seconds to wait for requests on a Keep-Alive connection.
Generally set in the 1-5 seconds range. Generally set in the 1-5 seconds range.
""") """
with Setting("debug") as s: class Debug(Setting):
s.section = "Debugging" name = "debug"
s.cli = ["--debug"] section = "Debugging"
s.validator = validate_bool cli = ["--debug"]
s.action = "store_true" validator = validate_bool
s.default = False action = "store_true"
s.fmt_desc("""\ default = False
desc = """\
Turn on debugging in the server. Turn on debugging in the server.
This limits the number of worker processes to 1 and changes some error This limits the number of worker processes to 1 and changes some error
handling that's sent to clients. handling that's sent to clients.
""") """
with Setting("spew") as s: class Spew(Setting):
s.section = "Debugging" name = "spew"
s.cli = ["--spew"] section = "Debugging"
s.validator = validate_bool cli = ["--spew"]
s.action = "store_true" validator = validate_bool
s.default = False action = "store_true"
s.fmt_desc("""\ default = False
desc = """\
Install a trace function that spews every line executed by the server. Install a trace function that spews every line executed by the server.
This is the nuclear option. This is the nuclear option.
""") """
with Setting("preload_app") as s: class PreloadApp(Setting):
s.section = "Server Mechanics" name = "preload_app"
s.cli = ["--preload"] section = "Server Mechanics"
s.validator = validate_bool cli = ["--preload"]
s.action = "store_true" validator = validate_bool
s.default = False action = "store_true"
s.fmt_desc("""\ default = False
desc = """\
Load application code before the worker processes are forked. Load application code before the worker processes are forked.
By preloading an application you can save some RAM resources as well as By preloading an application you can save some RAM resources as well as
speed up server boot times. Although, if you defer application loading speed up server boot times. Although, if you defer application loading
to each worker process, you can reload your application code easily by to each worker process, you can reload your application code easily by
restarting workers. restarting workers.
""") """
with Setting("daemon") as s: class Daemon(Setting):
s.section = "Server Mechanics" name = "daemon"
s.cli = ["-D", "--daemon"] section = "Server Mechanics"
s.validator = validate_bool cli = ["-D", "--daemon"]
s.action = "store_true" validator = validate_bool
s.default = False action = "store_true"
s.fmt_desc("""\ default = False
desc = """\
Daemonize the Gunicorn process. Daemonize the Gunicorn process.
Detaches the server from the controlling terminal and enters the Detaches the server from the controlling terminal and enters the
background. background.
""") """
with Setting("pidfile") as s: class Pidfile(Setting):
s.section = "Server Mechanics" name = "pidfile"
s.cli = ["-p", "--pid"] section = "Server Mechanics"
s.meta = "FILE" cli = ["-p", "--pid"]
s.validator = validate_string meta = "FILE"
s.default = None validator = validate_string
s.fmt_desc("""\ default = None
desc = """\
A filename to use for the PID file. A filename to use for the PID file.
If not set, no PID file will be written. If not set, no PID file will be written.
""") """
with Setting("user") as s: class User(Setting):
s.section = "Server Mechanics" name = "user"
s.cli = ["-u", "--user"] section = "Server Mechanics"
s.meta = "USER" cli = ["-u", "--user"]
s.validator = validate_string meta = "USER"
s.default = None validator = validate_string
s.fmt_desc("""\ default = None
desc = """\
Switch worker processes to run as this user. Switch worker processes to run as this user.
A valid user id (as an integer) or the name of a user that can be A valid user id (as an integer) or the name of a user that can be
retrieved with a call to pwd.getpwnam(value) or None to not change retrieved with a call to pwd.getpwnam(value) or None to not change
the worker process user. the worker process user.
""") """
with Setting("group") as s: class Group(Setting):
s.section = "Server Mechanics" name = "group"
s.cli = ["-g", "--group"] section = "Server Mechanics"
s.meta = "GROUP" cli = ["-g", "--group"]
s.validator = validate_string meta = "GROUP"
s.default = None validator = validate_string
s.fmt_desc("""\ default = None
desc = """\
Switch worker process to run as this group. Switch worker process to run as this group.
A valid group id (as an integer) or the name of a user that can be A valid group id (as an integer) or the name of a user that can be
retrieved with a call to pwd.getgrnam(value) or None to not change retrieved with a call to pwd.getgrnam(value) or None to not change
the worker processes group. the worker processes group.
""") """
with Setting("umask") as s: class Umask(Setting):
s.section = "Server Mechanics" name = "umask"
s.cli = ["-m", "--umask"] section = "Server Mechanics"
s.meta = "INT" cli = ["-m", "--umask"]
s.validator = validate_pos_int meta = "INT"
s.type = "int" validator = validate_pos_int
s.default = 0 type = "int"
s.fmt_desc("""\ default = 0
desc = """\
A bit mask for the file mode on files written by Gunicorn. A bit mask for the file mode on files written by Gunicorn.
Note that this affects unix socket permissions. Note that this affects unix socket permissions.
@ -430,14 +463,15 @@ with Setting("umask") as s:
A valid value for the os.umask(mode) call or a string compatible with A valid value for the os.umask(mode) call or a string compatible with
int(value, 0) (0 means Python guesses the base, so values like "0", int(value, 0) (0 means Python guesses the base, so values like "0",
"0xFF", "0022" are valid for decimal, hex, and octal representations) "0xFF", "0022" are valid for decimal, hex, and octal representations)
""") """
with Setting("tmp_upload_dir") as s: class TmpUploadDir(Setting):
s.section = "Server Mechanics" name = "tmp_upload_dir"
s.meta = "DIR" section = "Server Mechanics"
s.validator = validate_string meta = "DIR"
s.default = None validator = validate_string
s.fmt_desc("""\ default = None
desc = """\
Directory to store temporary request data as they are read. Directory to store temporary request data as they are read.
This may disappear in the near future. This may disappear in the near future.
@ -445,28 +479,30 @@ with Setting("tmp_upload_dir") as s:
This path should be writable by the process permissions set for Gunicorn This path should be writable by the process permissions set for Gunicorn
workers. If not specified, Gunicorn will choose a system generated workers. If not specified, Gunicorn will choose a system generated
temporary directory. temporary directory.
""") """
with Setting("logfile") as s: class Logfile(Setting):
s.section = "Logging" name = "logfile"
s.cli = ["--log-file"] section = "Logging"
s.meta = "FILE" cli = ["--log-file"]
s.validator = validate_string meta = "FILE"
s.default = "-" validator = validate_string
s.fmt_desc("""\ default = "-"
desc = """\
The log file to write to. The log file to write to.
"-" means log to stdout. "-" means log to stdout.
""") """
with Setting("loglevel") as s: class Loglevel(Setting):
s.section = "Logging" name = "loglevel"
s.cli = ["--log-level"] section = "Logging"
s.meta = "LEVEL" cli = ["--log-level"]
s.validator = validate_string meta = "LEVEL"
s.default = "info" validator = validate_string
s.fmt_desc("""\ default = "info"
The granularity of log output desc = """\
The granularity of log outputs.
Valid level names are: Valid level names are:
@ -475,15 +511,16 @@ with Setting("loglevel") as s:
* warning * warning
* error * error
* critical * critical
""") """
with Setting("proc_name") as s: class Procname(Setting):
s.section = "Process Naming" name = "proc_name"
s.cli = ["-n", "--name"] section = "Process Naming"
s.meta = "STRING" cli = ["-n", "--name"]
s.validator = validate_string meta = "STRING"
s.default = "gunicorn" validator = validate_string
s.fmt_desc("""\ default = "gunicorn"
desc = """\
A base to use with setproctitle for process naming. A base to use with setproctitle for process naming.
This affects things like ``ps`` and ``top``. If you're going to be This affects things like ``ps`` and ``top``. If you're going to be
@ -492,67 +529,81 @@ with Setting("proc_name") as s:
module. module.
It defaults to 'gunicorn'. It defaults to 'gunicorn'.
""") """
with Setting("default_proc_name") as s: class DefaultProcName(Setting):
s.section = "Process Naming" name = "default_proc_name"
s.validator = validate_string section = "Process Naming"
s.default = "gunicorn" validator = validate_string
s.fmt_desc("""\ default = "gunicorn"
desc = """\
Internal setting that is adjusted for each type of application. Internal setting that is adjusted for each type of application.
""") """
with Setting("pre_fork") as s: class Prefork(Setting):
s.section = "Server Hooks" name = "pre_fork"
s.validator = validate_callable(2) section = "Server Hooks"
s.type = "callable" validator = validate_callable(2)
type = "callable"
def def_pre_fork(server, worker): def def_pre_fork(server, worker):
pass pass
s.default = def_pre_fork def_pre_fork = staticmethod(def_pre_fork)
s.fmt_desc("""\ default = def_pre_fork
desc = """\
Called just before a worker is forked. Called just before a worker is forked.
The callable needs to accept two instance variables for the Arbiter and The callable needs to accept two instance variables for the Arbiter and
new Worker. new Worker.
""") """
with Setting("post_fork") as s:
s.section = "Server Hooks" class Postfork(Setting):
s.validator = validate_callable(2) name = "post_fork"
s.type = "callable" section = "Server Hooks"
validator = validate_callable(2)
type = "callable"
def def_post_fork(server, worker): def def_post_fork(server, worker):
server.log.info("Worker spawned (pid: %s)" % worker.pid) server.log.info("Worker spawned (pid: %s)" % worker.pid)
s.default = def_post_fork def_post_fork = staticmethod(def_post_fork)
s.fmt_desc("""\ default = def_post_fork
desc = """\
Called just after a worker has been forked. Called just after a worker has been forked.
The callable needs to accept two instance variables for the Arbiter and The callable needs to accept two instance variables for the Arbiter and
new Worker. new Worker.
""") """
class WhenReady(Setting):
name = "when_ready"
section = "Server Hooks"
validator = validate_callable(1)
type = "callable"
with Setting("when_ready") as s:
s.section = "Server Hooks"
s.validator = validate_callable(1)
s.type = "callable"
def def_start_server(server): def def_start_server(server):
pass pass
s.default = def_start_server def_start_server = staticmethod(def_start_server)
s.fmt_desc("""\ default = def_start_server
desc = """\
Called just after the server is started. Called just after the server is started.
The callable needs to accept a single instance variable for the Arbiter. The callable needs to accept a single instance variable for the Arbiter.
""") """
with Setting("pre_exec") as s:
s.section = "Server Hooks" class PreExec(Setting):
s.validator = validate_callable(1) name = "pre_exec"
s.type = "callable" section = "Server Hooks"
validator = validate_callable(1)
type = "callable"
def def_pre_exec(server): def def_pre_exec(server):
server.log.info("Forked child, reexecuting.") server.log.info("Forked child, reexecuting.")
s.default = def_pre_exec def_pre_exec = staticmethod(def_pre_exec)
s.fmt_desc("""\ default = def_pre_exec
desc = """\
Called just before a new master process is forked. Called just before a new master process is forked.
The callable needs to accept a single instance variable for the Arbiter. The callable needs to accept a single instance variable for the Arbiter.
""") """