Skip to content Skip to sidebar Skip to footer

Resolve Argparse Alias Back To The Original Command

I'm using a subparser/subcommand that has an alias. I'm using the dest option for the subparser to store the name of the subcommand so I can get it later. Currently if the subcom

Solution 1:

There was a Python bug/issue requesting this - saving the 'base' name, rather than the alias. You can't change that without changing argparse.py code. I think the change would limited to the Action subclass that handles subparsers. https://bugs.python.org/issue36664

But I point out that there's simpler way of handling this. Just use set_defaults as documented near the end of the https://docs.python.org/3/library/argparse.html#sub-commands section. There

parser_foo.set_defaults(func=foo)

is used to set a subparser specific function, but it could just as well be used to set the 'base' name.

parser_foo.set_defaults(name='theIncrediblyLongAlias')

Solution 2:

This was surprisingly difficult to dig out. When you add a subparser, it gets stored in the parents ._actions attribute. From there it is just digging through attributes to get what you need. Below I create dictionaries to reference the subparser arguments by the dest name, and then added a function that lets us remap the inputted arguments to the primary argument name.

from collections import defaultdict

defget_subparser_aliases(parser, dest):
    out = defaultdict(list)
    prog_str = parser.prog
    dest_dict = {a.dest: a for a in parser._actions}
    try:
        choices = dest_dict.get(dest).choices
    except AttributeError:
        raise AttributeError(f'The parser "{parser}" has no subparser with a `dest` of "{dest}"')

    for k, v in choices.items():
        clean_v = v.prog.replace(prog_str, '', 1).strip()
        out[clean_v].append(k)
    returndict(out)

defremap_args(args, mapping, dest):
    setattr(args, dest, mapping.get(getattr(args, dest)))
    return args

Using your example, we can remap the parse args using:

import argparse

parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='command', help='sub-command help')
parser_ag = subparsers.add_parser('mySubcommand',
                                  aliases=['m'],
                                  help='Subcommand help')

args = parser.parse_args('m'.split())
mapping = get_subparser_aliases(parser, 'command')
remap_args(args, mapping, 'command')
print(args)
# prints:
Namespace(command='mySubcommand')

Here is an example of it at work with multiple subparser levels.. We have a parser with an optional argument and a subparser. The subparser has 3 possible arguments, the last of which invoke another subparser (a sub-subparser), with 2 possible arguments.

You can examine either the top level parser or the first level subparser to see alias mappings.

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--someoption', '-s', action='store_true')

subparser1 = parser.add_subparsers(help='sub-command help', dest='sub1')
parser_r = subparser1.add_parser('reallyLongName', aliases=['r'])
parser_r.add_argument('foo', type=int, help='foo help')
parser_s = subparser1.add_parser('otherReallyLong', aliases=['L'])
parser_s.add_argument('bar', choices='abc', help='bar help')
parser_z = subparser1.add_parser('otherOptions', aliases=['oo'])

subparser2 = parser_z.add_subparsers(help='sub-sub-command help', dest='sub2')
parser_x = subparser2.add_parser('xxx', aliases=['x'])
parser_x.add_argument('fizz', type=float, help='fizz help')
parser_y = subparser2.add_parser('yyy', aliases=['y'])
parser_y.add_argument('blip', help='blip help')

get_subparser_aliases(parser, 'sub1')
# returns:
{'reallyLongName': ['reallyLongName', 'r'],
 'otherReallyLong': ['otherReallyLong', 'L'],
 'otherOptions': ['otherOptions', 'oo']}

get_subparser_aliases(parser_z, 'sub2')
# returns:
{'xxx': ['xxx', 'x'], 'yyy': ['yyy', 'y']}

Using this with the function above, we can remap the collected args to their longer names.

args = parser.parse_args('-s oo x 1.23'.split())
print(args)
# prints:
Namespace(fizz=1.23, someoption=True, sub1='oo', sub2='x')

for p, dest inzip((parser, parser_z), ('sub1', 'sub2')):
    mapping = get_subparser_aliases(p, dest)
    remap_args(args, mapping, dest)

print(args)
# prints:
Namespace(fizz=1.23, someoption=True, sub1='otherOptions', sub2='xxx')

Post a Comment for "Resolve Argparse Alias Back To The Original Command"