def inspect_object()

in nubia/internal/typing/__init__.py [0:0]


def inspect_object(obj, accept_bound_methods=False):
    """
    Used to inspect a function or method annotated with @command or
    @argument. Returns a well structured dict summarizing the metadata added
    through the decorators

    Check the module documentation for more info
    """

    command = getattr(obj, "__command", None)
    arguments_decorator_specs = getattr(obj, "__arguments_decorator_specs", {})

    argspec = get_arg_spec(obj)

    args = argspec.args
    # remove the first argument in case this is a method (normally the first
    # arg is 'self')
    if ismethod(obj):
        args = args[1:]

    result = {"arguments": OrderedDict(), "command": None, "subcommands": {}}

    if command:
        result["command"] = Command(
            name=command["name"] or obj.__name__,
            help=command["help"] or obj.__doc__,
            aliases=command["aliases"],
            exclusive_arguments=command["exclusive_arguments"],
        )

    # Is this a super command?
    is_supercommand = isclass(obj)

    for i, arg in enumerate(args):
        if (is_supercommand or accept_bound_methods) and arg == "self":
            continue
        arg_idx_with_default = len(args) - len(argspec.defaults)
        default_value_set = bool(argspec.defaults and i >= arg_idx_with_default)
        default_value = (
            argspec.defaults[i - arg_idx_with_default] if default_value_set else None
        )
        # We will reject classes (super-commands) that has required arguments to
        # reduce complexity
        if is_supercommand and not default_value_set:
            raise ValueError(
                "Cannot accept super commands that has required "
                "arguments with no default value "
                "like '{}' in super-command '{}'".format(arg, result["command"].name)
            )
        arg_decor_spec = arguments_decorator_specs.get(
            arg, _empty_arg_decorator_spec(arg)
        )

        result["arguments"][arg_decor_spec.name] = Argument(
            arg=arg_decor_spec.arg,
            description=arg_decor_spec.description,
            type=argspec.annotations.get(arg),
            default_value_set=default_value_set,
            default_value=default_value,
            name=arg_decor_spec.name,
            extra_names=arg_decor_spec.aliases,
            positional=arg_decor_spec.positional,
            choices=arg_decor_spec.choices,
        )
    if argspec.varkw:
        # We will inject all the arguments that are not defined explicitly in
        # the function signature.
        for arg, arg_decor_spec in arguments_decorator_specs.items():
            added_arguments = [v.name for v in result["arguments"].values()]
            if arg_decor_spec.name not in added_arguments:
                # This is an extra argument
                result["arguments"][arg_decor_spec.name] = Argument(
                    arg=arg,
                    description=arg_decor_spec.description,
                    type=argspec.annotations.get(arg),
                    default_value_set=True,
                    default_value=None,
                    name=arg_decor_spec.name,
                    extra_names=arg_decor_spec.aliases,
                    positional=arg_decor_spec.positional,
                    choices=arg_decor_spec.choices,
                )

    # Super Command Support
    if is_supercommand:
        result["subcommands"] = []
        for attr in dir(obj):
            if attr.startswith("_"):  # ignore "private" methods
                continue
            candidate = getattr(obj, attr)
            if not callable(candidate):  # avoid e.g. properties
                continue
            metadata = inspect_object(candidate, accept_bound_methods=True)
            # ignore subcommands without docstring
            if metadata.command:
                if not metadata.command.help:
                    cprint(
                        (
                            f"[WARNING] The sub-command {metadata.command.name} "
                            "will not be loaded. "
                            "Please provide a help message by either defining a "
                            "docstring or filling the help argument in the "
                            "@command annotation"
                        ),
                        "red",
                    )
                    continue

                result["subcommands"].append((attr, metadata))

    return FunctionInspection(**result)