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)