def __getattr__(self, attr): # Assure that we are not just missing some late binding # @datasetmethod . We will use interface definitions. # The gotcha could be the mismatch between explicit name # provided to @datasetmethod and what is defined in interfaces meth = None if not attr.startswith('_'): # do not even consider those from datalad.interface.base import ( get_interface_groups, get_api_name, load_interface ) groups = get_interface_groups(True) for group, _, interfaces in groups: for intfspec in interfaces: # lgr.log(5, "Considering interface %s", intfspec) name = get_api_name(intfspec) if attr == name: meth_ = load_interface(intfspec) if meth_: lgr.debug("Found matching interface %s for %s", intfspec, name) if meth: lgr.debug( "New match %s possibly overloaded previous one %s", meth_, meth ) meth = meth_ if not meth: lgr.debug("Found no match among known interfaces for %r", attr) return super(Dataset, self).__getattribute__(attr)
def __getattr__(self, attr): # Assure that we are not just missing some late binding # @datasetmethod . We will use interface definitions. # The gotcha could be the mismatch between explicit name # provided to @datasetmethod and what is defined in interfaces meth = None if not attr.startswith('_'): # do not even consider those from datalad.interface.base import ( get_interface_groups, get_api_name, load_interface ) groups = get_interface_groups(True) for group, _, interfaces in groups: for intfspec in interfaces: # lgr.log(5, "Considering interface %s", intfspec) name = get_api_name(intfspec) if attr == name: meth_ = load_interface(intfspec) if meth_: lgr.debug("Found matching interface %s for %s", intfspec, name) if meth: lgr.debug( "New match %s possibly overloaded previous one %s", meth_, meth ) meth = meth_ if not meth: lgr.debug("Found no match among known interfaces for %r", attr) return super(Dataset, self).__getattribute__(attr)
def _get_all_interfaces(self): # load all extensions and command specs # this does not fully tune all the command docs from datalad.interface.base import get_interface_groups interface_groups = get_interface_groups() add_entrypoints_to_interface_groups(interface_groups) return interface_groups
def test_consistent_order_of_args(): from datalad.interface.base import get_interface_groups from importlib import import_module for grp_name, grp_descr, interfaces in get_interface_groups(): for intfspec in interfaces: # turn the interface spec into an instance mod = import_module(intfspec[0], package='datalad') intf = getattr(mod, intfspec[1]) spec = getattr(intf, '_params_', dict()) # figure out which of the specs are "positional" spec_posargs = { name for name, param in spec.items() if param.cmd_args and not param.cmd_args[0].startswith('-') } # we have information about positional args yield _test_consistent_order_of_args, intf, spec_posargs
def test_consistent_order_of_args(): from datalad.interface.base import get_interface_groups from importlib import import_module for grp_name, grp_descr, interfaces in get_interface_groups(): for intfspec in interfaces: # turn the interface spec into an instance mod = import_module(intfspec[0], package='datalad') intf = getattr(mod, intfspec[1]) spec = getattr(intf, '_params_', dict()) # figure out which of the specs are "positional" spec_posargs = { name for name, param in spec.items() if param.cmd_args and not param.cmd_args[0].startswith('-') } # we have information about positional args yield _test_consistent_order_of_args, intf, spec_posargs
def _command_summary(): # Import here to avoid polluting the datalad.api namespace. from collections import defaultdict from datalad.interface.base import alter_interface_docs_for_api from datalad.interface.base import get_api_name from datalad.interface.base import get_cmd_doc from datalad.interface.base import get_cmd_summaries from datalad.interface.base import get_interface_groups from datalad.interface.base import load_interface groups = get_interface_groups() grp_short_descriptions = defaultdict(list) for group, _, specs in sorted(groups, key=lambda x: x[1]): for spec in specs: intf = load_interface(spec) if intf is None: continue sdescr = getattr(intf, "short_description", None) or \ alter_interface_docs_for_api(get_cmd_doc(intf)).split("\n")[0] grp_short_descriptions[group].append((get_api_name(spec), sdescr)) return "\n".join(get_cmd_summaries(grp_short_descriptions, groups))
def _command_summary(): # Import here to avoid polluting the datalad.api namespace. from collections import defaultdict from datalad.interface.base import alter_interface_docs_for_api from datalad.interface.base import get_api_name from datalad.interface.base import get_cmd_doc from datalad.interface.base import get_cmd_summaries from datalad.interface.base import get_interface_groups from datalad.interface.base import load_interface groups = get_interface_groups(include_plugins=True) grp_short_descriptions = defaultdict(list) for group, _, specs in sorted(groups, key=lambda x: x[1]): for spec in specs: intf = load_interface(spec) if intf is None: continue sdescr = getattr(intf, "short_description", None) or \ alter_interface_docs_for_api(get_cmd_doc(intf)).split("\n")[0] grp_short_descriptions[group].append( (get_api_name(spec), sdescr)) return "\n".join(get_cmd_summaries(grp_short_descriptions, groups))
def setup_parser(cmdlineargs, formatter_class=argparse.RawDescriptionHelpFormatter, return_subparsers=False, completing=False, help_ignore_extensions=False): """ The holy grail of establishing CLI for DataLad's Interfaces Parameters ---------- cmdlineargs: sys.argv Used to make some shortcuts when construction of a full parser can be avoided. formatter_class: Passed to argparse return_subparsers: bool, optional is used ATM only by BuildManPage in _datalad_build_support completing: bool, optional Flag to indicate whether the process was invoked by argcomplete help_ignore_extensions: bool, optional Prevent loading of extension entrypoints when --help is requested. This is enabled when building docs to avoid pollution of generated manpages with extensions commands (that should appear in their own docs, but not in the core datalad package docs) """ lgr.log(5, "Starting to setup_parser") # main parser parser = ArgumentParserDisableAbbrev( fromfile_prefix_chars=None, prog='datalad', # usage="%(prog)s ...", description=help_gist, epilog='"Be happy!"', formatter_class=formatter_class, add_help=False, # TODO: when dropping support for Python 3.8: uncomment below # and use parse_known_args instead of _parse_known_args: # # set to False so parse_known_args does not add its error handling # # Added while RFing from using _parse_known_args to parse_known_args. # exit_on_error=False, ) # common options parser_add_common_options(parser) # get all interface definitions from datalad-core interface_groups = get_interface_groups() # try to figure out whether the parser construction can be limited to # a single (sub)command -- don't even try to do this, when we are in # any of the doc-building capacities -- timing is not relevant there status, parseinfo = single_subparser_possible( cmdlineargs, parser, completing, ) if not return_subparsers else ('allparsers', None) command_provider = 'core' if status == 'allparsers' and not help_ignore_extensions: from .helpers import add_entrypoints_to_interface_groups add_entrypoints_to_interface_groups(interface_groups) # when completing and we have no incomplete option or parameter # we still need to offer all commands for completion if (completing and status == 'allknown') or ( status == 'subcommand' and parseinfo not in get_commands_from_groups(interface_groups)): # we know the command is not in the core package # still a chance it could be in an extension command_provider = 'extension' # we need the full help, or we have a potential command that # lives in an extension, must load all extension, expensive from .helpers import add_entrypoints_to_interface_groups # need to load all the extensions and try again # TODO load extensions one-by-one and stop when a command was found add_entrypoints_to_interface_groups(interface_groups) if status == 'subcommand': known_commands = get_commands_from_groups(interface_groups) if parseinfo not in known_commands: # certainly not possible to identify a single parser that # could be constructed, but we can be helpful # will sys.exit() unless we are completing try_suggest_extension_with_command(parser, parseinfo, completing, known_commands) # in completion mode we can get here, even for a command # that does not exist at all! command_provider = None # TODO check if not needed elsewhere if status == 'help' or completing and status in ('allknown', 'unknownopt'): # --help specification was delayed since it causes immediate # printout of # --help output before we setup --help for each command parser_add_common_opt(parser, 'help') all_parsers = {} # name: (sub)parser if (completing and status == 'allknown') or status \ in ('allparsers', 'subcommand'): # parseinfo could be None here, when we could not identify # a subcommand, but need to locate matching ones for # completion # create subparser, use module suffix as cmd name subparsers = parser.add_subparsers() for _, _, _interfaces \ in sorted(interface_groups, key=lambda x: x[1]): for _intfspec in _interfaces: cmd_name = get_cmdline_command_name(_intfspec) if status == 'subcommand': # in case only a subcommand is desired, we could # skip some processing if command_provider and cmd_name != parseinfo: # a known command, but know what we are looking for continue if command_provider is None and not cmd_name.startswith( parseinfo): # an unknown command, and has no common prefix with # the current command candidate, not even good # for completion continue subparser = add_subparser( _intfspec, subparsers, cmd_name, formatter_class, completing=completing, ) if subparser: # interface can fail to load all_parsers[cmd_name] = subparser # "main" parser is under "datalad" name all_parsers['datalad'] = parser lgr.log(5, "Finished setup_parser") if return_subparsers: # TODO why not pull the subparsers from the main parser? return all_parsers else: return parser
def get_all_commands() -> list: return list(get_commands_from_groups(get_interface_groups()))