00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 """A command line parsing module that lets modules define their own options.
00018
00019 Each module defines its own options which are added to the global
00020 option namespace, e.g.::
00021
00022 from tornado.options import define, options
00023
00024 define("mysql_host", default="127.0.0.1:3306", help="Main user DB")
00025 define("memcache_hosts", default="127.0.0.1:11011", multiple=True,
00026 help="Main user memcache servers")
00027
00028 def connect():
00029 db = database.Connection(options.mysql_host)
00030 ...
00031
00032 The ``main()`` method of your application does not need to be aware of all of
00033 the options used throughout your program; they are all automatically loaded
00034 when the modules are loaded. However, all modules that define options
00035 must have been imported before the command line is parsed.
00036
00037 Your ``main()`` method can parse the command line or parse a config file with
00038 either::
00039
00040 tornado.options.parse_command_line()
00041 # or
00042 tornado.options.parse_config_file("/etc/server.conf")
00043
00044 Command line formats are what you would expect (``--myoption=myvalue``).
00045 Config files are just Python files. Global names become options, e.g.::
00046
00047 myoption = "myvalue"
00048 myotheroption = "myothervalue"
00049
00050 We support `datetimes <datetime.datetime>`, `timedeltas
00051 <datetime.timedelta>`, ints, and floats (just pass a ``type`` kwarg to
00052 `define`). We also accept multi-value options. See the documentation for
00053 `define()` below.
00054
00055 `tornado.options.options` is a singleton instance of `OptionParser`, and
00056 the top-level functions in this module (`define`, `parse_command_line`, etc)
00057 simply call methods on it. You may create additional `OptionParser`
00058 instances to define isolated sets of options, such as for subcommands.
00059
00060 .. note::
00061
00062 By default, several options are defined that will configure the
00063 standard `logging` module when `parse_command_line` or `parse_config_file`
00064 are called. If you want Tornado to leave the logging configuration
00065 alone so you can manage it yourself, either pass ``--logging=none``
00066 on the command line or do the following to disable it in code::
00067
00068 from tornado.options import options, parse_command_line
00069 options.logging = None
00070 parse_command_line()
00071 """
00072
00073 from __future__ import absolute_import, division, print_function, with_statement
00074
00075 import datetime
00076 import numbers
00077 import re
00078 import sys
00079 import os
00080 import textwrap
00081
00082 from tornado.escape import _unicode
00083 from tornado.log import define_logging_options
00084 from tornado import stack_context
00085 from tornado.util import basestring_type, exec_in
00086
00087
00088 class Error(Exception):
00089 """Exception raised by errors in the options module."""
00090 pass
00091
00092
00093 class OptionParser(object):
00094 """A collection of options, a dictionary with object-like access.
00095
00096 Normally accessed via static functions in the `tornado.options` module,
00097 which reference a global instance.
00098 """
00099 def __init__(self):
00100
00101 self.__dict__['_options'] = {}
00102 self.__dict__['_parse_callbacks'] = []
00103 self.define("help", type=bool, help="show this help information",
00104 callback=self._help_callback)
00105
00106 def __getattr__(self, name):
00107 if isinstance(self._options.get(name), _Option):
00108 return self._options[name].value()
00109 raise AttributeError("Unrecognized option %r" % name)
00110
00111 def __setattr__(self, name, value):
00112 if isinstance(self._options.get(name), _Option):
00113 return self._options[name].set(value)
00114 raise AttributeError("Unrecognized option %r" % name)
00115
00116 def __iter__(self):
00117 return iter(self._options)
00118
00119 def __getitem__(self, item):
00120 return self._options[item].value()
00121
00122 def items(self):
00123 """A sequence of (name, value) pairs.
00124
00125 .. versionadded:: 3.1
00126 """
00127 return [(name, opt.value()) for name, opt in self._options.items()]
00128
00129 def groups(self):
00130 """The set of option-groups created by ``define``.
00131
00132 .. versionadded:: 3.1
00133 """
00134 return set(opt.group_name for opt in self._options.values())
00135
00136 def group_dict(self, group):
00137 """The names and values of options in a group.
00138
00139 Useful for copying options into Application settings::
00140
00141 from tornado.options import define, parse_command_line, options
00142
00143 define('template_path', group='application')
00144 define('static_path', group='application')
00145
00146 parse_command_line()
00147
00148 application = Application(
00149 handlers, **options.group_dict('application'))
00150
00151 .. versionadded:: 3.1
00152 """
00153 return dict(
00154 (name, opt.value()) for name, opt in self._options.items()
00155 if not group or group == opt.group_name)
00156
00157 def as_dict(self):
00158 """The names and values of all options.
00159
00160 .. versionadded:: 3.1
00161 """
00162 return dict(
00163 (name, opt.value()) for name, opt in self._options.items())
00164
00165 def define(self, name, default=None, type=None, help=None, metavar=None,
00166 multiple=False, group=None, callback=None):
00167 """Defines a new command line option.
00168
00169 If ``type`` is given (one of str, float, int, datetime, or timedelta)
00170 or can be inferred from the ``default``, we parse the command line
00171 arguments based on the given type. If ``multiple`` is True, we accept
00172 comma-separated values, and the option value is always a list.
00173
00174 For multi-value integers, we also accept the syntax ``x:y``, which
00175 turns into ``range(x, y)`` - very useful for long integer ranges.
00176
00177 ``help`` and ``metavar`` are used to construct the
00178 automatically generated command line help string. The help
00179 message is formatted like::
00180
00181 --name=METAVAR help string
00182
00183 ``group`` is used to group the defined options in logical
00184 groups. By default, command line options are grouped by the
00185 file in which they are defined.
00186
00187 Command line option names must be unique globally. They can be parsed
00188 from the command line with `parse_command_line` or parsed from a
00189 config file with `parse_config_file`.
00190
00191 If a ``callback`` is given, it will be run with the new value whenever
00192 the option is changed. This can be used to combine command-line
00193 and file-based options::
00194
00195 define("config", type=str, help="path to config file",
00196 callback=lambda path: parse_config_file(path, final=False))
00197
00198 With this definition, options in the file specified by ``--config`` will
00199 override options set earlier on the command line, but can be overridden
00200 by later flags.
00201 """
00202 if name in self._options:
00203 raise Error("Option %r already defined in %s" %
00204 (name, self._options[name].file_name))
00205 frame = sys._getframe(0)
00206 options_file = frame.f_code.co_filename
00207 file_name = frame.f_back.f_code.co_filename
00208 if file_name == options_file:
00209 file_name = ""
00210 if type is None:
00211 if not multiple and default is not None:
00212 type = default.__class__
00213 else:
00214 type = str
00215 if group:
00216 group_name = group
00217 else:
00218 group_name = file_name
00219 self._options[name] = _Option(name, file_name=file_name,
00220 default=default, type=type, help=help,
00221 metavar=metavar, multiple=multiple,
00222 group_name=group_name,
00223 callback=callback)
00224
00225 def parse_command_line(self, args=None, final=True):
00226 """Parses all options given on the command line (defaults to
00227 `sys.argv`).
00228
00229 Note that ``args[0]`` is ignored since it is the program name
00230 in `sys.argv`.
00231
00232 We return a list of all arguments that are not parsed as options.
00233
00234 If ``final`` is ``False``, parse callbacks will not be run.
00235 This is useful for applications that wish to combine configurations
00236 from multiple sources.
00237 """
00238 if args is None:
00239 args = sys.argv
00240 remaining = []
00241 for i in range(1, len(args)):
00242
00243 if not args[i].startswith("-"):
00244 remaining = args[i:]
00245 break
00246 if args[i] == "--":
00247 remaining = args[i + 1:]
00248 break
00249 arg = args[i].lstrip("-")
00250 name, equals, value = arg.partition("=")
00251 name = name.replace('-', '_')
00252 if not name in self._options:
00253 self.print_help()
00254 raise Error('Unrecognized command line option: %r' % name)
00255 option = self._options[name]
00256 if not equals:
00257 if option.type == bool:
00258 value = "true"
00259 else:
00260 raise Error('Option %r requires a value' % name)
00261 option.parse(value)
00262
00263 if final:
00264 self.run_parse_callbacks()
00265
00266 return remaining
00267
00268 def parse_config_file(self, path, final=True):
00269 """Parses and loads the Python config file at the given path.
00270
00271 If ``final`` is ``False``, parse callbacks will not be run.
00272 This is useful for applications that wish to combine configurations
00273 from multiple sources.
00274 """
00275 config = {}
00276 with open(path) as f:
00277 exec_in(f.read(), config, config)
00278 for name in config:
00279 if name in self._options:
00280 self._options[name].set(config[name])
00281
00282 if final:
00283 self.run_parse_callbacks()
00284
00285 def print_help(self, file=None):
00286 """Prints all the command line options to stderr (or another file)."""
00287 if file is None:
00288 file = sys.stderr
00289 print("Usage: %s [OPTIONS]" % sys.argv[0], file=file)
00290 print("\nOptions:\n", file=file)
00291 by_group = {}
00292 for option in self._options.values():
00293 by_group.setdefault(option.group_name, []).append(option)
00294
00295 for filename, o in sorted(by_group.items()):
00296 if filename:
00297 print("\n%s options:\n" % os.path.normpath(filename), file=file)
00298 o.sort(key=lambda option: option.name)
00299 for option in o:
00300 prefix = option.name
00301 if option.metavar:
00302 prefix += "=" + option.metavar
00303 description = option.help or ""
00304 if option.default is not None and option.default != '':
00305 description += " (default %s)" % option.default
00306 lines = textwrap.wrap(description, 79 - 35)
00307 if len(prefix) > 30 or len(lines) == 0:
00308 lines.insert(0, '')
00309 print(" --%-30s %s" % (prefix, lines[0]), file=file)
00310 for line in lines[1:]:
00311 print("%-34s %s" % (' ', line), file=file)
00312 print(file=file)
00313
00314 def _help_callback(self, value):
00315 if value:
00316 self.print_help()
00317 sys.exit(0)
00318
00319 def add_parse_callback(self, callback):
00320 """Adds a parse callback, to be invoked when option parsing is done."""
00321 self._parse_callbacks.append(stack_context.wrap(callback))
00322
00323 def run_parse_callbacks(self):
00324 for callback in self._parse_callbacks:
00325 callback()
00326
00327 def mockable(self):
00328 """Returns a wrapper around self that is compatible with
00329 `mock.patch <unittest.mock.patch>`.
00330
00331 The `mock.patch <unittest.mock.patch>` function (included in
00332 the standard library `unittest.mock` package since Python 3.3,
00333 or in the third-party ``mock`` package for older versions of
00334 Python) is incompatible with objects like ``options`` that
00335 override ``__getattr__`` and ``__setattr__``. This function
00336 returns an object that can be used with `mock.patch.object
00337 <unittest.mock.patch.object>` to modify option values::
00338
00339 with mock.patch.object(options.mockable(), 'name', value):
00340 assert options.name == value
00341 """
00342 return _Mockable(self)
00343
00344
00345 class _Mockable(object):
00346 """`mock.patch` compatible wrapper for `OptionParser`.
00347
00348 As of ``mock`` version 1.0.1, when an object uses ``__getattr__``
00349 hooks instead of ``__dict__``, ``patch.__exit__`` tries to delete
00350 the attribute it set instead of setting a new one (assuming that
00351 the object does not catpure ``__setattr__``, so the patch
00352 created a new attribute in ``__dict__``).
00353
00354 _Mockable's getattr and setattr pass through to the underlying
00355 OptionParser, and delattr undoes the effect of a previous setattr.
00356 """
00357 def __init__(self, options):
00358
00359 self.__dict__['_options'] = options
00360 self.__dict__['_originals'] = {}
00361
00362 def __getattr__(self, name):
00363 return getattr(self._options, name)
00364
00365 def __setattr__(self, name, value):
00366 assert name not in self._originals, "don't reuse mockable objects"
00367 self._originals[name] = getattr(self._options, name)
00368 setattr(self._options, name, value)
00369
00370 def __delattr__(self, name):
00371 setattr(self._options, name, self._originals.pop(name))
00372
00373
00374 class _Option(object):
00375 UNSET = object()
00376
00377 def __init__(self, name, default=None, type=basestring_type, help=None,
00378 metavar=None, multiple=False, file_name=None, group_name=None,
00379 callback=None):
00380 if default is None and multiple:
00381 default = []
00382 self.name = name
00383 self.type = type
00384 self.help = help
00385 self.metavar = metavar
00386 self.multiple = multiple
00387 self.file_name = file_name
00388 self.group_name = group_name
00389 self.callback = callback
00390 self.default = default
00391 self._value = _Option.UNSET
00392
00393 def value(self):
00394 return self.default if self._value is _Option.UNSET else self._value
00395
00396 def parse(self, value):
00397 _parse = {
00398 datetime.datetime: self._parse_datetime,
00399 datetime.timedelta: self._parse_timedelta,
00400 bool: self._parse_bool,
00401 basestring_type: self._parse_string,
00402 }.get(self.type, self.type)
00403 if self.multiple:
00404 self._value = []
00405 for part in value.split(","):
00406 if issubclass(self.type, numbers.Integral):
00407
00408 lo, _, hi = part.partition(":")
00409 lo = _parse(lo)
00410 hi = _parse(hi) if hi else lo
00411 self._value.extend(range(lo, hi + 1))
00412 else:
00413 self._value.append(_parse(part))
00414 else:
00415 self._value = _parse(value)
00416 if self.callback is not None:
00417 self.callback(self._value)
00418 return self.value()
00419
00420 def set(self, value):
00421 if self.multiple:
00422 if not isinstance(value, list):
00423 raise Error("Option %r is required to be a list of %s" %
00424 (self.name, self.type.__name__))
00425 for item in value:
00426 if item is not None and not isinstance(item, self.type):
00427 raise Error("Option %r is required to be a list of %s" %
00428 (self.name, self.type.__name__))
00429 else:
00430 if value is not None and not isinstance(value, self.type):
00431 raise Error("Option %r is required to be a %s (%s given)" %
00432 (self.name, self.type.__name__, type(value)))
00433 self._value = value
00434 if self.callback is not None:
00435 self.callback(self._value)
00436
00437
00438 _DATETIME_FORMATS = [
00439 "%a %b %d %H:%M:%S %Y",
00440 "%Y-%m-%d %H:%M:%S",
00441 "%Y-%m-%d %H:%M",
00442 "%Y-%m-%dT%H:%M",
00443 "%Y%m%d %H:%M:%S",
00444 "%Y%m%d %H:%M",
00445 "%Y-%m-%d",
00446 "%Y%m%d",
00447 "%H:%M:%S",
00448 "%H:%M",
00449 ]
00450
00451 def _parse_datetime(self, value):
00452 for format in self._DATETIME_FORMATS:
00453 try:
00454 return datetime.datetime.strptime(value, format)
00455 except ValueError:
00456 pass
00457 raise Error('Unrecognized date/time format: %r' % value)
00458
00459 _TIMEDELTA_ABBREVS = [
00460 ('hours', ['h']),
00461 ('minutes', ['m', 'min']),
00462 ('seconds', ['s', 'sec']),
00463 ('milliseconds', ['ms']),
00464 ('microseconds', ['us']),
00465 ('days', ['d']),
00466 ('weeks', ['w']),
00467 ]
00468
00469 _TIMEDELTA_ABBREV_DICT = dict(
00470 (abbrev, full) for full, abbrevs in _TIMEDELTA_ABBREVS
00471 for abbrev in abbrevs)
00472
00473 _FLOAT_PATTERN = r'[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?'
00474
00475 _TIMEDELTA_PATTERN = re.compile(
00476 r'\s*(%s)\s*(\w*)\s*' % _FLOAT_PATTERN, re.IGNORECASE)
00477
00478 def _parse_timedelta(self, value):
00479 try:
00480 sum = datetime.timedelta()
00481 start = 0
00482 while start < len(value):
00483 m = self._TIMEDELTA_PATTERN.match(value, start)
00484 if not m:
00485 raise Exception()
00486 num = float(m.group(1))
00487 units = m.group(2) or 'seconds'
00488 units = self._TIMEDELTA_ABBREV_DICT.get(units, units)
00489 sum += datetime.timedelta(**{units: num})
00490 start = m.end()
00491 return sum
00492 except Exception:
00493 raise
00494
00495 def _parse_bool(self, value):
00496 return value.lower() not in ("false", "0", "f")
00497
00498 def _parse_string(self, value):
00499 return _unicode(value)
00500
00501
00502 options = OptionParser()
00503 """Global options object.
00504
00505 All defined options are available as attributes on this object.
00506 """
00507
00508
00509 def define(name, default=None, type=None, help=None, metavar=None,
00510 multiple=False, group=None, callback=None):
00511 """Defines an option in the global namespace.
00512
00513 See `OptionParser.define`.
00514 """
00515 return options.define(name, default=default, type=type, help=help,
00516 metavar=metavar, multiple=multiple, group=group,
00517 callback=callback)
00518
00519
00520 def parse_command_line(args=None, final=True):
00521 """Parses global options from the command line.
00522
00523 See `OptionParser.parse_command_line`.
00524 """
00525 return options.parse_command_line(args, final=final)
00526
00527
00528 def parse_config_file(path, final=True):
00529 """Parses global options from a config file.
00530
00531 See `OptionParser.parse_config_file`.
00532 """
00533 return options.parse_config_file(path, final=final)
00534
00535
00536 def print_help(file=None):
00537 """Prints all the command line options to stderr (or another file).
00538
00539 See `OptionParser.print_help`.
00540 """
00541 return options.print_help(file)
00542
00543
00544 def add_parse_callback(callback):
00545 """Adds a parse callback, to be invoked when option parsing is done.
00546
00547 See `OptionParser.add_parse_callback`
00548 """
00549 options.add_parse_callback(callback)
00550
00551
00552
00553 define_logging_options(options)