def _validate_signature(func, locator): act_stack = list(_inspect_signature(func).parameters.values()) exp_stack = list(_formals) while True: exp_name = exp_stack.pop() if 0 == len(act_stack): xx(f"{locator!r} must take at least {len(_formals)} params") param = act_stack.pop() while True: # If this one has a default, assume it's an optional kw arg at end if param.default == param.empty: break param = act_stack.pop() if exp_name != param.name: xx("this will probably change but very strict for now: " f"{locator!r} parameter {param.name!r} should be " f"called {exp_name!r}") if param.kind != param.POSITIONAL_OR_KEYWORD: xx(f"expected {locator!r} parameter {param.name!r} " f"to be POSITIONAL_OR_KEYWORD, had {param.kind!s}") if 0 == len(exp_stack): break res = len(act_stack) assert all(param.kind == param.POSITIONAL_OR_KEYWORD for param in act_stack) # right? getting crazy return res
def _call_function(func, args): positionals = [] keywords = {} sig = _inspect_signature(func) for name, param in sig.parameters.items(): arg = getattr(args, name) if param.kind in [param.POSITIONAL_ONLY, param.POSITIONAL_OR_KEYWORD]: positionals.append(arg) elif param.kind == param.VAR_POSITIONAL: positionals.extend(arg) else: keywords[name] = arg return func(*positionals, **keywords)
def _public_signature(func): full_sig = _inspect_signature(func) return full_sig.replace( parameters=list(param for param in full_sig.parameters.values() if not param.name.startswith('_')))
def _populate_parser(func, parser, parsers, short): sig = _inspect_signature(func) doc = _parse_doc(func) hints = _get_type_hints(func) parser.description = doc.text types = dict((name, _get_type(func, name, doc, hints)) for name, param in sig.parameters.items()) positionals = set( name for name, param in sig.parameters.items() if (param.default == param.empty and not types[name].container and param.kind != param.KEYWORD_ONLY)) if short is None: count_initials = Counter(name[0] for name in sig.parameters if name not in positionals) short = dict( (name.replace('_', '-'), name[0]) for name in sig.parameters if name not in positionals and count_initials[name[0]] == 1) for name, param in sig.parameters.items(): kwargs = {} if name in doc.params: help_ = doc.params[name].text if help_ is not None: kwargs['help'] = help_.replace('%', '%%') type_ = types[name] if param.kind == param.VAR_KEYWORD: raise ValueError('**kwargs not supported') hasdefault = param.default != param.empty default = param.default if hasdefault else SUPPRESS required = not hasdefault and param.kind != param.VAR_POSITIONAL positional = name in positionals if type_.type == bool and not positional and not type_.container: # Special case: just add parameterless --name and --no-name flags. group = parser.add_mutually_exclusive_group(required=required) _add_argument( group, name, short, action='store_true', default=default, # Add help if available. **kwargs) _add_argument(group, 'no-' + name, short, action='store_false', default=default, dest=name) continue if positional: kwargs['_positional'] = True if param.kind == param.VAR_POSITIONAL: kwargs['nargs'] = '*' # This is purely to override the displayed default of None. # Ideally we wouldn't want to show a default at all. kwargs['default'] = [] else: kwargs['required'] = required kwargs['default'] = default if type_.container: assert type_.container == list kwargs['nargs'] = '*' if param.kind == param.VAR_POSITIONAL: kwargs['action'] = 'append' kwargs['default'] = [] if inspect.isclass(type_.type) and issubclass(type_.type, Enum): # Want these to behave like argparse choices. kwargs['choices'] = _ValueOrderedDict( (x.name, x) for x in type_.type) kwargs['type'] = _enum_getter(type_.type) else: kwargs['type'] = _get_parser(type_.type, parsers) _add_argument(parser, name, short, **kwargs)
def _populate_parser(func, parser, parsers, short, strict_kwonly): full_sig = _inspect_signature(func) sig = full_sig.replace(parameters=list( param for param in full_sig.parameters.values() if not param.name.startswith('_'))) doc = _parse_function_docstring(func) hints = _get_type_hints(func) parser.description = doc.text types = dict((name, _get_type(func, name, doc, hints)) for name, param in sig.parameters.items()) positionals = set( name for name, param in sig.parameters.items() if ((param.default is param.empty or strict_kwonly) and not types[name].container and param.kind != param.KEYWORD_ONLY)) if short is None: count_initials = Counter(name[0] for name in sig.parameters if name not in positionals) short = dict( (name.replace('_', '-'), name[0]) for name in sig.parameters if name not in positionals and count_initials[name[0]] == 1) for name, param in sig.parameters.items(): kwargs = {} if name in doc.params: help_ = doc.params[name].text if help_ is not None: kwargs['help'] = help_.replace('%', '%%') type_ = types[name] if param.kind == param.VAR_KEYWORD: raise ValueError('**kwargs not supported') hasdefault = param.default is not param.empty default = param.default if hasdefault else SUPPRESS required = not hasdefault and param.kind != param.VAR_POSITIONAL positional = name in positionals if type_.type == bool and not positional and not type_.container: # Special case: just add parameterless --name and --no-name flags. group = parser.add_mutually_exclusive_group(required=required) _add_argument( group, name, short, action='store_true', default=default, # Add help if available. **kwargs) _add_argument(group, 'no-' + name, short, action='store_false', default=default, dest=name) continue if positional: kwargs['_positional'] = True if param.default is not param.empty: kwargs['nargs'] = '?' kwargs['default'] = default if param.kind == param.VAR_POSITIONAL: kwargs['nargs'] = '*' # This is purely to override the displayed default of None. # Ideally we wouldn't want to show a default at all. kwargs['default'] = [] else: kwargs['required'] = required kwargs['default'] = default if type_.container: assert type_.container == list kwargs['nargs'] = '*' if param.kind == param.VAR_POSITIONAL: kwargs['action'] = 'append' kwargs['default'] = [] make_tuple = member_types = None if _is_generic_type(type_.type, Tuple): make_tuple = tuple member_types = type_.type.__args__ kwargs['nargs'] = len(member_types) kwargs['action'] = _make_store_tuple_action_class( tuple, member_types, parsers) elif (inspect.isclass(type_.type) and issubclass(type_.type, tuple) and hasattr(type_.type, '_fields') and hasattr(type_.type, '_field_types')): # Before Py3.6, `_field_types` does not preserve order, so retrieve # the order from `_fields`. member_types = tuple(type_.type._field_types[field] for field in type_.type._fields) kwargs['nargs'] = len(member_types) kwargs['action'] = _make_store_tuple_action_class( lambda args, type_=type_: type_.type(*args), member_types, parsers) if not positional: # http://bugs.python.org/issue14074 kwargs['metavar'] = type_.type._fields elif inspect.isclass(type_.type) and issubclass(type_.type, Enum): # Want these to behave like argparse choices. kwargs['choices'] = _ValueOrderedDict( (x.name, x) for x in type_.type) kwargs['type'] = _enum_getter(type_.type) else: kwargs['type'] = _get_parser(type_.type, parsers) _add_argument(parser, name, short, **kwargs)