Home

Awesome

Action Hero Logo

PyPI - Python Version Code style: black codecov Build Status PEP8 PyPI - License

action_hero is a python package that helps you
write powerful command line applications using the built-in argparse library

Introduction · Quick Usage · Help & FAQ · Catalog · Development

Introduction

argparse is a python standard library module used to make command line applications. argparse provides ArgumentParser that parses user arguments and runs argparse.Actions on them.

action_hero augments argparse making it more capable by providing a large number of custom actions. For example, the FileIsWritableAction automatically verifies that file path(s) accepted as arguments are writable, even informing the user if they aren't. This saves you the trouble of doing all that work yourself. These actions can be pipelined as well. Nice, no? Browse the catalog for many such actions.

Quick Usage

1. Installation:

pip install action_hero

2. Quick Usage: Import an action and specify it when adding an argument to your parser.

from action_hero import FileIsReadableAction
...
parser.add_argument("--file", action=FileIsReadableAction)
...

3. Full Example: CLI program that counts number of lines of a file.

# examples/line_counter.py
import argparse

from action_hero import FileIsReadableAction


if __name__ == "__main__":

    # Create parser
    parser = argparse.ArgumentParser()

    # Add user argument "--file" and confirm that it will be readable
    parser.add_argument("--file", action=FileIsReadableAction)

    # Parse user arguments
    args = parser.parse_args()

    if args.file:
        # Print number of lines in file
        with open(args.file) as f:
            print("{} has {} lines".format(args.file, len(f.readlines())))
    else:
        # Print usage if no arguments were given
        parser.print_usage()

Run line_counter.py on the command line

$ ls
line_counter.py mary.md

$ python line_counter.py --file mary.md
mary.md has 39 lines

$ python line_counter.py
usage: line_counter.py [-h] [--file FILE]

$ python line_counter.py --file nofile.md
usage: line_counter.py [-h] [--file FILE]
line_counter.py: error: argument --file: File is not readable

Note: Supported Python Versions >= 3.5

Help and FAQ

Accepting action_values

There are times your action requires an additional value. For instance, when your argument accepts only filenames with md or markdown extensions. You can use the FileHasExtensionAction action for this and pass in the extensions to check for via action_values, like so —

parser.add_argument(
    "--file", 
    action=FileHasExtensionAction,
    action_values=["md", "markdown"]
)

Unless otherwise mentioned, action_values should be provided as a non-empty list of strings. e.g. action_values = ["md", "markdown"].

Pipelining multiple actions

The PipelineAction allows you to run multiple actions as a pipeline. Pass in your pipeline of actions as a list to action_values. If one of the actions you're passing in has it's own action_values, put that one as a tuple, like such: (FileHasExtensionAction, ["md", "markdown"]). Here's an example of pipelining actions for --file

  1. File has extensions md or markdown
  2. File exists
parser.add_argument(
    "--file", 
    action=PipelineAction, 
    action_values=[
        (FileHasExtensionAction, ["md", "markdown"]),
        FileExistsAction
    ]
)

Another helpful feature, this action provides is the order of error reporting. In the above example, if the supplied argument file did not have the markdown extensions, the error message would reflect that and exit. After the user redoes the entry with a valid filename the next action in the pipeline applies FileExistsAction which checks for existence. If the file does not exist, an error message about file not existing will be shown and exits allowing the user to try again.

Pipelining can save you a lot of manual condition checks. For example, here's how to check for an existing markdown file that is writable and empty, -

parser.add_argument(
    "--file", 
    action=PipelineAction, 
    action_values=[
        FileExistsAction, 
        (FileHasExtensionAction, ["md", "markdown"]),
        FileIsWritableAction,
        FileIsEmptyAction
    ]

Exceptions in this module

You'll come across two different exceptions in action_hero.

  1. ValueError: These are intended for you, the CLI developer. You'd want to fix any underlying issue that causes them before releasing your CLI. e.g. when action_values is an empty list.

  2. argparse.ArgumentError: These are intended for your CLI's users, so they might use the messages as hints to provide corrent command line options.

Not capturing user argument exceptions

argparse.ArgumentParser has a slightly unconventional approach to handling argparse.ArgumentErrors. Upon encountering one, it prints argument usage information, error and exits. I mention this, so you don't setup a try/except around parser.parse_args() to capture that exception.

In order to maintain consistency with the rest of your argparse code, exceptions in action_hero are also of type argparse.ArgumentError and causes system exit as well. More information can be found in PEP 389. Since this is expected behavior, I recommend you allow this exception and let it display usage information and exit.

Arguments accepting multiple values

Just like any other argparse.Action each action_hero.Action handles multiple values and provides relevant error messages.

FAQ

What do I need to know to use action_hero in my command line application?

Vanilla argparse knowledge should do it.

Where can I find information about argparse?

argparse is part of the Python standard library.

Is action_hero tied to the argparse module?

Yes (but technically no — any project that can use an argpoarse.Action should work as long as it handles the argparse.ArgumentError exception)

What type are the user argument exceptions?

argparse.ArgumentError{"helpful error message"}, just like any other argparse.Action.

Why re-implement actions already provided by argparse like the choices action?

In order to include them in PipelineAction.

There was no mention of humans! Does this work for humans?

Yes, it works for humans :)

