~ntt-pf-lab/nova/monkey_patch_notification

« back to all changes in this revision

Viewing changes to vendor/tornado/tornado/options.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
#
 
3
# Copyright 2009 Facebook
 
4
#
 
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
 
8
#
 
9
#     http://www.apache.org/licenses/LICENSE-2.0
 
10
#
 
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
 
15
# under the License.
 
16
 
 
17
"""A command line parsing module that lets modules define their own options.
 
18
 
 
19
Each module defines its own options, e.g.,
 
20
 
 
21
    from tornado.options import define, options
 
22
 
 
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")
 
26
 
 
27
    def connect():
 
28
        db = database.Connection(options.mysql_host)
 
29
        ...
 
30
 
 
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:
 
35
 
 
36
    import tornado.options
 
37
    tornado.options.parse_config_file("/etc/server.conf")
 
38
    tornado.options.parse_command_line()
 
39
 
 
40
Command line formats are what you would expect ("--myoption=myvalue").
 
41
Config files are just Python files. Global names become options, e.g.,
 
42
 
 
43
    myoption = "myvalue"
 
44
    myotheroption = "myothervalue"
 
45
 
 
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
 
48
for define() below.
 
49
"""
 
50
 
 
51
import datetime
 
52
import logging
 
53
import logging.handlers
 
54
import re
 
55
import sys
 
56
import time
 
57
 
 
58
# For pretty log messages, if available
 
59
try:
 
60
    import curses
 
61
except:
 
62
    curses = None
 
63
 
 
64
 
 
65
def define(name, default=None, type=str, help=None, metavar=None,
 
66
           multiple=False):
 
67
    """Defines a new command line option.
 
68
 
 
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.
 
73
 
 
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.
 
76
 
 
77
    help and metavar are used to construct the automatically generated
 
78
    command line help string. The help message is formatted like:
 
79
 
 
80
       --name=METAVAR      help string
 
81
 
 
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.
 
85
    """
 
86
    if name in options:
 
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,
 
95
                            multiple=multiple)
 
96
 
 
97
 
 
98
def parse_command_line(args=None):
 
99
    """Parses all options given on the command line.
 
100
 
 
101
    We return all command line arguments that are not options as a list.
 
102
    """
 
103
    if args is None: args = sys.argv
 
104
    remaining = []
 
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("-"):
 
108
            remaining = args[i:]
 
109
            break
 
110
        if args[i] == "--":
 
111
            remaining = args[i+1:]
 
112
            break
 
113
        arg = args[i].lstrip("-")
 
114
        name, equals, value = arg.partition("=")
 
115
        name = name.replace('-', '_')
 
116
        if not name in options:
 
117
            print_help()
 
118
            raise Error('Unrecognized command line option: %r' % name)
 
119
        option = options[name]
 
120
        if not equals:
 
121
            if option.type == bool:
 
122
                value = "true"
 
123
            else:
 
124
                raise Error('Option %r requires a value' % name)
 
125
        option.parse(value)
 
126
    if options.help:
 
127
        print_help()
 
128
        sys.exit(0)
 
129
 
 
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()
 
134
 
 
135
    return remaining
 
136
 
 
137
 
 
138
def parse_config_file(path, overwrite=True):
 
139
    """Parses and loads the Python config file at the given path."""
 
140
    config = {}
 
141
    execfile(path, config, config)
 
142
    for name in config:
 
143
        if name in options:
 
144
            options[name].set(config[name])
 
145
 
 
146
 
 
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]
 
150
    print >> file, ""
 
151
    print >> file, "Options:"
 
152
    by_file = {}
 
153
    for option in options.itervalues():
 
154
        by_file.setdefault(option.file_name, []).append(option)
 
155
 
 
156
    for filename, o in sorted(by_file.items()):
 
157
        if filename: print >> file, filename
 
158
        o.sort(key=lambda option: option.name)
 
159
        for option in o:
 
160
            prefix = option.name
 
161
            if option.metavar:
 
162
                prefix += "=" + option.metavar
 
163
            print >> file, "  --%-30s %s" % (prefix, option.help or "")
 
164
    print >> file
 
165
 
 
166
 
 
167
class _Options(dict):
 
168
    """Our global program options, an dictionary with object-like access."""
 
169
    @classmethod
 
170
    def instance(cls):
 
171
        if not hasattr(cls, "_instance"):
 
172
            cls._instance = cls()
 
173
        return cls._instance
 
174
 
 
175
    def __getattr__(self, name):
 
176
        if isinstance(self.get(name), _Option):
 
177
            return self[name].value()
 
178
        raise AttributeError("Unrecognized option %r" % name)
 
179
 
 
180
 
 
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:
 
185
            default = []
 
186
        self.name = name
 
187
        self.type = type
 
188
        self.help = help
 
189
        self.metavar = metavar
 
190
        self.multiple = multiple
 
191
        self.file_name = file_name
 
192
        self.default = default
 
193
        self._value = None
 
194
 
 
195
    def value(self):
 
196
        return self.default if self._value is None else self._value
 
