Update tornado
This commit is contained in:
Regular → Executable
+198
-139
@@ -48,12 +48,16 @@ kwarg to define). We also accept multi-value options. See the documentation
|
||||
for define() below.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, with_statement
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import logging.handlers
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
import textwrap
|
||||
|
||||
from tornado.escape import _unicode
|
||||
|
||||
@@ -64,136 +68,123 @@ except ImportError:
|
||||
curses = None
|
||||
|
||||
|
||||
def define(name, default=None, type=None, help=None, metavar=None,
|
||||
multiple=False, group=None):
|
||||
"""Defines a new command line option.
|
||||
|
||||
If type is given (one of str, float, int, datetime, or timedelta)
|
||||
or can be inferred from the default, we parse the command line
|
||||
arguments based on the given type. If multiple is True, we accept
|
||||
comma-separated values, and the option value is always a list.
|
||||
|
||||
For multi-value integers, we also accept the syntax x:y, which
|
||||
turns into range(x, y) - very useful for long integer ranges.
|
||||
|
||||
help and metavar are used to construct the automatically generated
|
||||
command line help string. The help message is formatted like::
|
||||
|
||||
--name=METAVAR help string
|
||||
|
||||
group is used to group the defined options in logical groups. By default,
|
||||
command line options are grouped by the defined file.
|
||||
|
||||
Command line option names must be unique globally. They can be parsed
|
||||
from the command line with parse_command_line() or parsed from a
|
||||
config file with parse_config_file.
|
||||
"""
|
||||
if name in options:
|
||||
raise Error("Option %r already defined in %s", name,
|
||||
options[name].file_name)
|
||||
frame = sys._getframe(0)
|
||||
options_file = frame.f_code.co_filename
|
||||
file_name = frame.f_back.f_code.co_filename
|
||||
if file_name == options_file: file_name = ""
|
||||
if type is None:
|
||||
if not multiple and default is not None:
|
||||
type = default.__class__
|
||||
else:
|
||||
type = str
|
||||
if group:
|
||||
group_name = group
|
||||
else:
|
||||
group_name = file_name
|
||||
options[name] = _Option(name, file_name=file_name, default=default,
|
||||
type=type, help=help, metavar=metavar,
|
||||
multiple=multiple, group_name=group_name)
|
||||
|
||||
|
||||
def parse_command_line(args=None):
|
||||
"""Parses all options given on the command line.
|
||||
|
||||
We return all command line arguments that are not options as a list.
|
||||
"""
|
||||
if args is None: args = sys.argv
|
||||
remaining = []
|
||||
for i in xrange(1, len(args)):
|
||||
# All things after the last option are command line arguments
|
||||
if not args[i].startswith("-"):
|
||||
remaining = args[i:]
|
||||
break
|
||||
if args[i] == "--":
|
||||
remaining = args[i+1:]
|
||||
break
|
||||
arg = args[i].lstrip("-")
|
||||
name, equals, value = arg.partition("=")
|
||||
name = name.replace('-', '_')
|
||||
if not name in options:
|
||||
print_help()
|
||||
raise Error('Unrecognized command line option: %r' % name)
|
||||
option = options[name]
|
||||
if not equals:
|
||||
if option.type == bool:
|
||||
value = "true"
|
||||
else:
|
||||
raise Error('Option %r requires a value' % name)
|
||||
option.parse(value)
|
||||
if options.help:
|
||||
print_help()
|
||||
sys.exit(0)
|
||||
|
||||
# Set up log level and pretty console logging by default
|
||||
if options.logging != 'none':
|
||||
logging.getLogger().setLevel(getattr(logging, options.logging.upper()))
|
||||
enable_pretty_logging()
|
||||
|
||||
return remaining
|
||||
|
||||
|
||||
def parse_config_file(path):
|
||||
"""Parses and loads the Python config file at the given path."""
|
||||
config = {}
|
||||
execfile(path, config, config)
|
||||
for name in config:
|
||||
if name in options:
|
||||
options[name].set(config[name])
|
||||
|
||||
|
||||
def print_help(file=sys.stdout):
|
||||
"""Prints all the command line options to stdout."""
|
||||
print >> file, "Usage: %s [OPTIONS]" % sys.argv[0]
|
||||
print >> file, ""
|
||||
print >> file, "Options:"
|
||||
by_group = {}
|
||||
for option in options.itervalues():
|
||||
by_group.setdefault(option.group_name, []).append(option)
|
||||
|
||||
for filename, o in sorted(by_group.items()):
|
||||
if filename: print >> file, filename
|
||||
o.sort(key=lambda option: option.name)
|
||||
for option in o:
|
||||
prefix = option.name
|
||||
if option.metavar:
|
||||
prefix += "=" + option.metavar
|
||||
print >> file, " --%-30s %s" % (prefix, option.help or "")
|
||||
print >> file
|
||||
class Error(Exception):
|
||||
"""Exception raised by errors in the options module."""
|
||||
pass
|
||||
|
||||
|
||||
class _Options(dict):
|
||||
"""Our global program options, an dictionary with object-like access."""
|
||||
@classmethod
|
||||
def instance(cls):
|
||||
if not hasattr(cls, "_instance"):
|
||||
cls._instance = cls()
|
||||
return cls._instance
|
||||
"""A collection of options, a dictionary with object-like access.
|
||||
|
||||
Normally accessed via static functions in the `tornado.options` module,
|
||||
which reference a global instance.
|
||||
"""
|
||||
def __getattr__(self, name):
|
||||
if isinstance(self.get(name), _Option):
|
||||
return self[name].value()
|
||||
raise AttributeError("Unrecognized option %r" % name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if isinstance(self.get(name), _Option):
|
||||
return self[name].set(value)
|
||||
raise AttributeError("Unrecognized option %r" % name)
|
||||
|
||||
def define(self, name, default=None, type=None, help=None, metavar=None,
|
||||
multiple=False, group=None):
|
||||
if name in self:
|
||||
raise Error("Option %r already defined in %s", name,
|
||||
self[name].file_name)
|
||||
frame = sys._getframe(0)
|
||||
options_file = frame.f_code.co_filename
|
||||
file_name = frame.f_back.f_code.co_filename
|
||||
if file_name == options_file:
|
||||
file_name = ""
|
||||
if type is None:
|
||||
if not multiple and default is not None:
|
||||
type = default.__class__
|
||||
else:
|
||||
type = str
|
||||
if group:
|
||||
group_name = group
|
||||
else:
|
||||
group_name = file_name
|
||||
self[name] = _Option(name, file_name=file_name, default=default,
|
||||
type=type, help=help, metavar=metavar,
|
||||
multiple=multiple, group_name=group_name)
|
||||
|
||||
def parse_command_line(self, args=None):
|
||||
if args is None:
|
||||
args = sys.argv
|
||||
remaining = []
|
||||
for i in xrange(1, len(args)):
|
||||
# All things after the last option are command line arguments
|
||||
if not args[i].startswith("-"):
|
||||
remaining = args[i:]
|
||||
break
|
||||
if args[i] == "--":
|
||||
remaining = args[i + 1:]
|
||||
break
|
||||
arg = args[i].lstrip("-")
|
||||
name, equals, value = arg.partition("=")
|
||||
name = name.replace('-', '_')
|
||||
if not name in self:
|
||||
print_help()
|
||||
raise Error('Unrecognized command line option: %r' % name)
|
||||
option = self[name]
|
||||
if not equals:
|
||||
if option.type == bool:
|
||||
value = "true"
|
||||
else:
|
||||
raise Error('Option %r requires a value' % name)
|
||||
option.parse(value)
|
||||
if self.help:
|
||||
print_help()
|
||||
sys.exit(0)
|
||||
|
||||
# Set up log level and pretty console logging by default
|
||||
if self.logging != 'none':
|
||||
logging.getLogger().setLevel(getattr(logging, self.logging.upper()))
|
||||
enable_pretty_logging()
|
||||
|
||||
return remaining
|
||||
|
||||
def parse_config_file(self, path):
|
||||
config = {}
|
||||
execfile(path, config, config)
|
||||
for name in config:
|
||||
if name in self:
|
||||
self[name].set(config[name])
|
||||
|
||||
def print_help(self, file=sys.stdout):
|
||||
"""Prints all the command line options to stdout."""
|
||||
print >> file, "Usage: %s [OPTIONS]" % sys.argv[0]
|
||||
print >> file, "\nOptions:\n"
|
||||
by_group = {}
|
||||
for option in self.itervalues():
|
||||
by_group.setdefault(option.group_name, []).append(option)
|
||||
|
||||
for filename, o in sorted(by_group.items()):
|
||||
if filename:
|
||||
print >> file, "\n%s options:\n" % os.path.normpath(filename)
|
||||
o.sort(key=lambda option: option.name)
|
||||
for option in o:
|
||||
prefix = option.name
|
||||
if option.metavar:
|
||||
prefix += "=" + option.metavar
|
||||
description = option.help or ""
|
||||
if option.default is not None and option.default != '':
|
||||
description += " (default %s)" % option.default
|
||||
lines = textwrap.wrap(description, 79 - 35)
|
||||
if len(prefix) > 30 or len(lines) == 0:
|
||||
lines.insert(0, '')
|
||||
print >> file, " --%-30s %s" % (prefix, lines[0])
|
||||
for line in lines[1:]:
|
||||
print >> file, "%-34s %s" % (' ', line)
|
||||
print >> file
|
||||
|
||||
|
||||
class _Option(object):
|
||||
def __init__(self, name, default=None, type=str, help=None, metavar=None,
|
||||
def __init__(self, name, default=None, type=basestring, help=None, metavar=None,
|
||||
multiple=False, file_name=None, group_name=None):
|
||||
if default is None and multiple:
|
||||
default = []
|
||||
@@ -215,18 +206,17 @@ class _Option(object):
|
||||
datetime.datetime: self._parse_datetime,
|
||||
datetime.timedelta: self._parse_timedelta,
|
||||
bool: self._parse_bool,
|
||||
str: self._parse_string,
|
||||
basestring: self._parse_string,
|
||||
}.get(self.type, self.type)
|
||||
if self.multiple:
|
||||
if self._value is None:
|
||||
self._value = []
|
||||
self._value = []
|
||||
for part in value.split(","):
|
||||
if self.type in (int, long):
|
||||
# allow ranges of the form X:Y (inclusive at both ends)
|
||||
lo, _, hi = part.partition(":")
|
||||
lo = _parse(lo)
|
||||
hi = _parse(hi) if hi else lo
|
||||
self._value.extend(range(lo, hi+1))
|
||||
self._value.extend(range(lo, hi + 1))
|
||||
else:
|
||||
self._value.append(_parse(part))
|
||||
else:
|
||||
@@ -244,8 +234,8 @@ class _Option(object):
|
||||
(self.name, self.type.__name__))
|
||||
else:
|
||||
if value != None and not isinstance(value, self.type):
|
||||
raise Error("Option %r is required to be a %s" %
|
||||
(self.name, self.type.__name__))
|
||||
raise Error("Option %r is required to be a %s (%s given)" %
|
||||
(self.name, self.type.__name__, type(value)))
|
||||
self._value = value
|
||||
|
||||
# Supported date/time formats in our options
|
||||
@@ -313,14 +303,64 @@ class _Option(object):
|
||||
return _unicode(value)
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Exception raised by errors in the options module."""
|
||||
pass
|
||||
options = _Options()
|
||||
"""Global options dictionary.
|
||||
|
||||
Supports both attribute-style and dict-style access.
|
||||
"""
|
||||
|
||||
|
||||
def enable_pretty_logging():
|
||||
def define(name, default=None, type=None, help=None, metavar=None,
|
||||
multiple=False, group=None):
|
||||
"""Defines a new command line option.
|
||||
|
||||
If type is given (one of str, float, int, datetime, or timedelta)
|
||||
or can be inferred from the default, we parse the command line
|
||||
arguments based on the given type. If multiple is True, we accept
|
||||
comma-separated values, and the option value is always a list.
|
||||
|
||||
For multi-value integers, we also accept the syntax x:y, which
|
||||
turns into range(x, y) - very useful for long integer ranges.
|
||||
|
||||
help and metavar are used to construct the automatically generated
|
||||
command line help string. The help message is formatted like::
|
||||
|
||||
--name=METAVAR help string
|
||||
|
||||
group is used to group the defined options in logical groups. By default,
|
||||
command line options are grouped by the defined file.
|
||||
|
||||
Command line option names must be unique globally. They can be parsed
|
||||
from the command line with parse_command_line() or parsed from a
|
||||
config file with parse_config_file.
|
||||
"""
|
||||
return options.define(name, default=default, type=type, help=help,
|
||||
metavar=metavar, multiple=multiple, group=group)
|
||||
|
||||
|
||||
def parse_command_line(args=None):
|
||||
"""Parses all options given on the command line (defaults to sys.argv).
|
||||
|
||||
Note that args[0] is ignored since it is the program name in sys.argv.
|
||||
|
||||
We return a list of all arguments that are not parsed as options.
|
||||
"""
|
||||
return options.parse_command_line(args)
|
||||
|
||||
|
||||
def parse_config_file(path):
|
||||
"""Parses and loads the Python config file at the given path."""
|
||||
return options.parse_config_file(path)
|
||||
|
||||
|
||||
def print_help(file=sys.stdout):
|
||||
"""Prints all the command line options to stdout."""
|
||||
return options.print_help(file)
|
||||
|
||||
|
||||
def enable_pretty_logging(options=options):
|
||||
"""Turns on formatted logging output as configured.
|
||||
|
||||
|
||||
This is called automatically by `parse_command_line`.
|
||||
"""
|
||||
root_logger = logging.getLogger()
|
||||
@@ -348,7 +388,6 @@ def enable_pretty_logging():
|
||||
root_logger.addHandler(channel)
|
||||
|
||||
|
||||
|
||||
class _LogFormatter(logging.Formatter):
|
||||
def __init__(self, color, *args, **kwargs):
|
||||
logging.Formatter.__init__(self, *args, **kwargs)
|
||||
@@ -366,13 +405,13 @@ class _LogFormatter(logging.Formatter):
|
||||
if (3, 0) < sys.version_info < (3, 2, 3):
|
||||
fg_color = unicode(fg_color, "ascii")
|
||||
self._colors = {
|
||||
logging.DEBUG: unicode(curses.tparm(fg_color, 4), # Blue
|
||||
logging.DEBUG: unicode(curses.tparm(fg_color, 4), # Blue
|
||||
"ascii"),
|
||||
logging.INFO: unicode(curses.tparm(fg_color, 2), # Green
|
||||
logging.INFO: unicode(curses.tparm(fg_color, 2), # Green
|
||||
"ascii"),
|
||||
logging.WARNING: unicode(curses.tparm(fg_color, 3), # Yellow
|
||||
logging.WARNING: unicode(curses.tparm(fg_color, 3), # Yellow
|
||||
"ascii"),
|
||||
logging.ERROR: unicode(curses.tparm(fg_color, 1), # Red
|
||||
logging.ERROR: unicode(curses.tparm(fg_color, 1), # Red
|
||||
"ascii"),
|
||||
}
|
||||
self._normal = unicode(curses.tigetstr("sgr0"), "ascii")
|
||||
@@ -382,6 +421,7 @@ class _LogFormatter(logging.Formatter):
|
||||
record.message = record.getMessage()
|
||||
except Exception, e:
|
||||
record.message = "Bad message (%r): %r" % (e, record.__dict__)
|
||||
assert isinstance(record.message, basestring) # guaranteed by logging
|
||||
record.asctime = time.strftime(
|
||||
"%y%m%d %H:%M:%S", self.converter(record.created))
|
||||
prefix = '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]' % \
|
||||
@@ -389,7 +429,29 @@ class _LogFormatter(logging.Formatter):
|
||||
if self._color:
|
||||
prefix = (self._colors.get(record.levelno, self._normal) +
|
||||
prefix + self._normal)
|
||||
formatted = prefix + " " + record.message
|
||||
|
||||
# Encoding notes: The logging module prefers to work with character
|
||||
# strings, but only enforces that log messages are instances of
|
||||
# basestring. In python 2, non-ascii bytestrings will make
|
||||
# their way through the logging framework until they blow up with
|
||||
# an unhelpful decoding error (with this formatter it happens
|
||||
# when we attach the prefix, but there are other opportunities for
|
||||
# exceptions further along in the framework).
|
||||
#
|
||||
# If a byte string makes it this far, convert it to unicode to
|
||||
# ensure it will make it out to the logs. Use repr() as a fallback
|
||||
# to ensure that all byte strings can be converted successfully,
|
||||
# but don't do it by default so we don't add extra quotes to ascii
|
||||
# bytestrings. This is a bit of a hacky place to do this, but
|
||||
# it's worth it since the encoding errors that would otherwise
|
||||
# result are so useless (and tornado is fond of using utf8-encoded
|
||||
# byte strings whereever possible).
|
||||
try:
|
||||
message = _unicode(record.message)
|
||||
except UnicodeDecodeError:
|
||||
message = repr(record.message)
|
||||
|
||||
formatted = prefix + " " + message
|
||||
if record.exc_info:
|
||||
if not record.exc_text:
|
||||
record.exc_text = self.formatException(record.exc_info)
|
||||
@@ -398,9 +460,6 @@ class _LogFormatter(logging.Formatter):
|
||||
return formatted.replace("\n", "\n ")
|
||||
|
||||
|
||||
options = _Options.instance()
|
||||
|
||||
|
||||
# Default options
|
||||
define("help", type=bool, help="show this help information")
|
||||
define("logging", default="info",
|
||||
|
||||
Reference in New Issue
Block a user