Python Command Line Trip: Deep into argparse

Author: HelloGitHub-Prodesire

Preface

In the previous article, "Deep into argparse (1)", we explored the basic functions of argparse, including parameter actions and parameter categories, and developed the ability to write a simple command line program.This article will continue to gain insight into the advanced gameplay of argparse, gain a glimpse into its full extent, and help us build our ability to implement complex command-line programs.

This series of articles uses Python 3 as the interpreter by default.
If you're still using Python 2, notice the syntax and library usage differences between the two.

Help

Auto Generate Help

Help information is output when you specify the -h or --help parameter in the command line program.argparse can automatically output help information by specifying add_help as a True or not.

>>> import argparse
>>> parser = argparse.ArgumentParser(add_help=True)
>>> parser.add_argument('--foo')
>>> parser.parse_args(['-h'])
usage: [-h] [--foo FOO]

optional arguments:
  -h, --help  show this help message and exit
  --foo FOO

If add_help=False, specifying -h on the command line will result in an error:

>>> import argparse
>>> parser = argparse.ArgumentParser(add_help=False)
>>> parser.add_argument('--foo')
>>> parser.parse_args(['-h'])
usage: [--foo FOO]
: error: unrecognized arguments: -h

Custom Help

ArgumentParser uses the formatter_class input parameter to control the output help format.
For example, by specifying formatter_class=argparse.RawTextHelpFormatter, we can make the help content follow the original format:

>>> import argparse
>>> parser = argparse.ArgumentParser(
...     add_help=True,
...     formatter_class=argparse.RawTextHelpFormatter,
...     description="""
...     description
...         raw
...            formatted"""
... )
>>> parser.add_argument(
...     '-a', action="store_true",
...     help="""argument
...         raw
...             formatted
...     """
... )
>>>
>>> parser.parse_args(['-h'])
usage: [-h] [-a]

    description
        raw
           formatted

optional arguments:
  -h, --help  show this help message and exit
  -a          argument
                      raw
                          formatted

By not specifying the help output for formatter_class, you can see the difference between descirption and -a:

>>> import argparse
>>> parser = argparse.ArgumentParser(
...     add_help=True,
...     description="""
...     description
...         notraw
...            formatted"""
... )
>>> parser.add_argument(
...     '-a', action="store_true",
...     help="""argument
...         notraw
...             formatted
...     """
... )
>>> parser.parse_args(['-h'])
usage: [-h] [-a]

description notraw formatted

optional arguments:
  -h, --help  show this help message and exit
  -a          argument notraw formatted

Parameter Group

Sometimes, we need to group the parameters so that they can be displayed together when the help information is displayed.

For example, a command line supports three parameter options--user, --password, and--push, which need to be placed in a group named authentication to indicate that they are authentication information.Then we can use ArgumentParser.add_argument_group to satisfy:

>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> group = parser.add_argument_group('authentication')
>>> group.add_argument('--user', action="store")
>>> group.add_argument('--password', action="store")
>>> parser.add_argument('--push', action='store')
>>> parser.parse_args(['-h'])
usage: [-h] [--user USER] [--password PASSWORD] [--push PUSH]

optional arguments:
  -h, --help           show this help message and exit
  --push PUSH

authentication:
  --user USER
  --password PASSWORD

You can see that when we output help information, --user and--password options appear in the authentication group.

Option parameter prefix

I don't know if you've noticed that option parameter prefixes may be different for command line programs on different platforms.On Unix, for example, the prefix is -; on Windows, most command-line programs, such as findstr, have an option parameter prefix of /.

In argparse, the option parameter prefix defaults to the Unix command line convention, that is, -.But it also supports custom prefixes, as shown in the following example:

>>> import argparse
>>> 
>>> parser = argparse.ArgumentParser(
...     description='Option prefix',
...     prefix_chars='-+/',
... )
>>> 
>>> parser.add_argument('-power', action="store_false",
...                     default=None,
...                     help='Set power off',
...                     )
>>> parser.add_argument('+power', action="store_true",
...                     default=None,
...                     help='Set power on',
...                     )
>>> parser.add_argument('/win',
...                     action="store_true",
...                     default=False)
>>> parser.parse_args(['-power'])
Namespace(power=False, win=False)
>>> parser.parse_args(['+power', '/win'])
Namespace(power=True, win=True)

In this example, we specify three option parameter Prefixes -, +, and /, thus:

  • Make power=False by specifying the option parameter -power
  • Make power=True by specifying the option parameter + power
  • Make win=True by specifying the option parameter/win

Shared Parser

Sometimes we need to share the parser to share the parameter configurations inside.For example, our command line tools need to support operations on Ali Cloud and AWS, both of which require specifying AccessKeyId and AccessessKeySecret to represent user identity and privileges.Sharing the parser is especially necessary so that you don't have to repeat code.

We can do this by defining a parent parser in base.py that holds AccessKey-related parameter configurations as a common parser.Since subsequent child parsers automatically generate help information, the parent parser here specifies add_help=False to not automatically generate help information:

# bash.py
import argparse

parser = argparse.ArgumentParser(add_help=False)

parser.add_argument('--ak-id', action="store")
parser.add_argument('--ak-secret', action="store")

Subparsers can then be defined in ali.py and aws.py, respectively, and specified by parents participation, inheriting common parameters and implementing their own parameters:

# ali.py
import argparse
import base

