def test_consistent_order_of_args(intf, spec_posargs): f = getattr(intf, '__call__') args, varargs, varkw, defaults = getargspec(f) # now verify that those spec_posargs are first among args if not spec_posargs: pytest.skip("no positional args") eq_(set(args[:len(spec_posargs)]), spec_posargs)
def call_from_parser(cls, args): # XXX needs safety check for name collisions from reproman.utils import getargspec argnames = getargspec(cls.__call__)[0] kwargs = {k: getattr(args, k) for k in argnames if k != 'self'} try: return cls.__call__(**kwargs) except KeyboardInterrupt: ui.error("\nInterrupted by user while doing magic") sys.exit(1)
def setup_parser(cls, parser): # XXX needs safety check for name collisions # XXX allow for parser kwargs customization parser_kwargs = {} from reproman.utils import getargspec # get the signature ndefaults = 0 args, varargs, varkw, defaults = getargspec(cls.__call__) if not defaults is None: ndefaults = len(defaults) for i, arg in enumerate(args): if arg == 'self': continue param = cls._params_[arg] defaults_idx = ndefaults - len(args) + i cmd_args = param.cmd_args if cmd_args is None: cmd_args = [] if not len(cmd_args): if defaults_idx >= 0: # dealing with a kwarg template = '--%s' else: # positional arg template = '%s' # use parameter name as default argument name parser_args = (template % arg.replace('_', '-'),) else: parser_args = [c.replace('_', '-') for c in cmd_args] parser_kwargs = param.cmd_kwargs if defaults_idx >= 0: parser_kwargs['default'] = defaults[defaults_idx] help = alter_interface_docs_for_cmdline(param._doc) if help and help[-1] != '.': help += '.' if param.constraints is not None: parser_kwargs['type'] = param.constraints # include value constraint description and default # into the help string cdoc = alter_interface_docs_for_cmdline( param.constraints.long_description()) if cdoc[0] == '(' and cdoc[-1] == ')': cdoc = cdoc[1:-1] help += ' Constraints: %s' % cdoc if defaults_idx >= 0: help += " [Default: %r]" % (defaults[defaults_idx],) # create the parameter, using the constraint instance for type # conversion parser.add_argument(*parser_args, help=help, **parser_kwargs)
def update_docstring_with_parameters(func, params, prefix=None, suffix=None): """Generate a useful docstring from a parameter spec Amends any existing docstring of a callable with a textual description of its parameters. The Parameter spec needs to match the number and names of the callables arguments. """ from reproman.utils import getargspec # get the signature ndefaults = 0 args, varargs, varkw, defaults = getargspec(func) if not defaults is None: ndefaults = len(defaults) # start documentation with what the callable brings with it doc = prefix if prefix else u'' if len(args) > 1: if len(doc): doc += '\n' doc += "Parameters\n----------\n" for i, arg in enumerate(args): if arg == 'self': continue # we need a parameter spec for each argument if not arg in params: raise ValueError( "function has argument '%s' not described as a parameter" % arg) param = params[arg] # validate the default -- to make sure that the parameter description is # somewhat OK defaults_idx = ndefaults - len(args) + i if defaults_idx >= 0: if not param.constraints is None: param.constraints(defaults[defaults_idx]) orig_docs = param._doc param._doc = alter_interface_docs_for_api(param._doc) doc += param.get_autodoc( arg, default=defaults[defaults_idx] if defaults_idx >= 0 else None, has_default=defaults_idx >= 0) param._doc = orig_docs doc += '\n' doc += suffix if suffix else u"" # assign the amended docs func.__doc__ = doc return func
class Parameter(object): """This class shall serve as a representation of a parameter. """ # Known keyword arguments which we want to allow to pass over into # argparser.add_argument . Mentioned explicitly, since otherwise # are not verified while working in Python-only API _KNOWN_ARGS = getargspec(argparse.Action.__init__)[0] + ['action'] def __init__(self, constraints=None, doc=None, args=None, **kwargs): """Add constraints (validator) specifications and a docstring for a parameter. Parameters ---------- constraints : callable A functor that takes any input value, performs checks or type conversions and finally returns a value that is appropriate for a parameter or raises an exception. This will also be used to set up the ``type`` functionality of argparse.add_argument. doc : str Documentation about the purpose of this parameter. args : tuple or None Any additional positional args for argparser.add_argument. This is most useful for assigned multiple alternative argument names or create positional arguments. **kwargs : Any additional keyword args for argparser.add_argument. Examples -------- Ensure a parameter is a float >>> from reproman.support.param import Parameter >>> from reproman.support.constraints import (EnsureFloat, EnsureRange, ... AltConstraints, Constraints) >>> C = Parameter(constraints=EnsureFloat()) Ensure a parameter is of type float or None: >>> C = Parameter(constraints=AltConstraints(EnsureFloat(), None)) Ensure a parameter is None or of type float and lies in the inclusive range (7.0,44.0): >>> C = Parameter( ... AltConstraints( ... Constraints(EnsureFloat(), ... EnsureRange(min=7.0, max=44.0)), ... None)) """ self.constraints = expand_constraint_spec(constraints) self._doc = doc self.cmd_args = args # Verify that no mistyped kwargs present unknown_args = set(kwargs).difference(self._KNOWN_ARGS) if unknown_args: raise ValueError( "Detected unknown argument(s) for the Parameter: %s. Known are: %s" % (', '.join(unknown_args), ', '.join(self._KNOWN_ARGS))) self.cmd_kwargs = kwargs def get_autodoc(self, name, indent=" ", width=70, default=None, has_default=False): """Docstring for the parameter to be used in lists of parameters Returns ------- string or list of strings (if indent is None) """ paramsdoc = '%s' % name sdoc = None if self.constraints is not None: sdoc = self.constraints.short_description() elif 'action' in self.cmd_kwargs \ and self.cmd_kwargs['action'] in ("store_true", "store_false"): sdoc = 'bool' if not sdoc is None: if sdoc[0] == '(' and sdoc[-1] == ')': sdoc = sdoc[1:-1] paramsdoc += " : %s" % sdoc if has_default: paramsdoc += ", optional" paramsdoc = [paramsdoc] doc = self._doc if doc is None: doc = '' doc.strip() if len(doc) and not doc.endswith('.'): doc += '.' if self.constraints is not None: cdoc = self.constraints.long_description() if cdoc[0] == '(' and cdoc[-1] == ')': cdoc = cdoc[1:-1] addinfo = '' if 'nargs' in self.cmd_kwargs \ and not self.cmd_kwargs['nargs'] == '?': addinfo = 'list expected, each ' doc += ' Constraints: %s%s.' % (addinfo, cdoc) if has_default: doc += " [Default: %r]" % (default, ) # Explicitly deal with multiple spaces, for some reason # replace_whitespace is non-effective doc = _whitespace_re.sub(' ', doc) paramsdoc += [ indent + x for x in textwrap.wrap( doc, width=width - len(indent), replace_whitespace=True) ] return '\n'.join(paramsdoc)