diff --git a/gunicorn/app/base.py b/gunicorn/app/base.py index c5754073..47d84af6 100644 --- a/gunicorn/app/base.py +++ b/gunicorn/app/base.py @@ -47,7 +47,7 @@ class Application(object): # Lastly, update the configuration with any command line # settings. for k, v in opts.__dict__.iteritems(): - if v is None or self.cfg.modified(k.lower()): + if v is None: continue self.cfg.set(k.lower(), v) diff --git a/gunicorn/config.py b/gunicorn/config.py index be4313b1..30566421 100644 --- a/gunicorn/config.py +++ b/gunicorn/config.py @@ -10,108 +10,19 @@ import optparse import os import pwd import sys +import textwrap import types from gunicorn import __version__ from gunicorn import util -class Setting(object): - def __init__(self, name, opts): - self.name = name - self.section = opts["section"] - self.order = int(opts.get("order", 0)) - self.cli = opts["cli"].split() - self.type = opts["type"].strip() - self.arity = opts.get('arity', None) - if self.arity: - self.arity = int(self.arity) - self.meta = opts.get('meta', "").strip() or None - self.action = opts.get('action', "store").strip() - self.default = opts.get('default').strip() or None - self.desc = opts["desc"] - self.short = self.desc.splitlines()[0].strip() - - # Special case the callable types. - self.value = None - if self.default and self.type != 'callable': - self.set(self.default, modified=False) - - # A flag that tells us if this setting - # has been altered - self.modified = False - - def add_option(self, parser): - if not len(self.cli): - return - args = tuple(self.cli) - opttypes = { - "pos_int": "int", - "bool": None - } - kwargs = { - "dest": self.name, - "metavar": self.meta or None, - "action": self.action, - "type": opttypes.get(self.type, "string"), - "default": None, - "help": "%s [%s]" % (self.short, self.default) - } - parser.add_option(*args, **kwargs) - - def get(self): - return self.value - - def set(self, val, modified=True): - validator = getattr(self, "set_%s" % self.type) - self.value = validator(val) - self.modified = modified - - def set_bool(self, val): - if isinstance(val, types.BooleanType): - return val - if val.lower().strip() == "true": - return True - elif val.lower().strip() == "false": - return False - else: - raise ValueError("Invalid boolean: %s" % val) - - def set_pos_int(self, val): - if not isinstance(val, (types.IntType, types.LongType)): - val = int(val, 0) - if val < 0: - raise ValueError("Value must be positive: %s" % val) - return val - - def set_string(self, val): - return val.strip() - - def set_callable(self, val): - if not callable(val): - raise TypeError("Value is not callable: %s" % val) - arity = len(inspect.getargspec(val)[0]) - if arity != self.arity: - raise TypeError("Value must have an arity of: %s" % self.arity) - return val +KNOWN_SETTINGS = [] class Config(object): def __init__(self, usage): - self.settings = {} + self.settings = dict((s.name, s) for s in KNOWN_SETTINGS) self.usage = usage - - path = os.path.join(os.path.dirname(__file__), "options.ini") - opts = ConfigParser.SafeConfigParser() - if not len(opts.read(path)): - raise RuntimeError("Options configuration file is missing!") - - for sect in opts.sections(): - self.settings[sect] = Setting(sect, dict(opts.items(sect))) - - # Special case hook functions - self.settings['pre_fork'].set(def_pre_fork, modified=False) - self.settings['post_fork'].set(def_post_fork, modified=False) - self.settings['pre_exec'].set(def_pre_exec, modified=False) def __getattr__(self, name): if name not in self.settings: @@ -131,8 +42,7 @@ class Config(object): def parser(self): kwargs = { "usage": self.usage, - "version": __version__, - "formatter": HelpFormatter() + "version": __version__ } parser = optparse.OptionParser(**kwargs) @@ -144,9 +54,6 @@ class Config(object): self.settings[k].add_option(parser) return parser - def modified(self, name): - return self.settings[name].modified - @property def worker_class(self): uri = self.settings['worker_class'].get() @@ -208,18 +115,411 @@ class Config(object): def pre_exec(self): return self.settings['pre_exec'].get() +class Setting(object): + def __init__(self, name): + self.name = name + self.value = None + self.section = None + self.order = len(KNOWN_SETTINGS) + 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): + return self + + def __exit__(self, exc_type, exc_val, traceback): + if exc_type is None: + KNOWN_SETTINGS.append(self) + if self.default is not None: + self.set(self.default) + + def fmt_desc(self, desc): + desc = textwrap.dedent(desc).strip() + self.desc = desc + self.short = desc.splitlines()[0] + + def add_option(self, parser): + if not self.cli: + return + args = tuple(self.cli) + kwargs = { + "dest": self.name, + "metavar": self.meta or None, + "action": self.action or "store", + "type": self.type or "string", + "default": None, + "help": "%s [%s]" % (self.short, self.default) + } + if kwargs["action"] != "store": + kwargs.pop("type") + parser.add_option(*args, **kwargs) + + def get(self): + return self.value + + def set(self, val): + assert callable(self.validator), "Invalid validator: %s" % self.name + self.value = self.validator(val) -def def_pre_fork(server, worker): - pass +def validate_bool(val): + if isinstance(val, types.BooleanType): + return val + if val.lower().strip() == "true": + return True + elif val.lower().strip() == "false": + return False + else: + raise ValueError("Invalid boolean: %s" % val) -def def_post_fork(server, worker): - server.log.info("Worker spawned (pid: %s)" % worker.pid) +def validate_pos_int(val): + if not isinstance(val, (types.IntType, types.LongType)): + val = int(val, 0) + if val < 0: + raise ValueError("Value must be positive: %s" % val) + return val -def def_pre_exec(server): - server.log.info("Forked child, reexecuting.") +def validate_string(val): + return val.strip() +def validate_callable(arity): + def _validate_callable(val): + if not callable(val): + raise TypeError("Value is not callable: %s" % val) + if arity != len(inspect.getargspec(val)[0]): + raise TypeError("Value must have an arity of: %s" % arity) + return val + return _validate_callable -class HelpFormatter(optparse.IndentedHelpFormatter): - pass +with Setting("config") as s: + s.section = "Config" + s.cli = ["-c", "--config"] + s.meta = "FILE" + s.validator = validate_string + s.default = "gunicorn.conf.py" + s.fmt_desc("""\ + The path to a Gunicorn config file. + + By default Gunicorn will try to read a file named 'gunicorn.conf.py' in + the current directory. + + Only has an effect when specified on the command line or as part of an + application specific configuration. + """) +with Setting("bind") as s: + s.section = "Server Socket" + s.cli = ["-b", "--bind"] + s.meta = "ADDRESS" + s.validator = validate_string + s.default = "127.0.0.1:8000" + s.fmt_desc("""\ + The socket to bind. + + A string of the form: 'HOST', 'HOST:PORT', 'unix:PATH'. An IP is a valid + HOST. + """) +with Setting("backlog") as s: + s.section = "Server Socket" + s.cli = ["--backlog"] + s.meta = "INT" + s.validator = validate_pos_int + s.type = "int" + s.default = 2048 + s.fmt_desc("""\ + The maximum number of pending connections. + + This refers to the number of clients that can be waiting to be served. + Exceeding this number results in the client getting an error when + attempting to connect. It should only affect servers under significant + load. + + Must be a positive integer. Generally set in the 64-2048 range. + """) + +with Setting("workers") as s: + s.section = "Worker Processes" + s.cli = ["-w", "--workers"] + s.meta = "INT" + s.validator = validate_pos_int + s.type = "int" + s.default = 1 + s.fmt_desc("""\ + The number of worker process for handling requests. + + 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 + application's work load. + """) + +with Setting("worker_class") as s: + s.section = "Worker Processes" + s.cli = ["-k", "--worker-class"] + s.meta = "STRING" + s.validator = validate_string + s.default = "egg:gunicorn#sync" + s.fmt_desc("""\ + The type of workers to use. + + The default async class should handle most 'normal' types of work loads. + You'll want to read http://gunicorn/deployment.hml for information on + when you might want to choose one of the other worker classes. + + An string referring to a 'gunicorn.workers' entry point or a + MODULE:CLASS pair where CLASS is a subclass of + gunicorn.workers.base.Worker. + + The default provided values are: + + * egg:gunicorn#sync + * egg:gunicorn#eventlet - Requires eventlet >= 0.9.7 + * egg:gunicorn#gevent - Requires gevent >= 0.12.2 (?) + * egg:gunicorn#tornado - Requires tornado >= 0.2 + """) + +with Setting("worker_connections") as s: + s.section = "Worker Processes" + s.cli = ["--worker-connections"] + s.meta = "INT" + s.validator = validate_pos_int + s.type = "int" + s.default = 1000 + s.fmt_desc("""\ + The maximum number of simultaneous clients. + + This setting only affects the Eventlet and Gevent worker types. + """) + +with Setting("timeout") as s: + s.section = "Worker Processes" + s.cli = ["-t", "--timeout"] + s.meta = "INT" + s.validator = validate_pos_int + s.type = "int" + s.default = 30 + s.fmt_desc("""\ + Workers silent for more than this many seconds are killed and restarted. + + Generally set to thirty seconds. Only set this noticeably higher if + 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 + is not tied to the length of time required to handle a single request. + """) + +with Setting("keepalive") as s: + s.section = "Worker Processes" + s.cli = ["--keep-alive"] + s.meta = "INT" + s.validator = validate_pos_int + s.type = "int" + s.default = 2 + s.fmt_desc("""\ + The number of seconds to wait for requests on a Keep-Alive connection. + + Generally set in the 1-5 seconds range. + """) + +with Setting("debug") as s: + s.section = "Debugging" + s.cli = ["-d", "--debug"] + s.validator = validate_bool + s.action = "store_true" + s.default = False + s.fmt_desc("""\ + Turn on debugging in the server. + + This limits the number of worker processes to 1 and changes some error + handling that's sent to clients. + """) + +with Setting("spew") as s: + s.section = "Debugging" + s.cli = ["--spew"] + s.validator = validate_bool + s.action = "store_true" + s.default = False + s.fmt_desc("""\ + Install a trace function that spews every line executed by the server. + + This is the nuclear option. + """) + +with Setting("daemon") as s: + s.section = "Server Mechanics" + s.cli = ["-D", "--daemon"] + s.validator = validate_bool + s.action = "store_true" + s.default = False + s.fmt_desc("""\ + Daemonize the Gunicorn process. + + Detaches the server from the controlling terminal and enters the + background. + """) + +with Setting("pidfile") as s: + s.section = "Server Mechanics" + s.cli = ["-p", "--pid"] + s.meta = "FILE" + s.validator = validate_string + s.fmt_desc("""\ + A filename to use for the PID file. + + If not set, no PID file will be written. + """) + +with Setting("user") as s: + s.section = "Server Mechanics" + s.cli = ["-u", "--user"] + s.validator = validate_string + s.fmt_desc("""\ + Switch worker processes to run as this user. + + 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 the + worker process user. + """) + +with Setting("group") as s: + s.section = "Server Mechanics" + s.cli = ["-g", "--group"] + s.validator = validate_string + s.fmt_desc("""\ + Switch worker process to run as this group. + + 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 change the + worker processes group. + """) + +with Setting("umask") as s: + s.section = "Server Mechanics" + s.cli = ["-m", "--umask"] + s.meta = "INT" + s.validator = validate_pos_int + s.type = "int" + s.default = 0 + s.fmt_desc("""\ + A bit mask for the file mode on files written by Gunicorn. + + Note that this affects unix socket permissions. + + 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", + "0xFF", "0022" are valid for decimal, hex, and octal representations) + """) + +with Setting("tmp_upload_dir") as s: + s.section = "Server Mechanics" + s.meta = "DIR" + s.validator = validate_string + s.fmt_desc("""\ + Directory to store temporary request data as they are read. + + This may disappear in the near future. + + This path should be writable by the process permissions set for Gunicorn + workers. If not specified, Gunicorn will choose a system generated + temporary directory. + """) + +with Setting("logfile") as s: + s.section = "Logging" + s.cli = ["--log-file"] + s.meta = "FILE" + s.validator = validate_string + s.default = "-" + s.fmt_desc("""\ + The log file to write to. + + "-" means log to stdout. + """) + +with Setting("loglevel") as s: + s.section = "Logging" + s.cli = ["--log-level"] + s.meta = "LEVEL" + s.validator = validate_string + s.default = "info" + s.fmt_desc("""\ + The granularity of log output + + Valid level names are: + + * debug + * info + * warning + * error + * critical + """) + +with Setting("proc_name") as s: + s.section = "Process Naming" + s.cli = ["-n", "--name"] + s.meta = "STRING" + s.validator = validate_string + s.default = "gunicorn" + s.fmt_desc("""\ + A base to use with setproctitle for process naming. + + This affects things like 'ps' and 'top'. If you're going to be running + more than one instance of Gunicorn you'll probably want to set a name to + tell them apart. This requires that you install the setproctitle module. + + It defaults to 'gunicorn'. + """) + +with Setting("default_proc_name") as s: + s.section = "Process Naming" + s.validator = validate_string + s.default = "gunicorn" + s.fmt_desc("""\ + Internal setting that is adjusted for each type of application. + """) + +with Setting("pre_fork") as s: + s.section = "Server Hooks" + s.validator = validate_callable(2) + s.type = "callable" + def def_pre_fork(server, worker): + pass + s.default = def_pre_fork + s.fmt_desc("""\ + Called just before a worker is forked. + + The callable needs to accept two instance variables for the Arbiter and + new Worker. + """) + +with Setting("post_fork") as s: + s.section = "Server Hooks" + s.validator = validate_callable(2) + s.type = "callable" + def def_post_fork(server, worker): + server.log.info("Worker spawned (pid: %s)" % worker.pid) + s.default = def_post_fork + s.fmt_desc("""\ + Called just after a worker has been forked. + + The callable needs to accept two instance variables for the Arbiter and + new Worker. + """) + +with Setting("pre_exec") as s: + s.section = "Server Hooks" + s.validator = validate_callable(1) + s.type = "callable" + def def_pre_exec(server): + server.log.info("Forked child, reexecuting.") + s.default = def_pre_exec + s.fmt_desc("""\ + Called just before a new master process is forked. + + The callable needs to accept a single instance variable for the Arbiter. + """) diff --git a/gunicorn/options.ini b/gunicorn/options.ini deleted file mode 100644 index e5957c24..00000000 --- a/gunicorn/options.ini +++ /dev/null @@ -1,334 +0,0 @@ -# This file is read by config.py to set up the default -# information for all server settings. It is here so -# that we can use the data to generate all the various -# docs related to configuration information. - -# -# Config file -# - -[config] -section = Config -cli = -c --config -meta = FILE -type = string -default = gunicorn.conf.py -desc = The path to a Gunicorn config file. - - By default Gunicorn will try to read a file named 'gunicorn.conf.py' in the - current directory. - - Only has an effect when specified on the command line or as part of an - application specific configuration. - -# -# Server Socket -# - -[bind] -section = Server Socket -order = 0 -cli = -b --bind -meta = ADDRESS -type = string -default = 127.0.0.1:8000 -desc = The socket to bind. - -[backlog] -section = Server Socket -order = 1 -cli = --backlog -meta = INT -type = pos_int -default = 2048 -desc = The maximum number of pending connections. - - This refers to the number of clients that can be waiting to be served. - Exceeding this number results in the client getting an error when attempting - to connect. It should only affect servers under significant load. - - Must be a positive integer. Generally set in the 64-2048 range. - -# -# Worker processes -# - -[workers] -section = Worker Processes -order = 1 -cli = -w --workers -meta = INT -type = pos_int -default = 1 -desc = The number of worker process for handling requests. - - 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 application's work - load. - -[worker_class] -section = Worker Processes -order = 2 -cli = -k --worker-class -meta = STRING -type = string -default = egg:gunicorn#sync -desc = The type of workers to use. - - The default async class should handle most 'normal' types of work loads. - You'll want to read http://gunicorn/deployment.hml for information on when - you might want to choose one of the other worker classes. - - An string referring to a 'gunicorn.workers' entry point or a MODULE:CLASS - pair where CLASS is a subclass of gunicorn.workers.base.Worker. The default - provided values are: - - egg:gunicorn#sync - egg:gunicorn#eventlet - Requires eventlet >= 0.9.7 - egg:gunicorn#gevent - Requires gevent >= 0.12.2 (?) - egg:gunicorn#tornado - Requires tornado >= 0.2 - -[worker_connections] -section = Worker Processes -order = 3 -cli = --worker-connections -meta = INT -type = pos_int -default = 1000 -desc = The maximum number of simultaneous clients. - - This setting only affects the Eventlet and Gevent worker types. - - A positive integer generally set to around 1000. - -[timeout] -section = Worker Processes -order = 4 -cli = -t --timeout -meta = INT -type = pos_int -default = 30 -desc = Workers silent for more than this many seconds are killed and restarted. - - Generally set to thirty seconds. Only set this noticeably higher if 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 is not tied to the - length of time required to handle a single request. - -[keepalive] -section = Worker Processes -order = 5 -cli = --keep-alive -meta = INT -type = pos_int -default = 2 -desc = The number of seconds to wait for requests on a Keep-Alive connection. - - A positive integer. Generally set in the 1-5 seconds range. - -# -# Debugging -# - -[debug] -section = Debugging -order = 1 -cli = -d --debug -type = bool -action = store_true -default = False -desc = Turn on debugging in the server. - - This limits the number of worker processes to 1 and changes some error - handling that's sent to clients. - -[spew] -section = Debugging -order = 2 -cli = --spew -type = bool -action = store_true -default = False -desc = Install a trace function that spews every line executed by the server. - - This is the nuclear option. - -# -# Server mechanics -# - -[daemon] -section = Server Mechanics -order = 1 -cli = -D --daemon -action = store_true -type = bool -default = False -desc = Daemonize the Gunicorn process. - - Detaches the server from the controlling terminal and enters the background. - -[pidfile] -section = Server Mechanics -order = 2 -cli = -p --pid -meta = FILE -type = string -default = -desc = A filename to use for the PID file. - - If not set, no PID file will be written. - -[user] -section = Server Mechanics -order = 3 -cli = -u --user -type = string -default = -desc = Switch worker processes to run as this user. - - 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 the worker process - user. - -[group] -section = Server Mechanics -order = 4 -cli = -g --group -type = string -default = -desc = Switch worker process to run as this group. - - 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 change the worker processes - group. - -[umask] -section = Server Mechanics -order = 5 -cli = -m --umask -meta = INT -type = pos_int -default = 0 -desc = A bit mask for the file mode on files written by Gunicorn. - - Note that this affects unix socket permissions. - - 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", "0xFF", - "0022" are valid for decimal, hex, and octal representations) - -[tmp_upload_dir] -section = Server Mechanics -order = 6 -cli = -meta = DIR -type = string -default = -desc = Directory to store temporary request data as they are read. - - This may disappear in the near future. - - This path should be writable by the process permissions set for Gunicorn - workers. If not specified, Gunicorn will choose a system generated temporary - directory. - -# -# Logging -# - -[logfile] -section = Logging -order = 1 -cli = --log-file -meta = FILE -type = string -default = - -desc = The log file to write to. - - "-" means log to stdout. - -[loglevel] -section = Logging -order = 2 -cli = --log-level -meta = LEVEL -type = string -default = info -desc = The granularity of log output - - Valid level names are: - - debug - info - warning - error - critical - -# -# Process naming -# - -[proc_name] -section = Process Naming -order = 1 -cli = -n --name -meta = STRING -type = string -default = gunicorn -desc = A base to use with setproctitle for process naming. - - This affects things like 'ps' and 'top'. If you're going to be running more - than one instance of Gunicorn you'll probably want to set a name to tell - them apart. This requires that you install the setproctitle module. - - It defaults to 'gunicorn'. - -[default_proc_name] -section = Process Naming -order = 2 -cli = -type = string -default = gunicorn -desc = Internal setting that is adjusted for each type of application. - -# -# Server hooks -# - -[pre_fork] -section = Server Hooks -order = 1 -cli = -type = callable -arity = 2 -default = -desc = Called just before a worker is forked. - - The callable needs to accept two instance variables for the Arbiter and new - Worker. - -[post_fork] -section = Server Hooks -order = 2 -cli = -type = callable -arity = 2 -default = -desc = Called just after a worker has been forked. - - The callable needs to accept two instance variables for the Arbiter and new - Worker. - -[pre_exec] -section = Server Hooks -order = 3 -cli = -type = callable -arity = 1 -default = -desc = Called just before a new master process is forked. - - The callable needs to accept a single instance variable for the Arbiter. - - Cannot be specified from the command line.