Source code for funcargparse

"""Generate an ArgumentParser from the docstring.

**Disclaimer**

Copyright 2016-2019, Philipp S. Sommer

Copyright 2020-2021, Helmholtz-Zentrum Hereon

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
from __future__ import print_function, division
import os
import six
import sys
import inspect
from itertools import chain, groupby
from argparse import ArgumentParser, Namespace
import argparse
import docrep

try:
    from cyordereddict import OrderedDict
except ImportError:
    try:
        from collections import OrderedDict
    except ImportError:
        from ordereddict import OrderedDict


if six.PY2:
    import __builtin__ as builtins
else:
    import builtins


__author__ = "Philipp S. Sommer"
__copyright__ = (
    "2016 - 2019, Philipp S. Sommer\n"
    "2020 - 2021, Helmholtz-Zentrum Hereon"
)
__credits__ = ["Philipp S. Sommer"]
__license__ = "Apache-2.0"

__maintainer__ = "Philipp S. Sommer"
__email__ = "philipp.sommer@hereon.de"

__status__ = "Production"

__version__ = '0.2.5'


docstrings = docrep.DocstringProcessor()


_on_rtd = os.environ.get('READTHEDOCS', None) == 'True'


[docs]class FuncArgParser(ArgumentParser): """Subclass of an argument parser that get's parts of the information from a given function""" _finalized = False #: The unfinished arguments after the setup unfinished_arguments = {} #: The sections to extract from a function docstring that should be used #: in the epilog of this parser. See also the :meth:`setup_args` method epilog_sections = ['Notes', 'References'] #: The formatter specification for the epilog. This can either be a string #: out of 'header', 'bold', or #: 'rubric' or a callable (i.e. function) that takes two arguments, #: the section title and the section text, and returns a string. #: #: 'heading' #: Use section headers such as:: #: #: Notes #: ----- #: 'bold' #: Just make a bold header for the section, e.g. ``**Notes**`` #: 'rubric' #: Use a rubric rst directive, e.g. ``.. rubric:: Notes`` #: #: .. warning:: #: #: When building a sphinx documentation using the sphinx-argparse #: module, this value should be set to ``'bold'`` or ``'rubric'``! Just #: add this two lines to your conf.py: #: #: .. code-block:: python #: #: import funcargparse #: funcargparse.FuncArgParser.epilog_formatter = 'rubric' epilog_formatter = 'heading' def __init__(self, *args, **kwargs): """ Parameters ---------- ``*args,**kwargs`` Theses arguments are determined by the :class:`argparse.ArgumentParser` base class. Note that by default, we use a :class:`argparse.RawTextHelpFormatter` class for the `formatter_class` keyword, whereas the :class:`argparse.ArgumentParser` uses a :class:`argparse.HelpFormatter` Other Parameters ---------------- epilog_sections: list of str The default sections to use for the epilog (see the :attr:`epilog_sections` attribute). They can also be specified each time the :meth:`setup_args` method is called epilog_formatter: {'header', 'bold', 'rubric'} or function Specify how the epilog sections should be formatted and defaults to the :attr:`epilog_formatter` attribute. This can either be a string out of 'header', 'bold', or 'rubric' or a callable (i.e. function) that takes two arguments, the section title and the section text, and returns a string. 'heading' Use section headers such as:: Notes ----- 'bold' Just make a bold header for the section, e.g. ``**Notes**`` 'rubric' Use a rubric rst directive, e.g. ``.. rubric:: Notes`` """ self._subparsers_action = None kwargs.setdefault('formatter_class', argparse.RawTextHelpFormatter) epilog_sections = kwargs.pop('epilog_sections', None) if epilog_sections is not None: self.epilog_sections = epilog_sections epilog_formatter = kwargs.pop('epilog_formatter', None) if epilog_formatter is not None: self.epilog_formatter = epilog_formatter super(FuncArgParser, self).__init__(*args, **kwargs) self.unfinished_arguments = OrderedDict() self._used_functions = [] self.__currentarg = None self._chain_subparsers = False self._setup_as = None self._epilog_formatters = {'heading': self.format_heading, 'bold': self.format_bold, 'rubric': self.format_rubric}
[docs] @staticmethod def get_param_doc(doc, param): """Get the documentation and datatype for a parameter This function returns the documentation and the argument for a napoleon like structured docstring `doc` Parameters ---------- doc: str The base docstring to use param: str The argument to use Returns ------- str The documentation of the given `param` str The datatype of the given `param`""" arg_doc = docrep.keep_params(doc, param) or \ docrep.keep_types(doc, param) dtype = None if arg_doc: lines = arg_doc.splitlines() arg_doc = inspect.cleandoc('\n' + '\n'.join(lines[1:])) param_desc = lines[0].split(':', 1) if len(param_desc) > 1: dtype = param_desc[1].strip() return arg_doc, dtype
[docs] @docstrings.get_sections(base='FuncArgParser.setup_args', sections=['Parameters', 'Returns']) @docstrings.dedent def setup_args(self, func=None, setup_as=None, insert_at=None, interprete=True, epilog_sections=None, overwrite=False, append_epilog=True): """ Add the parameters from the given `func` to the parameter settings Parameters ---------- func: function The function to use. If None, a function will be returned that can be used as a decorator setup_as: str The attribute that shall be assigned to the function in the resulting namespace. If specified, this function will be used when calling the :meth:`parse2func` method insert_at: int The position where the given `func` should be inserted. If None, it will be appended at the end and used when calling the :meth:`parse2func` method interprete: bool If True (default), the docstrings are interpreted and switches and lists are automatically inserted (see the [interpretation-docs]_ epilog_sections: list of str The headers of the sections to extract. If None, the :attr:`epilog_sections` attribute is used overwrite: bool If True, overwrite the existing epilog and the existing description of the parser append_epilog: bool If True, append to the existing epilog Returns ------- function Either the function that can be used as a decorator (if `func` is ``None``), or the given `func` itself. Examples -------- Use this method as a decorator:: >>> @parser.setup_args ... def do_something(a=1): ''' Just an example Parameters ---------- a: int A number to increment by one ''' return a + 1 >>> args = parser.parse_args('-a 2'.split()) Or by specifying the setup_as function:: >>> @parser.setup_args(setup_as='func') ... def do_something(a=1): ''' Just an example Parameters ---------- a: int A number to increment by one ''' return a + 1 >>> args = parser.parse_args('-a 2'.split()) >>> args.func is do_something >>> parser.parse2func('-a 2'.split()) 3 References ---------- .. [interpretation-docs] http://funcargparse.readthedocs.io/en/latest/docstring_interpretation.html) """ def setup(func): # insert the function if insert_at is None: self._used_functions.append(func) else: self._used_functions.insert(insert_at, func) args_dict = self.unfinished_arguments # save the function to use in parse2funcs if setup_as: args_dict[setup_as] = dict( long=setup_as, default=func, help=argparse.SUPPRESS) self._setup_as = setup_as # create arguments try: spec = inspect.getfullargspec(func)[:4] except AttributeError: # py27 spec = inspect.getargspec(func) args, varargs, varkw, defaults = spec doc = inspect.getdoc(func) full_doc = docstrings.dedent(doc) if doc else '' summary = docstrings.get_full_description(full_doc) if summary: if not self.description or overwrite: self.description = summary full_doc = docstrings._remove_summary(full_doc) self.extract_as_epilog(full_doc, epilog_sections, overwrite, append_epilog) doc = docstrings._get_section(full_doc, 'Parameters') + '\n' doc += docstrings._get_section(full_doc, 'Other Parameters') doc = doc.rstrip() default_min = len(args or []) - len(defaults or []) for i, arg in enumerate(args): if arg == 'self' or arg in args_dict: continue arg_doc, dtype = self.get_param_doc(doc, arg) args_dict[arg] = d = {'dest': arg, 'short': arg.replace('_', '-'), 'long': arg.replace('_', '-')} if arg_doc: d['help'] = arg_doc if i >= default_min: d['default'] = defaults[i - default_min] else: d['positional'] = True if interprete and dtype == 'bool' and 'default' in d: d['action'] = 'store_false' if d['default'] else \ 'store_true' elif interprete and dtype: if dtype.startswith('list of'): d['nargs'] = '+' dtype = dtype[7:].strip() if dtype in ['str', 'string', 'strings']: d['type'] = six.text_type if dtype == 'strings': dtype = 'string' else: try: d['type'] = getattr(builtins, dtype) except AttributeError: try: # maybe the dtype has a final 's' d['type'] = getattr(builtins, dtype[:-1]) dtype = dtype[:-1] except AttributeError: pass d['metavar'] = dtype return func if func is None: return setup else: return setup(func)
[docs] @docstrings.get_sections(base='FuncArgParser.add_subparsers') @docstrings.dedent def add_subparsers(self, *args, **kwargs): """ Add subparsers to this parser Parameters ---------- ``*args, **kwargs`` As specified by the original :meth:`argparse.ArgumentParser.add_subparsers` method chain: bool Default: False. If True, It is enabled to chain subparsers""" chain = kwargs.pop('chain', None) ret = super(FuncArgParser, self).add_subparsers(*args, **kwargs) if chain: self._chain_subparsers = True self._subparsers_action = ret return ret
[docs] @docstrings.dedent def setup_subparser( self, func=None, setup_as=None, insert_at=None, interprete=True, epilog_sections=None, overwrite=False, append_epilog=True, return_parser=False, name=None, **kwargs): """ Create a subparser with the name of the given function Parameters are the same as for the :meth:`setup_args` function, other parameters are parsed to the :meth:`add_subparsers` method if (and only if) this method has not already been called. Parameters ---------- %(FuncArgParser.setup_args.parameters)s return_parser: bool If True, the create parser is returned instead of the function name: str The name of the created parser. If None, the function name is used and underscores (``'_'``) are replaced by minus (``'-'``) ``**kwargs`` Any other parameter that is passed to the add_parser method that creates the parser Other Parameters ---------------- Returns ------- FuncArgParser or %(FuncArgParser.setup_args.returns)s If return_parser is True, the created subparser is returned Examples -------- Use this method as a decorator:: >>> from funcargparser import FuncArgParser >>> parser = FuncArgParser() >>> @parser.setup_subparser ... def my_func(my_argument=None): ... pass >>> args = parser.parse_args('my-func -my-argument 1'.split()) """ def setup(func): if self._subparsers_action is None: raise RuntimeError( "No subparsers have yet been created! Run the " "add_subparsers method first!") # replace underscore by '-' name2use = name if name2use is None: name2use = func.__name__.replace('_', '-') doc = inspect.getdoc(func) kwargs.setdefault('help', docstrings.get_summary( docstrings.dedent(doc) if doc else '')) parser = self._subparsers_action.add_parser(name2use, **kwargs) parser.setup_args( func, setup_as=setup_as, insert_at=insert_at, interprete=interprete, epilog_sections=epilog_sections, overwrite=overwrite, append_epilog=append_epilog) return func, parser if func is None: return lambda f: setup(f)[0] else: return setup(func)[int(return_parser)]
[docs] @docstrings.get_sections(base='FuncArgParser.update_arg') @docstrings.dedent def update_arg(self, arg, if_existent=None, **kwargs): """ Update the `add_argument` data for the given parameter Parameters ---------- arg: str The name of the function argument if_existent: bool or None If True, the argument is updated. If None (default), the argument is only updated, if it exists. Otherwise, if False, the given ``**kwargs`` are only used if the argument is not yet existing ``**kwargs`` The keyword arguments any parameter for the :meth:`argparse.ArgumentParser.add_argument` method """ if if_existent or (if_existent is None and arg in self.unfinished_arguments): self.unfinished_arguments[arg].update(kwargs) elif not if_existent and if_existent is not None: self.unfinished_arguments.setdefault(arg, kwargs)
[docs] @docstrings.dedent def update_argf(self, arg, **kwargs): """ Update the arguments as a decorator Parameters --------- %(FuncArgParser.update_arg.parameters)s Examples -------- Use this method as a decorator:: >>> from funcargparser import FuncArgParser >>> parser = FuncArgParser() >>> @parser.update_argf('my_argument', type=int) ... def my_func(my_argument=None): ... pass >>> args = parser.parse_args('my-func -my-argument 1'.split()) >>> isinstance(args.my_argument, int) True See Also -------- update_arg""" return self._as_decorator('update_arg', arg, **kwargs)
def _as_decorator(self, funcname, *args, **kwargs): def func_decorator(func): success = False for parser in self._get_corresponding_parsers(func): getattr(parser, funcname)(*args, **kwargs) success = True if not success: raise ValueError( "Could not figure out to which this %s belongs" % func) return func return func_decorator def _get_corresponding_parsers(self, func): """Get the parser that has been set up by the given `function`""" if func in self._used_functions: yield self if self._subparsers_action is not None: for parser in self._subparsers_action.choices.values(): for sp in parser._get_corresponding_parsers(func): yield sp
[docs] def pop_arg(self, *args, **kwargs): """Delete a previously defined argument from the parser """ return self.unfinished_arguments.pop(*args, **kwargs)
[docs] def pop_argf(self, *args, **kwargs): """Delete a previously defined argument from the parser via decorators Same as :meth:`pop_arg` but it can be used as a decorator""" return self._as_decorator('pop_arg', *args, **kwargs)
[docs] def pop_key(self, arg, key, *args, **kwargs): """Delete a previously defined key for the `add_argument` """ return self.unfinished_arguments[arg].pop(key, *args, **kwargs)
[docs] def pop_keyf(self, *args, **kwargs): """Delete a previously defined key for the `add_argument` Same as :meth:`pop_key` but it can be used as a decorator""" return self._as_decorator('pop_key', *args, **kwargs)
[docs] def create_arguments(self, subparsers=False): """Create and add the arguments Parameters ---------- subparsers: bool If True, the arguments of the subparsers are also created""" ret = [] if not self._finalized: for arg, d in self.unfinished_arguments.items(): try: not_positional = int(not d.pop('positional', False)) short = d.pop('short', None) long_name = d.pop('long', None) if short is None and long_name is None: raise ValueError( "Either a short (-) or a long (--) argument must " "be provided!") if not not_positional: short = arg long_name = None d.pop('dest', None) if short == long_name: long_name = None args = [] if short: args.append('-' * not_positional + short) if long_name: args.append('--' * not_positional + long_name) group = d.pop('group', self) if d.get('action') in ['store_true', 'store_false']: d.pop('metavar', None) ret.append(group.add_argument(*args, **d)) except Exception: print('Error while creating argument %s' % arg) raise else: raise ValueError('Parser has already been finalized!') self._finalized = True if subparsers and self._subparsers_action is not None: for parser in self._subparsers_action.choices.values(): parser.create_arguments(True) return ret
[docs] def append2help(self, arg, s): """Append the given string to the help of argument `arg` Parameters ---------- arg: str The function argument s: str The string to append to the help""" self.unfinished_arguments[arg]['help'] += s
[docs] def append2helpf(self, arg, s): """Append the given string to the help of argument `arg` Parameters ---------- arg: str The function argument s: str The string to append to the help""" return self._as_decorator('append2help', arg, s)
[docs] @staticmethod def format_bold(section, text): """Make a bold formatting for the section header""" return '**%s**\n\n%s' % (section, text)
[docs] @staticmethod def format_rubric(section, text): """Make a bold formatting for the section header""" return '.. rubric:: %s\n\n%s' % (section, text)
[docs] @staticmethod def format_heading(section, text): return '\n'.join([section, '-' * len(section), text])
[docs] def format_epilog_section(self, section, text): """Format a section for the epilog by inserting a format""" try: func = self._epilog_formatters[self.epilog_formatter] except KeyError: if not callable(self.epilog_formatter): raise func = self.epilog_formatter return func(section, text)
[docs] def extract_as_epilog(self, text, sections=None, overwrite=False, append=True): """Extract epilog sections from the a docstring Parameters ---------- text The docstring to use sections: list of str The headers of the sections to extract. If None, the :attr:`epilog_sections` attribute is used overwrite: bool If True, overwrite the existing epilog append: bool If True, append to the existing epilog""" if sections is None: sections = self.epilog_sections if ((not self.epilog or overwrite or append) and sections): epilog_parts = [] for sec in sections: text = docstrings._get_section(text, sec).strip() if text: epilog_parts.append( self.format_epilog_section(sec, text)) if epilog_parts: epilog = '\n\n'.join(epilog_parts) if overwrite or not self.epilog: self.epilog = epilog else: self.epilog += '\n\n' + epilog
[docs] def grouparg(self, arg, my_arg=None, parent_cmds=[]): """ Grouper function for chaining subcommands Parameters ---------- arg: str The current command line argument that is parsed my_arg: str The name of this subparser. If None, this parser is the main parser and has no parent parser parent_cmds: list of str The available commands of the parent parsers Returns ------- str or None The grouping key for the given `arg` or None if the key does not correspond to this parser or this parser is the main parser and does not have seen a subparser yet Notes ----- Quite complicated, there is no real need to deal with this function """ if self._subparsers_action is None: return None commands = self._subparsers_action.choices currentarg = self.__currentarg # the default return value is the current argument we are in or the # name of the subparser itself ret = currentarg or my_arg if currentarg is not None: # if we are already in a sub command, we use the sub parser sp_key = commands[currentarg].grouparg(arg, currentarg, chain( commands, parent_cmds)) if sp_key is None and arg in commands: # if the subparser did not recognize the command, we use the # command the corresponds to this parser or (of this parser # is the parent parser) the current subparser self.__currentarg = currentarg = arg ret = my_arg or currentarg elif sp_key not in commands and arg in parent_cmds: # otherwise, if the subparser recognizes the commmand but it is # not in the known command of this parser, it must be another # command of the subparser and this parser can ignore it ret = None else: # otherwise the command belongs to this subparser (if this one # is not the subparser) or the current subparser ret = my_arg or currentarg elif arg in commands: # if the argument is a valid subparser, we return this one self.__currentarg = arg ret = arg elif arg in parent_cmds: # if the argument is not a valid subparser but in one of our # parents, we return None to signalize that we cannot categorize # it ret = None return ret
[docs] def parse_known_args(self, args=None, namespace=None): if self._chain_subparsers: if args is None: args = sys.argv[1:] choices_d = OrderedDict() remainders = OrderedDict() main_args = [] # get the first argument to make sure that everything works cmd = self.__currentarg = None for i, (cmd, subargs) in enumerate(groupby(args, self.grouparg)): if cmd is None: main_args += list(subargs) else: # replace '-' by underscore ns_cmd = cmd.replace('-', '_') choices_d[ns_cmd], remainders[ns_cmd] = super( FuncArgParser, self).parse_known_args( list(chain(main_args, subargs))) main_ns, remainders[None] = self.__parse_main(main_args) for key, val in vars(main_ns).items(): choices_d[key] = val self.__currentarg = None if '__dummy' in choices_d: del choices_d['__dummy'] return Namespace(**choices_d), list(chain(*remainders.values())) # otherwise, use the default behaviour return super(FuncArgParser, self).parse_known_args(args, namespace)
def __parse_main(self, args): """Parse the main arguments only. This is a work around for python 2.7 because argparse does not allow to parse arguments without subparsers """ if six.PY2: self._subparsers_action.add_parser("__dummy") return super(FuncArgParser, self).parse_known_args( list(args) + ['__dummy']) return super(FuncArgParser, self).parse_known_args(args)
[docs] @docstrings.get_sections(base='FuncArgParser.update_short') @docstrings.dedent def update_short(self, **kwargs): """ Update the short optional arguments (those with one leading '-') This method updates the short argument name for the specified function arguments as stored in :attr:`unfinished_arguments` Parameters ---------- ``**kwargs`` Keywords must be keys in the :attr:`unfinished_arguments` dictionary (i.e. keywords of the root functions), values the short argument names Examples -------- Setting:: >>> parser.update_short(something='s', something_else='se') is basically the same as:: >>> parser.update_arg('something', short='s') >>> parser.update_arg('something_else', short='se') which in turn is basically comparable to:: >>> parser.add_argument('-s', '--something', ...) >>> parser.add_argument('-se', '--something_else', ...) See Also -------- update_shortf, update_long""" for key, val in six.iteritems(kwargs): self.update_arg(key, short=val)
[docs] @docstrings.dedent def update_shortf(self, **kwargs): """ Update the short optional arguments belonging to a function This method acts exactly like :meth:`update_short` but works as a decorator (see :meth:`update_arg` and :meth:`update_argf`) Parameters ---------- %(FuncArgParser.update_short.parameters)s Returns ------- function The function that can be used as a decorator Examples -------- Use this method as a decorator:: >>> @parser.update_shortf(something='s', something_else='se') ... def do_something(something=None, something_else=None): ... ... See also the examples in :meth:`update_short`. See Also -------- update_short, update_longf """ return self._as_decorator('update_short', **kwargs)
[docs] @docstrings.get_sections(base='FuncArgParser.update_long') @docstrings.dedent def update_long(self, **kwargs): """ Update the long optional arguments (those with two leading '-') This method updates the short argument name for the specified function arguments as stored in :attr:`unfinished_arguments` Parameters ---------- ``**kwargs`` Keywords must be keys in the :attr:`unfinished_arguments` dictionary (i.e. keywords of the root functions), values the long argument names Examples -------- Setting:: >>> parser.update_long(something='s', something_else='se') is basically the same as:: >>> parser.update_arg('something', long='s') >>> parser.update_arg('something_else', long='se') which in turn is basically comparable to:: >>> parser.add_argument('--s', dest='something', ...) >>> parser.add_argument('--se', dest='something_else', ...) See Also -------- update_short, update_longf""" for key, val in six.iteritems(kwargs): self.update_arg(key, long=val)
[docs] @docstrings.dedent def update_longf(self, **kwargs): """ Update the long optional arguments belonging to a function This method acts exactly like :meth:`update_long` but works as a decorator (see :meth:`update_arg` and :meth:`update_argf`) Parameters ---------- %(FuncArgParser.update_long.parameters)s Returns ------- function The function that can be used as a decorator Examples -------- Use this method as a decorator:: >>> @parser.update_shortf(something='s', something_else='se') ... def do_something(something=None, something_else=None): ... ... See also the examples in :meth:`update_long`. See Also -------- update_short, update_longf """ return self._as_decorator('update_long', **kwargs)
[docs] def parse2func(self, args=None, func=None): """Parse the command line arguments to the setup function This method parses the given command line arguments to the function used in the :meth:`setup_args` method to setup up this parser Parameters ---------- args: list The list of command line arguments func: function An alternative function to use. If None, the last function or the one specified through the `setup_as` parameter in the :meth:`setup_args` is used. Returns ------- object What ever is returned by the called function Note ---- This method does not cover subparsers!""" kws = vars(self.parse_args(args)) if func is None: if self._setup_as: func = kws.pop(self._setup_as) else: func = self._used_functions[-1] return func(**kws)
[docs] def parse_known2func(self, args=None, func=None): """Parse the command line arguments to the setup function This method parses the given command line arguments to the function used in the :meth:`setup_args` method to setup up this parser Parameters ---------- args: list The list of command line arguments func: function or str An alternative function to use. If None, the last function or the one specified through the `setup_as` parameter in the :meth:`setup_args` is used. Returns ------- object What ever is returned by the called function list The remaining command line arguments that could not be interpreted Note ---- This method does not cover subparsers!""" ns, remainder = self.parse_known_args(args) kws = vars(ns) if func is None: if self._setup_as: func = kws.pop(self._setup_as) else: func = self._used_functions[-1] return func(**kws), remainder
[docs] def parse_chained(self, args=None): """ Parse the argument directly to the function used for setup This function parses the command line arguments to the function that has been used for the :meth:`setup_args`. Parameters ---------- args: list The arguments parsed to the :meth:`parse_args` function Returns ------- argparse.Namespace The namespace with mapping from command name to the function return See also -------- parse_known_chained """ kws = vars(self.parse_args(args)) return self._parse2subparser_funcs(kws)
[docs] def parse_known_chained(self, args=None): """ Parse the argument directly to the function used for setup This function parses the command line arguments to the function that has been used for the :meth:`setup_args` method. Parameters ---------- args: list The arguments parsed to the :meth:`parse_args` function Returns ------- argparse.Namespace The namespace with mapping from command name to the function return list The remaining arguments that could not be interpreted See also -------- parse_known """ ns, remainder = self.parse_known_args(args) kws = vars(ns) return self._parse2subparser_funcs(kws), remainder
def _parse2subparser_funcs(self, kws): """ Recursive function to parse arguments to chained parsers """ choices = getattr(self._subparsers_action, 'choices', {}) replaced = {key.replace('-', '_'): key for key in choices} sp_commands = set(replaced).intersection(kws) if not sp_commands: if self._setup_as is not None: func = kws.pop(self._setup_as) else: try: func = self._used_functions[-1] except IndexError: return None return func(**{ key: kws[key] for key in set(kws).difference(choices)}) else: ret = {} for key in sp_commands: ret[key.replace('-', '_')] = \ choices[replaced[key]]._parse2subparser_funcs( vars(kws[key])) return Namespace(**ret)
[docs] def get_subparser(self, name): """ Convenience method to get a certain subparser Parameters ---------- name: str The name of the subparser Returns ------- FuncArgParser The subparsers corresponding to `name` """ if self._subparsers_action is None: raise ValueError("%s has no subparsers defined!" % self) return self._subparsers_action.choices[name]