Skip to content

Latest commit

 

History

History
155 lines (106 loc) · 7.02 KB

File metadata and controls

155 lines (106 loc) · 7.02 KB

Figuring out Python Subcommands with ArgParse

Overview

While watching mttaggart from The Taggart Institute stream is programming on KASM manager with python, he ran into a question that has haunted me before and if answer, would certainly help me with other projects. That question is roughly, how the heck does one tackle subcommands with the built-in library of argparse in python. Taggart eventually went with click as a package for “beautiful command line interfaces”.

Currently I have a python project I’d like to use the subcommand of “load” or “unload” with it that both have different arguments associated with it. This example, rant, and hopeful proof-of-concept will help in utilizing subcommands. Specifically, this is for adversary emulation within a devops environment that is confirmed to have the python docker sdk but no other non-standard module assumptions.

The Setup

This is just utilizing the argparse module, to create two global variable values.

from argparse import ArgumentParser

parser = ArgumentParser()

parser.add_argument('--global-thing', action = 'store_true')
parser.add_argument('--global-value', default = 42)
parser.add_argument('--registry', help="The location of the registry to login", default="docker.io")
parser.add_argument('--repository', help="Where to push your image")
parser.add_argument('--username')
parser.add_argument('--password')
parser.add_argument('--DockerClient', help="The docker socket to use that you have permissiosn for", default="unix:///var/run/docker.sock/")

Subparsers, subcommands, and their subarguments

Next we want to start the subparsers pointing to the subcommand, note that this is what python will use when describing if you print the object, I also use the title parameter so that it shows up in -h. We do this with the parser above and running the add_subparsers method on it. Then we can continue by adding arguments to the subparsers, like we did with the global arguments.

subcommand_parser = parser.add_subparsers(dest='subcommand', title="subcommand", help="additonal subcommand help here")

load_parser = subcommand_parser.add_parser('load')
unload_parser = subcommand_parser.add_parser('unload')

Next is adding arguments specific to our subcommands with the add_argument method of the specific subcommand. We can see that unload doesn’t have any additional arguments and load has two additional arguments.

# Load First
load_parser.add_argument('--dataLoc', help="Provide the file location of the data to load into the container")
load_parser.add_argument('--dockerFile', help="Specify a specific dockerfile location. Warning! this assumes you load the data into the container manually with the dockerfile")

# Unload doesn't have additonal arguments, just executes different functions

Conclusion

  • I’m definitely more comfortable with subcommands now and pretty much finished my initial use case in this post.
  • If you wanted to add further subcommands, it would be as simple as adding another add_subparsers to the subcommand_parser object.

Results from parsing arguments

print("The default --help results")
print(parser.parse_args(['--help']))
print("\n\n\n---\n\n\n")
print("The default load --help results")
print(parser.parse_args(['load', '--help']))
The default --help results
usage:  [-h] [--global-thing] [--global-value GLOBAL_VALUE]
        [--registry REGISTRY] [--repository REPOSITORY] [--username USERNAME]
        [--password PASSWORD] [--DockerClient DOCKERCLIENT]
        {load,unload} ...

options:
  -h, --help            show this help message and exit
  --global-thing
  --global-value GLOBAL_VALUE
  --registry REGISTRY   The location of the registry to login
  --repository REPOSITORY
                        Where to push your image
  --username USERNAME
  --password PASSWORD
  --DockerClient DOCKERCLIENT
                        The docker socket to use that you have permissiosn for

subcommand:
  {load,unload}         additonal subcommand help here


---



The default load --help results
usage:  load [-h] [--dataLoc DATALOC] [--dockerFile DOCKERFILE]

options:
  -h, --help            show this help message and exit
  --dataLoc DATALOC     Provide the file location of the data to load into the
                        container
  --dockerFile DOCKERFILE
                        Specify a specific dockerfile location. Warning! this
                        assumes you load the data into the container manually
                        with the dockerfile

Tangled code

from argparse import ArgumentParser

parser = ArgumentParser()

parser.add_argument('--global-thing', action = 'store_true')
parser.add_argument('--global-value', default = 42)
parser.add_argument('--registry', help="The location of the registry to login", default="docker.io")
parser.add_argument('--repository', help="Where to push your image")
parser.add_argument('--username')
parser.add_argument('--password')
parser.add_argument('--DockerClient', help="The docker socket to use that you have permissiosn for", default="unix:///var/run/docker.sock/")

subcommand_parser = parser.add_subparsers(dest='subcommand', title="subcommand", help="additonal subcommand help here")

load_parser = subcommand_parser.add_parser('load')
unload_parser = subcommand_parser.add_parser('unload')

# Load First
load_parser.add_argument('--dataLoc', help="Provide the file location of the data to load into the container")
load_parser.add_argument('--dockerFile', help="Specify a specific dockerfile location. Warning! this assumes you load the data into the container manually with the dockerfile")

# Unload doesn't have additonal arguments, just executes different functions

print("The default --help results")
print(parser.parse_args(['--help']))

print("The default load --help results")
print(parser.parse_args(['load', '--help']))