options.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Copyright 2009 Facebook
00004 #
00005 # Licensed under the Apache License, Version 2.0 (the "License"); you may
00006 # not use this file except in compliance with the License. You may obtain
00007 # a copy of the License at
00008 #
00009 #     http://www.apache.org/licenses/LICENSE-2.0
00010 #
00011 # Unless required by applicable law or agreed to in writing, software
00012 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
00013 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
00014 # License for the specific language governing permissions and limitations
00015 # under the License.
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         # we have to use self.__dict__ because we override setattr.
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             # All things after the last option are command line arguments
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         # Modify __dict__ directly to bypass __setattr__
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                     # allow ranges of the form X:Y (inclusive at both ends)
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     # Supported date/time formats in our options
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 # Default options
00553 define_logging_options(options)


rosbridge_tools
Author(s): Jonathan Mace
autogenerated on Sat Dec 27 2014 11:25:59