Catalog

  1. Special actions:
ActionDescriptionaction_values
PipelineActionRun multiple actions as a pipelineActions to run as a pipeline. e.g. [FileExistsAction, FileIsWritableAction]. (Read more)
DebugActionPrint debug information. There can be multiple of these in a pipeline
  1. Path, Directory and File related actions:
ActionDescriptionaction_values
DirectoryDoesNotExistActionCheck if directory does not exist
DirectoryExistsActionCheck if directory exists
DirectoryIsExecutableActionCheck if directory is executable
DirectoryIsNotExecutableActionCheck if directory is not executable
DirectoryIsNotReadableActionCheck if directory is not readable
DirectoryIsNotWritableActionCheck if directory is not writable
DirectoryIsReadableActionCheck if directory is readable
DirectoryIsValidActionCheck directory is valid
DirectoryIsWritableActionCheck if directory is writable
EnsureDirectoryAction Ensure directory exists and create it if it doesnt
EnsureFileAction Ensure file exists and create it if it doesnt
FileDoesNotExistActionCheck if file doesnt exist
FileExistsActionCheck if file exists
FileIsEmptyActionCheck if file is empty
FileIsExecutableActionCheck if file is executable
FileIsNotEmptyActionCheck if file is not empty
FileIsNotExecutableActionCheck if file is not executable
FileIsNotReadableActionCheck if file is not readable
FileIsNotWritableActionCheck if file is not writable
FileIsReadableActionCheck if file is readable
FileIsValidActionCheck file is valid
FileIsWritableActionCheck if file is writable
FileHasExtensionActionCheck if file has specified extensionExtensions to check against. e.g. ["md", "markdown"]
PathDoesNotExistsActionCheck if path does not exist
PathExistsActionCheck if path exists
PathIsExecutableActionCheck if path is executable
PathIsNotExecutableActionCheck if path is not executable
PathIsNotReadableActionCheck if path is not writable
PathIsNotWritableActionCheck if path is not writable
PathIsReadableActionCheck if path is readable
PathIsValidActionCheck if path is valid
PathIsWritableActionCheck if path is writable
ResolvePathAction Resolves path to canonical path removing symbolic links if present
  1. Net & Email related actions:
ActionDescriptionaction_values
IPIsValidIPAddressActionCheck if ip is valid ipv4 or ipv6 address
IPIsValidIPv4AddressActionCheck if ip address is valid ipv4 address
IPIsValidIPv6AddressActionCheck if ip address is valid ipv6 address
URLIsNotReachableActionCheck if URL is not reachable
URLIsReachableActionCheck if URL is reachable
URLWithHTTPResponseStatusCodeActionCheck if upplied URL responds with expected HTTP response status codeStatus codes to check against. e.g. ["200", "201", "202", "204"]
EmailIsValidActionChecks if email address is valid
  1. Type related actions:
ActionDescriptionaction_values
IsConvertibleToFloatActionCheck if value is convertible to float
IsConvertibleToIntActionCheck if value is convertible to int
IsConvertibleToUUIDActionChecks if value is convertible to UUID
IsFalsyActionChecks if value is falsy
IsTruthyActionChecks if value is truthy
  1. Range related actions:
ActionDescriptionaction_values
  1. Miscellaneous actions:
ActionDescriptionaction_values
ChoicesActionArgument can only have values from provided choice(s)Choices e.g. ["red", "blue", "green"]
NotifyAndContinueActionPrint provided notification message(s)Message(s) e.g. ["This command will be deprecated in the next version."]
NotifyAndExitActionPrint provided notification message(s) and ExitMessage(s) e.g. ["This command has been deprecated", "Try --new-command"]
ConfirmActionPrint provided message and proceed with user confirmation yes or no.Message(s) e.g. ["Proceed to Installation? (Y/n)"]
GetInputAction Get user input and save to self.destMessage(s) e.g. ["Favorite color"]
GetSecretInputAction Get user input without displaying characters and save to the self.destMessage(s) e.g. ["Enter your Password"]
LoadJSONFromFileAction Return loaded JSON file(s)
LoadYAMLFromFileAction Return loaded YAML file(s)
LoadPickleFromFileAction Return unpickled file(s)
CollectIntoDictAction Collect values into a dictDelimiter(s) to split value(s) into key:value pair(s) e.g. [":", "="] (If multiple delimiters exist inside a value, only the first match is used)
CollectIntoListAction Collect values into a list
CollectIntoTupleAction Collect values into a tuple

Footnotes

Actions that can make changes to self.dest
Actions that can make changes to disk

Development

Thank you for using action_hero@kadimisetty ⭐️✨