Source code for layeredconfig.commandline

import sys
import argparse

from six import text_type as str

from . import ConfigSource

UNIT_SEP = chr(31)

[docs]class Commandline(ConfigSource): rest = [] """The remainder of the command line, containing all parameters that couldn't be turned into configuration settings. """ def __init__(self, commandline = None, parser=None, sectionsep='-', add_help=True, **kwargs): """Load configuration from command line options. Any long-style parameters are turned into configuration values, and parameters containing the section separator (by default ``"-"``) are turned into nested config objects (i.e. ``--module-parameter=foo`` results in ``self.module.parameter == "foo"``. If an initialized ArgumentParser object is provided, the defined parameters in that object is used for supporting short form options (eg. ``'-f'`` instead of ``'--force'``), typing information and help text. The standards argparse feature of printing a helpful message when the '-h' option is given is retained. :param commandline: Command line arguments, in list form like :py:data:`sys.argv`. If not provided, uses the real :py:data:`sys.argv`. :type commandline: list :param parser: An initialized/configured argparse object :type parser: argparse.ArgumentParser :param sectionsep: An alternate section separator instead of ``-``. :type sectionsep: str :param add_help: Same as for ArgumentParser() :type add_help: bool """ # internal arguments: # * sectionkey: eg 'mymodule' or 'submodule_subsubmodule' etc super(Commandline, self).__init__(**kwargs) self.sectionsep = sectionsep self.sectionkey = kwargs.get('sectionkey', '') if commandline is None: if kwargs.get("empty"): self.commandline = [] else: self.commandline = sys.argv[1:] else: self.commandline = commandline self.autoargs = kwargs.get('autoargs', {}) if parser is None: if kwargs.get('parent'): # we're a subsection object, we don't need a parser. self.parser = None else: # create a "bootstrapping" argument parser self.parser = argparse.ArgumentParser() for arg in self.commandline: if arg.startswith("--"): argname = arg.split("=")[0][2:] if argname not in self.autoargs: # at this point we don't know anything about # this argument other than that it exists and # our bootstrapping argument parser should # handle it. # * use nargs='?' to allow arguments with or without # values. # * use const=True to treat valueless arguments as # set bool flags # * use action='append' to allow a single argument # (eg --extra) to be used multiple times with values) self.parser.add_argument("--%s" % argname, action='append', nargs='?', dest=argname.replace(self.sectionsep, UNIT_SEP), const=True) self.autoargs[argname] = True self._provided_parser = False else: self.parser = parser self._provided_parser = kwargs.get('provided_parser', True) self.writable = False # create a (possibly temporary) source (argparse.Namespace object) # unless we're a subsection object and already have been passed one. if kwargs.get('parent'): self.source = None # or {} ? else: if kwargs.get('source'): self.source = kwargs['source'] else: self.source, self.rest = self.parser.parse_known_args(self.commandline) if self._provided_parser and self.rest: # reconfigure our provided parser and try to add arguments # for every unprocessed long option. for arg in self.rest: if arg.startswith("--"): argname = arg.split("=")[0][2:] if (argname not in self.autoargs and argname not in self.source): self.parser.add_argument("--%s" % argname, action='append', nargs='?', dest=argname.replace(self.sectionsep, UNIT_SEP), const=True) self.autoargs[argname] = True # now redo the parsing self.source, self.rest = self.parser.parse_known_args(self.commandline)
[docs] def setup(self, config): if not self.parser: # we're in an empty subsection object return for key in config: # since this will be used to handle -h, we need to fill it # with helpy things (default types, default values, help strings) try: currentval = getattr(config, key) if currentval: currenttype = type(currentval) # select a good converter for string -> type kwargs = {'type': currenttype} else: kwargs = {} if key not in self.source: self.parser.add_argument('--%s' % key, action='append', nargs='?', const=True, dest=key.replace(self.sectionsep, UNIT_SEP), **kwargs) self.autoargs[key] = True except argparse.ArgumentError: # the parser already had this argument -- assume it's # fully configured with typing, help, and # everything. But it'd be nice if we could jam a # default value in there somehow pass # process everything and print help if -h is given self.source, self.rest = self.parser.parse_known_args(self.commandline)
[docs] def keys(self): if self.source: for arg in vars(self.source).keys(): if arg.startswith(self.sectionkey) and getattr(self.source, arg) is not None: k = arg[len(self.sectionkey):] if k.startswith(UNIT_SEP): k = k[1:] if UNIT_SEP not in k and getattr(self.source, arg) is not None: yield k
[docs] def has(self, key): if self.sectionkey: key = self.sectionkey + UNIT_SEP + key try: return getattr(self.source, key) is not None except AttributeError: return False
[docs] def get(self, key): if self.sectionkey: key = self.sectionkey + UNIT_SEP + key r = getattr(self.source, key) # undo the automatic list behaviour for autodiscovered # arguments (which has store='append') key = key.replace(UNIT_SEP, self.sectionsep) if (key in self.autoargs and isinstance(r, list) and len(r) == 1): return r[0] else: return r
[docs] def subsections(self): # argparse has no internal concept of subsections. We # construct one using arguments like '--mymodule-force'. These # are transformed into attributes like 'mymodule<UNIT_SEP>force' on an # argparse.Namespace object. Therefore, we can create a list # of subsections yielded = set() if self.source: subsectionsource = dict(self.source._get_kwargs()) else: subsectionsource = {} for args in subsectionsource.keys(): if args.startswith(self.sectionkey): args = args[len(self.sectionkey):] if args.startswith(UNIT_SEP): # sectionsep args = args[1:] else: continue # '_' is the hardcoded section separator once parse_args # has transformed the command line args into properties on # a Namespace object. if UNIT_SEP in args: section = args.split(UNIT_SEP)[0] if section not in yielded: yield(section) yielded.add(section)
[docs] def subsection(self, key): if self.sectionkey: key = self.sectionkey + UNIT_SEP + key return Commandline(self.commandline, sectionsep=self.sectionsep, source=self.source, parser=self.parser, provided_parser=self._provided_parser, autoargs=self.autoargs, sectionkey=key)
[docs] def set(self, key, value): setattr(self.source, key, value)
[docs] def typed(self, key): # if the config value has a non-string type, then it's typed if self.has(key) and not isinstance(self.get(key), str): return True if self._provided_parser: # a provided parser (not a bootstrapped parser) should be # able to convert input to typed data -- but only for # those arguments that were configured return self.has(key) and key not in self.autoargs else: # a boostrapped parser will support typing for bool # (valueless args) and lists (multiple args) return self.has(key) and not isinstance(self.get(key), str)