Пример #1
0
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
Пример #2
0
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
Пример #3
0
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"
Пример #4
0
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
Пример #5
0
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
Пример #6
0
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
Пример #7
0
    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
Пример #8
0
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
Пример #9
0
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)
Пример #10
0
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)
Пример #11
0
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)
Пример #12
0
def _is_generic_alias_of(this, that) -> bool:
    return isinstance(this, _GenericAlias) and issubclass_(
        this.__origin__, that)
Пример #13
0
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)
Пример #14
0
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)