"""Call a command interface
Provide a callable to register in a cmdline parser, for executing
a parameterized command call.
"""
# ATTN!
# Top-level inputs should be minimized. This module must be imported
# for parser construction, but the key function call_from_parser()
# is only executed when a command is actually engaged -- but not
# for a help action.
# Therefore no additional top-level imports beyond those already
# caused unconditionally by .main, and .parser.
from datalad import cfg
from datalad.interface.base import is_api_arg
from datalad.utils import getargspec
# only imported during command execution
# .interface._has_eval_results_call
# from .utils import EnsureKeyChoice
# special-case imports
# .renderer.DefaultOutputRenderer
# from datalad.ui import ui
# from .exceptions import CapturedException
[docs]
def call_from_parser(cls, args):
"""Executable to be registered with the parser for a particular command
Parameters
----------
cls : Interface
Class implementing a particular interface.
args : Namespace
Populated argparse namespace instance.
Returns
-------
iterable
Returns the iterable return by an command's implementation of
``__call__()``. It is unwound, in case of a generator being
returned to actually trigger the underlying processing.
"""
# XXX needs safety check for name collisions
import inspect
from datalad.interface.base import _has_eval_results_call
argspec = getargspec(cls.__call__, include_kwonlyargs=True)
if argspec.keywords is None:
# no **kwargs in the call receiver, pull argnames from signature
argnames = argspec.args
else:
# common options
# XXX define or better get from elsewhere
# ultimately .common_args.common_args could be used, but
# it is presently unclear what is being excluded here (incomplete set)
common_opts = ('change_path', 'common_debug', 'common_idebug', 'func',
'help', 'log_level', 'logger',
'result_renderer', 'subparser')
argnames = [name for name in dir(args)
if not (name.startswith('_') or name in common_opts)]
kwargs = {k: getattr(args, k)
for k in argnames
# some arguments might be Python-only and do not appear in the
# parser Namespace
if hasattr(args, k) and is_api_arg(k)}
# we are coming from the entry point, this is the toplevel command,
# let it run like generator so we can act on partial results quicker
# TODO remove following condition test when transition is complete and
# run indented code unconditionally
if _has_eval_results_call(cls):
# set all common args explicitly to override class defaults
# that are tailored towards the the Python API
kwargs['return_type'] = 'generator'
kwargs['result_xfm'] = None
if '{' in args.common_result_renderer:
from .renderer import DefaultOutputRenderer
# stupid hack, could and should become more powerful
kwargs['result_renderer'] = DefaultOutputRenderer(
args.common_result_renderer)
else:
# allow commands to override the default, unless something other
# than the default 'tailored' is requested
kwargs['result_renderer'] = \
args.common_result_renderer \
if args.common_result_renderer != 'tailored' \
else getattr(cls, 'result_renderer', 'generic')
if args.common_on_failure:
kwargs['on_failure'] = args.common_on_failure
# compose filter function from to be invented cmdline options
res_filter = _get_result_filter(args)
if res_filter is not None:
# Don't add result_filter if it's None because then
# eval_results can't distinguish between --report-{status,type}
# not specified via the CLI and None passed via the Python API.
kwargs['result_filter'] = res_filter
ret = cls.__call__(**kwargs)
if inspect.isgenerator(ret):
ret = list(ret)
return ret
def _get_result_filter(args):
from datalad.support.constraints import EnsureKeyChoice
result_filter = None
if args.common_report_status or 'datalad.runtime.report-status' in cfg:
report_status = args.common_report_status or \
cfg.obtain('datalad.runtime.report-status')
if report_status == "all":
pass # no filter
elif report_status == 'success':
result_filter = EnsureKeyChoice('status', ('ok', 'notneeded'))
elif report_status == 'failure':
result_filter = EnsureKeyChoice('status',
('impossible', 'error'))
else:
result_filter = EnsureKeyChoice('status', (report_status,))
if args.common_report_type:
tfilt = EnsureKeyChoice('type', tuple(args.common_report_type))
result_filter = result_filter & tfilt if result_filter else tfilt
return result_filter