Beispiel #1
0
    def __init__(self, fn):
        self._built_in = False
        self._fn = fn

        if not callable(fn):
            raise ValueError("fn argument must be a callable")

        self._obj_metadata = inspect_object(fn)
        self._is_super_command = len(self.metadata.subcommands) > 0
        self._subcommand_names = []

        # We never expect a function to be passed here that has a self argument
        # In that case, we should get a bound method
        if "self" in self.metadata.arguments and not inspect.ismethod(
                self._fn):
            raise ValueError("Expecting either a function (eg. bar) or "
                             "a bound method (eg. Foo().bar). "
                             "You passed what appears to be an unbound method "
                             "(eg. Foo.bar) it has a 'self' argument: %s" %
                             function_to_str(fn))

        if not self.metadata.command:
            raise ValueError("function or class {} needs to be annotated with "
                             "@command".format(function_to_str(fn)))
        # If this is a super command, we need a completer for sub-commands
        if self.super_command:
            self._commands_completer = WordCompleter([],
                                                     ignore_case=True,
                                                     sentence=True)
            for _, inspection in self.metadata.subcommands:
                _sub_name = inspection.command.name
                self._commands_completer.words.append(_sub_name)
                self._commands_completer.meta_dict[_sub_name] = dedent(
                    inspection.command.help).strip()
                self._subcommand_names.append(_sub_name)
Beispiel #2
0
    def decorator(function):
        # Following makes interactive really slow. (T20898480)
        # This should be revisited in T20899641
        #  if (description is not None and \
        #       arg is not None and type is not None):
        #      append_doc(function, arg, type, description)
        fn_specs = get_arg_spec(function)
        args = fn_specs.args or []
        if arg not in args and not fn_specs.varkw:
            raise NameError(
                "Argument {} does not exist in function {}".format(
                    arg, function_to_str(function)
                )
            )

        # init structures to store decorator data if not present
        _init_attr(function, "__annotations__", OrderedDict())
        _init_attr(function, "__arguments_decorator_specs", {})

        # Check if there is a conflict in type annotations
        current_type = function.__annotations__.get(arg)
        if current_type and type and current_type != type:
            raise TypeError(
                "Argument {} in {} is both specified as {} "
                "and {}".format(
                    arg, function_to_str(function), current_type, type
                )
            )

        if arg in function.__arguments_decorator_specs:
            raise ValueError(
                "@argument decorator was applied twice "
                "for the same argument {} on function {}".format(arg, function)
            )

        if positional and aliases:
            msg = "Aliases are not yet supported for positional arguments @ {}".format(
                arg
            )
            raise ValueError(msg)

        # reject positional=True if we are applied over a class
        if isclass(function) and positional:
            raise ValueError(
                "Cannot set positional arguments for super " "commands"
            )

        # We use __annotations__ to allow the usage of python 3 typing
        function.__annotations__.setdefault(arg, type)

        function.__arguments_decorator_specs[arg] = _ArgDecoratorSpec(
            arg=arg,
            description=description,
            name=name or transform_name(arg),
            aliases=aliases or [],
            positional=positional,
            choices=choices,
        )

        return function
Beispiel #3
0
def _validate_exclusive_arguments(function, normalized_exclusive_arguments):
    if not normalized_exclusive_arguments:
        return

    exclusive_arguments = normalized_exclusive_arguments
    flat_ex_args = [arg for group in exclusive_arguments for arg in group]

    if not flat_ex_args:
        return

    inspection = inspect_object(function)
    possible_args = list(inspection.arguments.keys())

    unknown_args = set(flat_ex_args) - set(possible_args)
    if unknown_args:
        msg = (
            "The following arguments were specified as exclusive but they "
            "are not present in function {}: {}".format(
                function_to_str(function), ", ".join(unknown_args)
            )
        )
        raise NameError(msg)

    if len(set(flat_ex_args)) != len(flat_ex_args):
        counts = (
            (item, group.count(item))
            for group in exclusive_arguments
            for item in group
        )
        repeated_args = [item for item, count in counts if count > 1]
        msg = (
            "The following args are present in more than one exclusive "
            "group: {}".format(", ".join(repeated_args))
        )
        raise ValueError(msg)
Beispiel #4
0
 def _get_arg_help(self, arg_meta):
     sb = ["["]
     if arg_meta.type:
         sb.append(function_to_str(arg_meta.type, False, False))
         sb.append(", ")
     if arg_meta.default_value_set:
         sb.append("default: ")
         sb.append(arg_meta.default_value)
     else:
         sb.append("required")
     sb.append("] ")
     sb.append(arg_meta.description if arg_meta.
               description else "<no description provided>")
     return "".join(str(item) for item in sb)
Beispiel #5
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(str(x) for x in 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:
                    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 is_list_type(args_metadata[arg].type):
                        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
Beispiel #6
0
 def test(expected, with_module, with_args):
     self.assertEqual(function_to_str(foo, with_module, with_args),
                      expected)