def _argument_to_argparse_input(arg): # type: (Any) -> Tuple[List, Dict[str, Any]] add_argument_kwargs = {"help": arg.description} if arg.positional: add_argument_args = [arg.name] if arg.extra_names: msg = "Aliases are not yet supported for positional arguments @ {}".format( arg.name) raise ValueError(msg) if arg.default_value_set: msg = ("Positional arguments with default values are " "not supported @ {}".format(arg.name)) raise ValueError(msg) else: add_argument_args = [ transform_argument_name(x) for x in ([arg.name] + arg.extra_names) ] add_argument_kwargs["default"] = arg.default_value add_argument_kwargs["required"] = not arg.default_value_set argument_type = (arg.type if not is_optional(arg.type) else get_first_type_variable(arg.type)) if argument_type in [int, float, str]: add_argument_kwargs["type"] = argument_type add_argument_kwargs["metavar"] = str(argument_type.__name__).upper() elif argument_type == bool or arg.default_value is False: add_argument_kwargs["action"] = "store_true" elif arg.default_value is True: add_argument_kwargs["action"] = "store_false" elif issubclass_(argument_type, Mapping): add_argument_kwargs["type"] = _parse_dict(argument_type) add_argument_kwargs["metavar"] = "DICT[{}: {}]".format( *get_dict_kv_arg_type_as_str(argument_type)) elif issubclass_(argument_type, Iterable): add_argument_kwargs["type"] = get_first_type_variable(argument_type) add_argument_kwargs["nargs"] = "+" add_argument_kwargs["metavar"] = "{}".format( get_list_arg_type_as_str(argument_type)) else: add_argument_kwargs["type"] = argument_type if arg.choices: add_argument_kwargs["choices"] = arg.choices add_argument_kwargs["help"] += " (choose from {})".format(", ".join( map(str, arg.choices))) if arg.positional and "metavar" in add_argument_kwargs: add_argument_kwargs["metavar"] = "{}<{}>".format( arg.name, add_argument_kwargs["metavar"]) return add_argument_args, add_argument_kwargs
def _build_simple_value(string, type): if not type or issubclass_(type, str): return string elif issubclass_(type, collections.Mapping): entries = (re.split(r"\s*[:=]\s*", entry, maxsplit=1) for entry in string.split(";")) if is_dict_value_iterable(type): entries = ((k, re.split(r"\s*,\s*", v)) for k, v in entries) return {k.strip(): v for k, v in entries} elif issubclass_(type, tuple): return tuple(item for item in string.split(",")) elif issubclass_(type, collections.Iterable): return [item for item in string.split(",")] else: return string
def get_list_arg_type_as_str(type): """ This takes a type (typing.List[int]) and returns a string representation of the type argument, or "any" if it's not defined """ assert issubclass_(type, collections.Iterable) args = getattr(type, "__args__", None) return args[0].__name__ if args else "any"
def get_typing_function(type): func = None # TypeVars are a problem as they can defined multiple possible types. # While a single type TypeVar is somewhat useless, no reason to deny it # though if type == typing.TypeVar: subtypes = type.__constraints__ if len(subtypes) != 1: raise ValueError("Cannot resolve typing function for TypeVar({}) " "as it declares none or multiple types".format( ", ".format(str(x) for x in subtypes))) func = get_typing_function(subtypes[0]) elif type == typing.Any: func = _identity_function elif issubclass_(type, str): func = str elif issubclass_(type, collections.Mapping): func = _apply_dict_type elif issubclass_(type, tuple): func = _apply_tuple_type elif issubclass_(type, collections.Iterable): func = _apply_list_type elif is_union(type): func = _apply_optional_type elif callable(type): func = type else: raise ValueError( 'Cannot find a function to apply type "{}"'.format(type)) args = getattr(type, "__args__", None) if args: # this can be a Generic type from the typing module, like # List[str], Mapping[int, str] and so on. In that case we need to # also deal with the generic typing args_types = [get_typing_function(arg) for arg in args] func = _partial_builder(args_types)(func) return func
def get_dict_kv_arg_type_as_str(type): """ This takes a type (typing.Mapping[str, int]) and returns a tuple (key_type, value_type) that contains string representations of the type arguments, or "any" if it's not defined """ assert issubclass_(type, collections.Mapping) args = getattr(type, "__args__", None) key_type = "any" value_type = "any" if args and len(args) >= 2: key_type = getattr(args[0], "__name__", str(args[0])) value_type = getattr(args[1], "__name__", str(args[1])) return key_type, value_type
def get_typing_function(tp): func = None # TypeVars are a problem as they can defined multiple possible types. # While a single type TypeVar is somewhat useless, no reason to deny it # though if is_typevar(tp): if len(tp.__constraints__) == 0: # Unconstrained TypeVars may come from generics func = _identity_function elif len(tp.__constraints__) == 1: assert not NEW_TYPING, "Python 3.7+ forbids single constraint for `TypeVar'" func = get_typing_function(tp.__constraints__[0]) else: raise ValueError( "Cannot resolve typing function for TypeVar({constraints}) " "as it declares multiple types".format(constraints=", ".join( getattr(c, "_name", c.__name__) for c in tp.__constraints__))) elif tp == typing.Any: func = _identity_function elif issubclass_(tp, str): func = str elif is_mapping_type(tp): func = _apply_dict_type elif is_tuple_type(tp): func = _apply_tuple_type elif is_iterable_type(tp): func = _apply_list_type elif is_optional_type(tp): func = _apply_optional_type elif callable(tp): func = tp else: raise ValueError( 'Cannot find a function to apply type "{}"'.format(tp)) args = getattr(tp, "__args__", None) if args: # this can be a Generic type from the typing module, like # List[str], Mapping[int, str] and so on. In that case we need to # also deal with the generic typing args_types = [get_typing_function(arg) for arg in args] func = _partial_builder(args_types)(func) return func
def run_interactive(self, cmd, args, raw): try: args_metadata = self.metadata.arguments parsed = parser.parse(args, expect_subcommand=self.super_command) # prepare args dict parsed_dict = parsed.asDict() args_dict = parsed.kv.asDict() key_values = parsed.kv.asDict() command_name = cmd # if this is a super command, we need first to create an instance of # the class (fn) and pass the right arguments if self.super_command: subcommand = parsed_dict.get("__subcommand__") if not subcommand: cprint( "A sub-command must be supplied, valid values: " "{}".format(", ".join(self._get_subcommands())), "red", ) return 2 sub_inspection = self.subcommand_metadata(subcommand) if not sub_inspection: cprint( "Invalid sub-command '{}', valid values: " "{}".format( subcommand, ", ".join(self._get_subcommands()) ), "red", ) return 2 instance, remaining_args = self._create_subcommand_obj( args_dict ) assert instance args_dict = remaining_args key_values = copy.copy(args_dict) args_metadata = sub_inspection.arguments attrname = self._find_subcommand_attr(subcommand) command_name = subcommand assert attrname is not None fn = getattr(instance, attrname) else: # not a super-command, use use the function instead fn = self._fn positionals = ( parsed_dict["positionals"] if parsed.positionals != "" else [] ) # We only allow positionals for arguments that have positional=True # ِ We filter out the OrderedDict this way to ensure we don't lose the # order of the arguments. We also filter out arguments that have # been passed by name already. The order of the positional arguments # follows the order of the function definition. can_be_positional = self._positional_arguments( args_metadata, args_dict.keys() ) if len(positionals) > len(can_be_positional): if len(can_be_positional) == 0: err = "This command does not support positional arguments" else: # We have more positionals than we should err = ( "This command only supports ({}) positional arguments, " "namely arguments ({}). You have passed {} arguments ({})" " instead!" ).format( len(can_be_positional), ", ".join(can_be_positional.keys()), len(positionals), ", ".join(positionals), ) cprint(err, "red") return 2 # constuct key_value dict from positional arguments. args_from_positionals = { key: value for value, key in zip(positionals, can_be_positional) } # update the total arguments dict with the positionals args_dict.update(args_from_positionals) # Run some validations on number of arguments provided # do we have keys that are supplied in both positionals and # key_value style? duplicate_keys = set(args_from_positionals.keys()).intersection( set(key_values.keys()) ) if duplicate_keys: cprint( "Arguments '{}' have been passed already, cannot have" " duplicate keys".format(list(duplicate_keys)), "red", ) return 2 # check for verbosity override in kwargs ctx = context.get_context() old_verbose = ctx.args.verbose if "verbose" in args_dict: ctx.set_verbose(args_dict["verbose"]) del args_dict["verbose"] del key_values["verbose"] # do we have keys that we know nothing about? extra_keys = set(args_dict.keys()) - set(args_metadata) if extra_keys: cprint( "Unknown argument(s) {} were" " passed".format(list(extra_keys)), "magenta", ) return 2 # is there any required keys that were not resolved from positionals # nor key_values? missing_keys = set(args_metadata) - set(args_dict.keys()) if missing_keys: required_missing = [] for key in missing_keys: if not args_metadata[key].default_value_set: required_missing.append(key) if required_missing: cprint( "Missing required argument(s) {} for command" " {}".format(required_missing, command_name), "yellow", ) return 3 # convert expected types for arguments for key, value in args_dict.items(): target_type = args_metadata[key].type if target_type is None: target_type = str try: new_value = apply_typing(value, target_type) except ValueError as e: fn_name = function_to_str(target_type, False, False) cprint( 'Cannot convert value "{}" to {} on argument {}'.format( value, fn_name, key ), "yellow", ) return 4 else: args_dict[key] = new_value # Validate that arguments with `choices` are supplied with the # acceptable values. for arg, value in args_dict.items(): choices = args_metadata[arg].choices if choices: # Validate the choices in the case of values and list of # values. if issubclass_(args_metadata[arg].type, typing.List): bad_inputs = [v for v in value if v not in choices] if bad_inputs: cprint( f"Argument '{arg}' got an unexpected " f"value(s) '{bad_inputs}'. Expected one " f"or more of {choices}.", "red", ) return 4 elif value not in choices: cprint( f"Argument '{arg}' got an unexpected value " f"'{value}'. Expected one of " f"{choices}.", "red", ) return 4 # arguments appear to be fine, time to run the function try: # convert argument names back to match the function signature args_dict = { args_metadata[k].arg: v for k, v in args_dict.items() } if inspect.iscoroutinefunction(fn): loop = asyncio.get_event_loop() ret = loop.run_until_complete(fn(**args_dict)) else: ret = fn(**args_dict) ctx.set_verbose(old_verbose) except Exception as e: cprint("Error running command: {}".format(str(e)), "red") cprint("-" * 60, "yellow") traceback.print_exc(file=sys.stderr) cprint("-" * 60, "yellow") return 1 return ret except CommandParseError as e: cprint("Error parsing command", "red") cprint(cmd + " " + args, "white", attrs=["bold"]) cprint((" " * (e.col + len(cmd))) + "^", "white", attrs=["bold"]) cprint(str(e), "yellow") return 1
def is_dict_value_iterable(type): assert issubclass_(type, collections.Mapping) args = getattr(type, "__args__", None) if args and len(args) == 2: return issubclass_(args[1], typing.List) return False
def is_list_type(tp) -> bool: """Checks whether a type is a typing.List.""" if NEW_TYPING: return tp is List or _is_generic_alias_of(tp, list) return issubclass_(tp, list)
def is_iterable_type(tp) -> bool: """Checks whether a type is an iterable type.""" if NEW_TYPING: return tp is Iterable or _is_generic_alias_of(tp, collections.abc.Iterable) return issubclass_(tp, list)
def is_mapping_type(tp) -> bool: """Checks whether a type is a mapping type.""" if NEW_TYPING: return tp is Mapping or _is_generic_alias_of(tp, collections.abc.Mapping) return issubclass_(tp, collections.abc.Mapping)
def _is_generic_alias_of(this, that) -> bool: return isinstance(this, _GenericAlias) and issubclass_( this.__origin__, that)
def is_iterable_type(tp) -> bool: """Checks whether a type is an iterable type.""" if PEP_560: return (tp is Iterable or isinstance(tp, _GenericAlias) and issubclass_(tp.__origin__, collections.abc.Iterable)) return issubclass_(tp, list)
def is_mapping_type(tp) -> bool: """Checks whether a type is a mapping type.""" if PEP_560: return (tp is Mapping or isinstance(tp, _GenericAlias) and issubclass_(tp.__origin__, collections.abc.Mapping)) return issubclass_(tp, collections.abc.Mapping)