197
 
 
198
    def parse(self, value):
 
199
        _parse = {
 
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)
 
205
        if self.multiple:
 
206
            if self._value is None:
 
207
                self._value = []
 
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(":")
 
212
                    lo = _parse(lo)
 
213
                    hi = _parse(hi) if hi else lo
 
214
                    self._value.extend(range(lo, hi+1))
 
215
                else:
 
216
                    self._value.append(_parse(part))
 
217
        else:
 
218
            self._value = _parse(value)
 
219
        return self.value()
 
220
 
 
221
    def set(self, value):
 
222
        if self.multiple:
 
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__))
 
226
            for item in value:
 
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__))
 
230
        else:
 
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__))
 
234
        self._value = value
 
235
 
 
236
    # Supported date/time formats in our options
 
237
    _DATETIME_FORMATS = [
 
238
        "%a %b %d %H:%M:%S %Y",
 
239
        "%Y-%m-%d %H:%M:%S",
 
240
        "%Y-%m-%d %H:%M",
 
241
        "%Y-%m-%dT%H:%M",
 
242
        "%Y%m%d %H:%M:%S",
 
243
        "%Y%m%d %H:%M",
 
244
        "%Y-%m-%d",
 
245
        "%Y%m%d",
 
246
        "%H:%M:%S",
 
247
        "%H:%M",
 
248
    ]
 
249
 
 
250
    def _parse_datetime(self, value):
 
251
        for format in self._DATETIME_FORMATS:
 
252
            try:
 
253
                return datetime.datetime.strptime(value, format)
 
254
            except ValueError:
 
255
                pass
 
256
        raise Error('Unrecognized date/time format: %r' % value)
 
257
 
 
258
    _TIMEDELTA_ABBREVS = [
 
259
        ('hours', ['h']),
 
260
        ('minutes', ['m', 'min']),
 
261
        ('seconds', ['s', 'sec']),
 
262
        ('milliseconds', ['ms']),
 
263
        ('microseconds', ['us']),
 
264
        ('days', ['d']),
 
265
        ('weeks', ['w']),
 
266
    ]
 
267
 
 
268
    _TIMEDELTA_ABBREV_DICT = dict(
 
269
        (abbrev, full) for full, abbrevs in _TIMEDELTA_ABBREVS
 
270
        for abbrev in abbrevs)
 
271
 
 
272
    _FLOAT_PATTERN = r'[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?'
 
273
 
 
274
    _TIMEDELTA_PATTERN = re.compile(
 
275
        r'\s*(%s)\s*(\w*)\s*' % _FLOAT_PATTERN, re.IGNORECASE)
 
276
 
 
277
    def _parse_timedelta(self, value):
 
278
        try:
 
279
            sum = datetime.timedelta()
 
280
            start = 0
 
281
            while start < len(value):
 
282
                m = self._TIMEDELTA_PATTERN.match(value, start)
 
283
                if not m:
 
284
                    raise Exception()
 
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})
 
289
                start = m.end()
 
290
            return sum
 
291
        except:
 
292
            raise
 
293
 
 
294
    def _parse_bool(self, value):
 
295
        return value.lower() not in ("false", "0", "f")
 
296
 
 
297
    def _parse_string(self, value):
 
298
        return value.decode("utf-8")
 
299
 
 
300
 
 
301
class Error(Exception):
 
302
    pass
 
303
 
 
304
 
 
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
 
310
        color = False
 
311
        if curses and sys.stderr.isatty():
 
312
            try:
 
313
                curses.setupterm()
 
314
                if curses.tigetnum("colors") > 0:
 
315
                    color = True
 
316
            except:
 
317
                pass
 
318
        channel = logging.StreamHandler()
 
319
        channel.setFormatter(_LogFormatter(color=color))
 
320
        logging.getLogger().addHandler(channel)
 
321
 
 
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)
 
329
 
 
330
 
 
331
class _LogFormatter(logging.Formatter):
 
332
    def __init__(self, color, *args, **kwargs):
 
333
        logging.Formatter.__init__(self, *args, **kwargs)
 
334
        self._color = color
 
335
        if color:
 
336
            fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or ""
 
337
            self._colors = {
 
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
 
342
            }
 
343
            self._normal = curses.tigetstr("sgr0")
 
344
 
 
345
    def format(self, record):
 
346
        try:
 
347
            record.message = record.getMessage()
 
348
        except Exception, e:
 
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]' % \
 
353
            record.__dict__
 
354
        if self._color:
 
355
            prefix = (self._colors.get(record.levelno, self._normal) +
 
356
                      prefix + self._normal)
 
357
        formatted = prefix + " " + record.message
 
358
        if record.exc_info:
 
359
            if not record.exc_text:
 
360
                record.exc_text = self.formatException(record.exc_info)
 
361
        if record.exc_text:
 
362
            formatted = formatted.rstrip() + "\n" + record.exc_text
 
363
        return formatted.replace("\n", "\n    ")
 
364
 
 
365
 
 
366
options = _Options.instance()
 
367
 
 
368
 
 
369
# Default options
 
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")