CLI Prompts In Python
Tags: beginners, python, 100daysofpython
Published: 2021-08-31 10:15:12

_**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 10 of the #100DaysOfPython challenge.

This post will use the [`PyInquirer`](https://github.com/CITGuru/PyInquirer/) library to demonstrate how to add some command line prompts to make it easier to build an interactive program.

It will build off the work done on learning [Python Fire](https://github.com/google/python-fire) basics in a [previous blog post](https://blog.dennisokeeffe.com/blog/2021-07-25-building-clis-with-python-fire).

## 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.
2. [Building CLIs with Python](https://blog.dennisokeeffe.com/blog/2021-07-25-building-clis-with-python-fire).

## Getting started

This code works off what was done in [Building CLIs with Python](https://blog.dennisokeeffe.com/blog/2021-07-25-building-clis-with-python-fire).

You should already have the code. In that blog post, all code was posted into a `hello-fire` directory with `cli.py` as the main file with a few dependencies already installed.

The code is available on my [GitHub repo](https://github.com/okeeffed/hello-python-fire).

We are going to add the [`PyInquirer`](https://github.com/CITGuru/PyInquirer/) to our dependencies to use in the `cli.py` file:

```s
# Add the lib
$ pipenv install pyinquirer
```

At this stage, we had a `cli.py` file that looks like the following:

```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)
```

Opening the virtual environment and running `python cli.py [command]` would run the program (ie `python cli.py digestion run` would output `Burp!`).

## Simple input prompt

For our first prompt, we are going to update the `DigestionStage` class `run` method to default to 0 burps and request that we provide a volume.

We do this with Python Fire as a call without an value for the argument would raise an error.

We can import the `prompt` function and use that to get the input:

```py
from PyInquirer import prompt

# ... omitted for brevity
class DigestionStage(object):
    def __init__(self):
        self.satiated = False

		# Update code with prompt question here
    def run(self, volume: int = 0) -> str:
        questions = [
            {
                'type': 'input',
                'name': 'volume',
                'message': 'How many burps?',
            }
        ]
        if volume == 0:
            volume = int(prompt(questions)['volume'])
        self.satiated = True
        return ' '.join(['Burp!'] * volume)

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

Now if we run our command line call `python cli.py digestion run`:

```s
$ python cli.py digestion run
? How many burps?  5
Burp! Burp! Burp! Burp! Burp!
```

A simple input prompt is all we need to get started.

Note: We could have simply removed `volume` altogether but we have left is so that Python Fire can cast the input into an int via a flag `--volume` without a prompt.

```s
$ python cli.py digestion run --volume=10
# ... no prompt
Burp! Burp! Burp! Burp! Burp! Burp! Burp! Burp! Burp! Burp!
```

## Working with dialogs

Prompt toolkit also comes with a way to create dialogs. There are choices between simple yes/no dialogs to helps for lists of options that operate as a "radio" or "checkbox".

Update the `DigestionStage` to look like the following:

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

    def run(self, volume: int = 0) -> str:
        questions = [
            {
                'type': 'input',
                'name': 'volume',
                'message': 'How many burps?',
            }
        ]
        if volume == 0:
            volume = int(prompt(questions)['volume'])
        self.satiated = True
        return ' '.join(['Burp!'] * volume)

		# Update function with dialog questions here
    def breakfast(self):
        questions = [
            {
                'type': 'list',
                'name': 'breakfast',
                'message': 'What did you want for breakfast?',
                'choices': ['eggs', 'bacon', 'toast']
            }
        ]

				# We are going to use associative arrays to switch on the value of the key
				# to determine how many burps are required
        switcher = {
            'eggs': 1,
            'bacon': 2,
            'toast': 3,
        }

        volume = switcher.get(prompt(questions)['breakfast'], 0)

        self.satiated = True
        return ' '.join(['Burp!'] * volume)

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

We have no added `digestion breakfast` command that we can run with `python run digestion breakfast`:

```s
$ python cli.py digestion breakfast
? What did you want for breakfast?  bacon
Burp! Burp!
$ python cli.py digestion breakfast
? What did you want for breakfast?  eggs
Burp!
$ python cli.py digestion breakfast
? What did you want for breakfast?  toast
Burp! Burp! Burp!
```

Now we have a `digestion breakfast` command that will run the `breakfast` method and output the result of our input into the dialog.

## Summary

We have used [`PyInquirer`](https://github.com/CITGuru/PyInquirer/) to demo a simple command line input as well as a way to pick an option from a list.

There are many examples that they have on their [GitHub](https://github.com/CITGuru/PyInquirer/tree/master/examples) if you would like to learn more.

## Resources and further reading

1. [The ABCs of Pipenv](https://blog.dennisokeeffe.com/blog/2020-07-04-abcs-of-pipenv)
2. [Building CLIs with Python Fire](https://blog.dennisokeeffe.com/blog/2021-07-25-building-clis-with-python-fire)
3. [Pipenv](https://pipenv.pypa.io/en/latest/)
4. [PyInquirer](https://github.com/CITGuru/PyInquirer/)
5. [More examples of PyInquirer](https://github.com/CITGuru/PyInquirer/tree/master/examples)
6. [Python Fire](https://github.com/google/python-fire)

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


_Originally posted on my [blog](https://blog.dennisokeeffe.com/blog/2021-07-29-cli-prompts-in-python). To see new posts without delay, read the posts there and subscribe to my newsletter._