parser = argparse.ArgumentParser(
    parents=[base.parser],
)

parser.add_argument('--ros',
                    action="store_true",
                    default=False,
                    help='Using ROS service to orchestrate cloud resources')

print(parser.parse_args())
# aws.py
import argparse
import base

parser = argparse.ArgumentParser(
    parents=[base.parser],
)

parser.add_argument('--cloudformation',
                    action="store_true",
                    default=False,
                    help='Using CloudFormation service to orchestrate cloud resources')

print(parser.parse_args())

Finally, the parameters supported by ali.py and aws.py are looked at through the -h parameter, where the common parameters are--ak-id and--ak-secret, and the specific parameters are--ros and--cloudformation:

$ python3 ali.py -h

usage: ali.py [-h] [--ak-id AK_ID] [--ak-secret AK_SECRET] [--ros]

optional arguments:
  -h, --help            show this help message and exit
  --ak-id AK_ID
  --ak-secret AK_SECRET
  --ros                 Using ROS service to orchestrate cloud resources
$ python3 aws.py -h

usage: aws.py [-h] [--ak-id AK_ID] [--ak-secret AK_SECRET] [--cloudformation]

optional arguments:
  -h, --help            show this help message and exit
  --ak-id AK_ID
  --ak-secret AK_SECRET
  --cloudformation      Using CloudFormation service to orchestrate cloud
                        resources

Nested Parser

In the command line we described earlier, cli --a --b xxx is usually used.But another very common command line use is cli subcmd --a --b xxx.For example, when we push tags through git, we use git push --tags.

By implementing a nested parser, we can easily parse this form of subcommand.

In a nested parser, we define a parent parser as the entry to the entire command line, and then define N sub-parsers to correspond to N sub-commands, which accomplishes the entire function.

In the following example, we support the create and delete subcommands to create or delete a specified path.The delete command supports the--recursive parameter to indicate whether the specified path is deleted recursively:

# cli.py
import argparse

parser = argparse.ArgumentParser()

subparsers = parser.add_subparsers(help='commands')

# Create
create_parser = subparsers.add_parser(
    'create', help='Create a directory')
create_parser.add_argument(
    'dirname', action='store',
    help='New directory to create')

# Delete
delete_parser = subparsers.add_parser(
    'delete', help='Remove a directory')
delete_parser.add_argument(
    'dirname', action='store', help='The directory to remove')
delete_parser.add_argument(
    '--recursive', '-r', default=False, action='store_true',
    help='Recursively remove the directory',
)

print(parser.parse_args())

Specify -h directly to view the supported subcommand and parameter options:

$ python3 cli.py -h

usage: cli.py [-h] {create,delete} ...

positional arguments:
  {create,delete}  commands
    create         Create a directory
    delete         Remove a directory

optional arguments:
  -h, --help       show this help message and exit

Specify delete-h directly to see the parameter options supported by the delete subcommand:

$ python3 cli.py delete -h

usage: cli.py delete [-h] [--recursive] dirname

positional arguments:
  dirname          The directory to remove

optional arguments:
  -h, --help       show this help message and exit
  --recursive, -r  Recursively remove the directory

Custom Action

In the previous article, "Deep into argparse (1)", eight parametric actions were introduced, which can be said to cover most of the scene.However, there are certain requirements that cannot be met, such as the parameter values you want to obtain are all capitalized.In this case, custom actions come in handy.

Implementing a custom action class inherits from argparse.Action, which is passed into the action input of ArgumentParser.add_argument.When the parser parses a parameter, it calls the class's u call_u method, which has a signature of u call_u (self, parser, namespace, values, option_string=None), where:

  • Parser is a parser instance
  • namespace holds resolution results
  • values is the parameter value passed in from the command line
  • option_string as parameter option

In the following example, we pass in a word through--words and convert its value to uppercase in a custom action class:

# cli.py
import argparse

class WordsAction(argparse.Action):

    def __call__(self, parser, namespace, values,
                 option_string=None):
        print(f'parser = {parser}')
        print(f'values = {values!r}')
        print(f'option_string = {option_string!r}')

        values = [v.upper() for v in values]
        setattr(namespace, self.dest, values)


parser = argparse.ArgumentParser()
parser.add_argument('--words', nargs='*', action=WordsAction)

results = parser.parse_args()
print(results)
$ python3 cli.py --words foo bar

parser = ArgumentParser(prog='cli.py', usage=None, description=None, formatter_class=<class 'argparse.HelpFormatter'>, conflict_handler='error', add_help=True)
values = ['foo', 'bar']
option_string = '--words'
Namespace(words=['FOO', 'BAR'])

Subsection

Through a shallow-to-deep introduction to argparse, I believe you have a thorough understanding of its power and the ability to develop command line tools.But "I always feel shallow on paper, I never know I have to work on it."

In the next article, you'll use argparse to implement git commands that are common in your daily work. Is that exciting?


Welcome to the HelloGitHub Public Number for more information and content on open source projects

Explain Open Source Projects Series Launch - Let people who are interested in open source projects stop fearing and the sponsors of open source projects stop being alone.Follow our articles and you'll find it so easy to program, use, and participate in open source projects.Welcome to contact us to submit a contribution for more people to love and contribute to Open Source.

Tags: Python AWS git Unix

Posted on Mon, 26 Aug 2019 19:37:44 -0700 by tarleton