3
# Copyright 2009 Facebook
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
# not use this file except in compliance with the License. You may obtain
7
# a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
# License for the specific language governing permissions and limitations
17
"""A command line parsing module that lets modules define their own options.
19
Each module defines its own options, e.g.,
21
from tornado.options import define, options
23
define("mysql_host", default="127.0.0.1:3306", help="Main user DB")
24
define("memcache_hosts", default="127.0.0.1:11011", multiple=True,
25
help="Main user memcache servers")
28
db = database.Connection(options.mysql_host)
31
The main() method of your application does not need to be aware of all of
32
the options used throughout your program; they are all automatically loaded
33
when the modules are loaded. Your main() method can parse the command line
34
or parse a config file with:
36
import tornado.options
37
tornado.options.parse_config_file("/etc/server.conf")
38
tornado.options.parse_command_line()
40
Command line formats are what you would expect ("--myoption=myvalue").
41
Config files are just Python files. Global names become options, e.g.,
44
myotheroption = "myothervalue"
46
We support datetimes, timedeltas, ints, and floats (just pass a 'type'
47
kwarg to define). We also accept multi-value options. See the documentation
53
import logging.handlers
58
# For pretty log messages, if available
65
def define(name, default=None, type=str, help=None, metavar=None,
67
"""Defines a new command line option.
69
If type is given (one of str, float, int, datetime, or timedelta),
70
we parse the command line arguments based on the given type. If
71
multiple is True, we accept comma-separated values, and the option
72
value is always a list.
74
For multi-value integers, we also accept the syntax x:y, which
75
turns into range(x, y) - very useful for long integer ranges.
77
help and metavar are used to construct the automatically generated
78
command line help string. The help message is formatted like:
80
--name=METAVAR help string
82
Command line option names must be unique globally. They can be parsed
83
from the command line with parse_command_line() or parsed from a
84
config file with parse_config_file.
87
raise Error("Option %r already defined in %s", name,
88
options[name].file_name)
89
frame = sys._getframe(0)
90
options_file = frame.f_code.co_filename
91
file_name = frame.f_back.f_code.co_filename
92
if file_name == options_file: file_name = ""
93
options[name] = _Option(name, file_name=file_name, default=default,
94
type=type, help=help, metavar=metavar,
98
def parse_command_line(args=None):
99
"""Parses all options given on the command line.
101
We return all command line arguments that are not options as a list.
103
if args is None: args = sys.argv
105
for i in xrange(1, len(args)):
106
# All things after the last option are command line arguments
107
if not args[i].startswith("-"):
111
remaining = args[i+1:]
113
arg = args[i].lstrip("-")
114
name, equals, value = arg.partition("=")
115
name = name.replace('-', '_')
116
if not name in options:
118
raise Error('Unrecognized command line option: %r' % name)
119
option = options[name]
121
if option.type == bool:
124
raise Error('Option %r requires a value' % name)
130
# Set up log level and pretty console logging by default
131
if options.logging != 'none':
132
logging.getLogger().setLevel(getattr(logging, options.logging.upper()))
133
enable_pretty_logging()
138
def parse_config_file(path, overwrite=True):
139
"""Parses and loads the Python config file at the given path."""
141
execfile(path, config, config)
144
options[name].set(config[name])
147
def print_help(file=sys.stdout):
148
"""Prints all the command line options to stdout."""
149
print >> file, "Usage: %s [OPTIONS]" % sys.argv[0]
151
print >> file, "Options:"
153
for option in options.itervalues():
154
by_file.setdefault(option.file_name, []).append(option)
156
for filename, o in sorted(by_file.items()):
157
if filename: print >> file, filename
158
o.sort(key=lambda option: option.name)
162
prefix += "=" + option.metavar
163
print >> file, " --%-30s %s" % (prefix, option.help or "")
167
class _Options(dict):
168
"""Our global program options, an dictionary with object-like access."""
171
if not hasattr(cls, "_instance"):
172
cls._instance = cls()
175
def __getattr__(self, name):
176
if isinstance(self.get(name), _Option):
177
return self[name].value()
178
raise AttributeError("Unrecognized option %r" % name)
181
class _Option(object):
182
def __init__(self, name, default=None, type=str, help=None, metavar=None,
183
multiple=False, file_name=None):
184
if default is None and multiple:
189
self.metavar = metavar
190
self.multiple = multiple
191
self.file_name = file_name
192
self.default = default
196
return self.default if self._value is None else self._value
198
def parse(self, value):
200
datetime.datetime: self._parse_datetime,
201
datetime.timedelta: self._parse_timedelta,
202
bool: self._parse_bool,
203
str: self._parse_string,
204
}.get(self.type, self.type)
206
if self._value is None:
208
for part in value.split(","):
209
if self.type in (int, long):
210
# allow ranges of the form X:Y (inclusive at both ends)
211
lo, _, hi = part.partition(":")
213
hi = _parse(hi) if hi else lo
214
self._value.extend(range(lo, hi+1))
216
self._value.append(_parse(part))
218
self._value = _parse(value)
221
def set(self, value):
223
if not isinstance(value, list):
224
raise Error("Option %r is required to be a list of %s" %
225
(self.name, self.type.__name__))
227
if item != None and not isinstance(item, self.type):
228
raise Error("Option %r is required to be a list of %s" %
229
(self.name, self.type.__name__))
231
if value != None and not isinstance(value, self.type):
232
raise Error("Option %r is required to be a %s" %
233
(self.name, self.type.__name__))
236
# Supported date/time formats in our options
237
_DATETIME_FORMATS = [
238
"%a %b %d %H:%M:%S %Y",
250
def _parse_datetime(self, value):
251
for format in self._DATETIME_FORMATS:
253
return datetime.datetime.strptime(value, format)
256
raise Error('Unrecognized date/time format: %r' % value)
258
_TIMEDELTA_ABBREVS = [
260
('minutes', ['m', 'min']),
261
('seconds', ['s', 'sec']),
262
('milliseconds', ['ms']),
263
('microseconds', ['us']),
268
_TIMEDELTA_ABBREV_DICT = dict(
269
(abbrev, full) for full, abbrevs in _TIMEDELTA_ABBREVS
270
for abbrev in abbrevs)
272
_FLOAT_PATTERN = r'[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?'
274
_TIMEDELTA_PATTERN = re.compile(
275
r'\s*(%s)\s*(\w*)\s*' % _FLOAT_PATTERN, re.IGNORECASE)
277
def _parse_timedelta(self, value):
279
sum = datetime.timedelta()
281
while start < len(value):
282
m = self._TIMEDELTA_PATTERN.match(value, start)
285
num = float(m.group(1))
286
units = m.group(2) or 'seconds'
287
units = self._TIMEDELTA_ABBREV_DICT.get(units, units)
288
sum += datetime.timedelta(**{units: num})
294
def _parse_bool(self, value):
295
return value.lower() not in ("false", "0", "f")
297
def _parse_string(self, value):
298
return value.decode("utf-8")
301
class Error(Exception):
305
def enable_pretty_logging():
306
"""Turns on formatted logging output as configured."""
307
if (options.log_to_stderr or
308
(options.log_to_stderr is None and not options.log_file_prefix)):
309
# Set up color if we are in a tty and curses is installed
311
if curses and sys.stderr.isatty():
314
if curses.tigetnum("colors") > 0:
318
channel = logging.StreamHandler()
319
channel.setFormatter(_LogFormatter(color=color))
320
logging.getLogger().addHandler(channel)
322
if options.log_file_prefix:
323
channel = logging.handlers.RotatingFileHandler(
324
filename=options.log_file_prefix,
325
maxBytes=options.log_file_max_size,
326
backupCount=options.log_file_num_backups)
327
channel.setFormatter(_LogFormatter(color=False))
328
logging.getLogger().addHandler(channel)
331
class _LogFormatter(logging.Formatter):
332
def __init__(self, color, *args, **kwargs):
333
logging.Formatter.__init__(self, *args, **kwargs)
336
fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or ""
338
logging.DEBUG: curses.tparm(fg_color, 4), # Blue
339
logging.INFO: curses.tparm(fg_color, 2), # Green
340
logging.WARNING: curses.tparm(fg_color, 3), # Yellow
341
logging.ERROR: curses.tparm(fg_color, 1), # Red
343
self._normal = curses.tigetstr("sgr0")
345
def format(self, record):
347
record.message = record.getMessage()
349
record.message = "Bad message (%r): %r" % (e, record.__dict__)
350
record.asctime = time.strftime(
351
"%y%m%d %H:%M:%S", self.converter(record.created))
352
prefix = '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]' % \
355
prefix = (self._colors.get(record.levelno, self._normal) +
356
prefix + self._normal)
357
formatted = prefix + " " + record.message
359
if not record.exc_text:
360
record.exc_text = self.formatException(record.exc_info)
362
formatted = formatted.rstrip() + "\n" + record.exc_text
363
return formatted.replace("\n", "\n ")
366
options = _Options.instance()
370
define("help", type=bool, help="show this help information")
371
define("logging", default="info",
372
help=("Set the Python log level. If 'none', tornado won't touch the "
373
"logging configuration."),
374
metavar="info|warning|error|none")
375
define("log_to_stderr", type=bool, default=None,
376
help=("Send log output to stderr (colorized if possible). "
377
"By default use stderr if --log_file_prefix is not set."))
378
define("log_file_prefix", type=str, default=None, metavar="PATH",
379
help=("Path prefix for log files. "
380
"Note that if you are running multiple tornado processes, "
381
"log_file_prefix must be different for each of them (e.g. "
382
"include the port number)"))
383
define("log_file_max_size", type=int, default=100 * 1000 * 1000,
384
help="max size of log files before rollover")
385
define("log_file_num_backups", type=int, default=10,
386
help="number of log files to keep")