Implementing sub-commands in Python with argparse

Implementing sub-commands in Python with argparse

Long chains of easier-to-read sub-commands have come to be all the rage these days, or maybe it’s just me. To fit into contemporary parlance, I feel like most programs historically used prefixes for sub-commands like -, like find’s -exec. These days, calling an application with docker volume ls or kubectl get all --all-namespaces is an everyday thing. Nowadays sed -i would be written like sed inplace.

Anyways, I have this Python Project where I originally used a complex web of one-dimensional optional arguments (all prefixed with --) for circumstances that were sometimes optional, and sometimes required. My first go was ugly. It mucks up the --help because nothing had any context relative to other options. Example:

--add --cf --dns --record --type A --name subdomain.domain.com --content 8.8.8.8

When enumerating additional arguments within the context of a sub-command, the --help is a lot more helpful. The equivalent of the above, but with greater elegance:

add cf dns record --type A --name subdomain.domain.com --content 8.8.8.8

Any sub-command not prefixed with -- is required until the last sub-command. Anything prefixed with -- is optional and/or non-sequential. It makes things much more clear and readable. Hence why it’s becoming more common.

I had found some argparse tutorials where I could implement one sub-command, but it just didn’t make sense to me.

I then tried using the Click package, but it’s paradigm made things even more confusing for me. Even after trying to tweak the example in Advanced Patterns, I was helplessly lost.

But eventually, I found a way to have multiple levels of sub-commands, and even wrapped their enumeration in for loops:

I hope this helps someone out there.

References

  • I wouldn’t have figured it out if it weren’t for this user and this gist: