Building CLIs With python-fire
Tags: beginners, python, 100daysofpython
Published: 2021-08-25 10:15:08

_**I write content for AWS, Kubernetes, Python, JavaScript and more. To view all the latest content, be sure to [visit my blog](https://blog.dennisokeeffe.com) and subscribe to my newsletter. [Follow me on Twitter](https://twitter.com/dennisokeeffe92).**_

> This is Day 6 of the #100DaysOfPython challenge.

This post will use the [Python Fire](https://github.com/google/python-fire) library to work through a simple example of setting up the library.

## Prerequisites

1. Familiarity with [Pipenv](https://pipenv.pypa.io/en/latest/). See [here](https://blog.dennisokeeffe.com/blog/2020-07-04-abcs-of-pipenv) for my post on Pipenv.

## Getting started

Let's create the `hello-fire` directory and install `python-fire`.

We will also create a file `cli.py` to hold our CLI script.

```s
# Make the `hello-fire` directory
$ mkdir hello-fire
$ cd hello-fire
# Make file the CLI script
$ touch cli.py

# Init the virtual environment
$ pipenv --three
$ pipenv install python-fire
```

We are now ready to add a script.

## The CLI script

For our demo example, we are going to take a slightly modified version of [grouping commands](https://github.com/google/python-fire/blob/master/docs/guide.md#grouping-commands) script to demo how to run subcommands.

Our aim is to have the following commands:

| Command            | Description                                                                                                               |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------- |
| `ingestion run`    | Print a message to the console to highlight that we have run the ingestion script                                         |
| `digestion status` | Print a message based on the value of the Digestion class `satiated` property                                             |
| `digestion run`    | Print a message to the console to highlight that we have run the digestion script and set the value of satiated to `True` |
| `run`              | Run both ingestion and digestion `run` commands and the digestion status                                                  |

## Adding the code

We set the base commands through the `Pipeline` class and the subcommands through their own class that is initiated as properties of the `Pipeline` class.

If you notice the optional `volume` argument for `DigestionStage.run`, it is used to set the volume of the `Digestion` class based on an argument passed to the CLI (defaulting to 1).

```py
#!/usr/bin/env python
import fire


class IngestionStage(object):
    def run(self):
        return 'Ingesting! Nom nom nom...'


class DigestionStage(object):
    def __init__(self):
        self.satiated = False

    def run(self, volume: int = 1) -> str:
        self.satiated = True
        return ' '.join(['Burp!'] * volume)

    def status(self):
        return 'Satiated.' if self.satiated else 'Not satiated.'


class Pipeline(object):

    def __init__(self):
        self.ingestion = IngestionStage()
        self.digestion = DigestionStage()

    def run(self):
        print(self.ingestion.run())
        print(self.digestion.run())
        print(self.digestion.status())
        return 'Pipeline complete'


if __name__ == '__main__':
    fire.Fire(Pipeline)
```

## Running the script

To run the script, we need to ensure we are running the Pipenv virtual environment.

We can do this with `pipenv shell`.

Once, in the shell, we can run our script and see the results:

```s
$ python cli.py ingestion run
# Ingesting! Nom nom nom...
$ python cli.py digestion run
# Burp!
$ python cli.py digestion run --volume=10
# Burp! Burp! Burp! Burp! Burp! Burp! Burp! Burp! Burp! Burp!
$ python cli.py status
# Not satiated.
$ python cli.py run
# Ingesting! Nom nom nom...
# Burp!
# Satiated.
# Pipeline complete
```

Running through our script, we can now see the results of our work.

Notice that the `status` property of the Digestion class is set to `True` when we run the `run` command and the number of "Burp!" messages printed is based on the `volume` argument passed to the `run` command.

## Summary

Today's post demonstrated how to use the `python-fire` package to write easier CLI scripts with their own subcommands and instance-managed state.

Of most languages that I have used, it must be said that `python-fire` has been one of the most approachable libraries I have seen for building out CLI tools.

## Resources and further reading

1. [The ABCs of Pipenv](https://blog.dennisokeeffe.com/blog/2020-07-04-abcs-of-pipenv) for the minimum you will need.
2. [Hello, JupyterLab](https://blog.dennisokeeffe.com/blog/2021-07-19-hello-jupyter-notebook).
3. [Python Fire](https://github.com/google/python-fire)
4. [Pipenv](https://pipenv.pypa.io/en/latest/)

_Photo credit: [cullansmith](https://unsplash.com/@cullansmith)_


_Originally posted on my [blog](https://blog.dennisokeeffe.com/blog/2021-07-25-building-clis-with-python-fire). To see new posts without delay, read the posts there and subscribe to my newsletter._