Back to Posts
Hand holding a smartphone with passcode screen illuminated, demonstrating a secure password concept which can be created using a Python Click CLI Password Generator

Develop a CLI Password Generator Using Python Click

Command Line Interfaces (CLI) are simply cool! This isn’t a quote you hear that often. It’s a shame, because they really are! You have probably heard of Graphical Unit Interfaces (GUIs) before, and if not, you’ve most definitely used one. The same goes for CLI. If you’re a developer and haven’t heard of it before, don’t worry; you’ve most probably used it without even knowing it. There are so many CLIs for applications that you probably didn’t know had one, for example, VLC, Blender, GIMP, and more.

CLIs are very powerful, especially for tasks that involve file manipulation or system configuration. When it comes to scripting and automation, CLI is essential since it allows users to create scripts that execute a series of commands. CLI commands usually follow a standardized format across different operating systems and platforms, providing a consistent experience for users. I mean, even XKCD has made a comic about it, so it must be correct, right?

Anyhow, what exactly is a CLI, and why do we use it?

Command Line Interface (CLI) is a text-based method for interacting with computers, offering a direct and efficient way to issue commands. Compared to the Graphical Unit Interface (GUI), CLI relies on text-based commands for interaction, emphasizing efficiency and automation, while GUI employs graphical elements for a visually intuitive and user-friendly experience. Doing a task for the first time in a GUI would probably go faster, but the same task performed thousands of times will most probably be done faster in a CLI.

Cool! How do we do it?

We can create a CLI tool with Python by using one of the following libraries: Click, Argparse, Fire, or Docopt. All of them have their advantages, but I chose Click because of its simple and intuitive syntax, automatic help page generation, and context-aware callback functions. When we’re creating the CLI, we’ll use Command Line Interface Guidelines. If you’re interested in reading them more in detail, see the following link: CLIG.

For this blog post, we’ll create a password generator that uses the secrets module in Python. The CLI tool is going to be named by the guardian.

Requirements

  • The tool should be able to run with the following command byteguardian generate_password
  • It should have the following optional options:
    • Length of password.
    • Include symbols in the password.
    • Include uppercase characters.
    • Include numbers.

For example, we should be able to run the following command in our terminal:

byteguardian generate-password --length 21 --include-symbols

Enough talking, let’s start coding!

First, create a new directory to store our code and navigate to the directory:

mkdir byteguardian && cd ./byteguardian

Secondly, we need to create a virtual environment and install the required dependencies in that environment.

python3 -m venv .byteguardian_venv && source .byteguardian_venv/bin/activate

This is how our terminal will look (depending on the styling of the terminal, of course):

(.byteguardian_venv)>

Once we’re inside the virtual environment, we install Click.

(.byteguardian_venv)> pip install click

After the installation is done, create file called commands.py using the following command:

touch commands.py

Now that we have our file, we’ll need to make it a Click command called generate_password.

# commands.py
import secrets
import string
import click

lower_case_alphabet = string.ascii_letters.lower()
upper_case_alphabet = lower_case_alphabet.upper()
symbols = string.punctuation
numbers = string.digits.lower()

@click.command()
@click.option("--length", type=int)
@click.option("--include-symbols", type=bool ,is_flag=True, show_default=True, default=False)
@click.option("--include-numbers", type=bool ,is_flag=True, show_default=True, default=False)
@click.option("--include-uppercase", type=bool, is_flag=True, show_default=True, default=False)
def generate_password(
    length, include_symbols, include_numbers, include_uppercase
) -> None:
		"""This script prints hello NAME COUNT times."""
    sequence = lower_case_alphabet

    if include_symbols:
        sequence += symbols

    if include_numbers:
        sequence += numbers

    if include_uppercase:
        sequence += upper_case_alphabet

    password = "".join(secrets.choice(sequence) for _i in range(length))

    click.echo(password)

A lot of code appeared out of nowhere! But don’t worry!

Firstly, we have the decorators. All the decorators are predefined in Click and will help Click generate extra and sweet functionality. Using the decorator @click.command() will define that the function will be a callable command in the terminal. Bear in mind that function names will be transformed into Kebab-case style, meaning our generate_password is going to be named as generate-password as a command.
Secondly, @click.option() defines an option for the command. In our case, that is, for example, the length of the password. Notice that each option is a parameter in the generate_password function. This is crucial, since otherwise the function will not take the option into account. The naming convention is also a little tricky since the option name (first parameter) needs to match one of the parameters that is defined in generate_password. Otherwise, the CLI will return TypeError: generate_password() got an unexpected keyword argument.

There are a couple of takeaways from this implementation. We don’t return values from the commands;instead, we send the values to stdout using click.echo(). This makes it possible to run the command with other commands, for example, piping.

However, it is not why we’re using Click. As we stated at the beginning of the blog post, we want to use the following command byteguardian generate-password . That means that we need to install the package. We’ll do this by converting our directory into a pyproject using pip to install the package locally and in development mode using the flag —ediable. Create a pyproject of byteguardian and update the folder structure of the directory.

Before

byteguardian/
	commands.py

After

byteguardian/
	byteguardian/
		commands.py
		__init__.py
		__main__.py
	pyproject.toml

The following is an basic pyproject.toml template that you can use!

[project]
name = "byteguardian"
version = "0.1.0"
authors = [{ name = "YOUR_NAME", email = "YOUR_EMAIL" }]
description = "PROJECT_DESCRIPTION"
requires-python = ">=3.9"
dependencies = [
    "click>=8.1"
]
[build-system]
requires = ["setuptools>=65.5.0", "wheel"]
build-backend = "setuptools.build_meta"

[project.scripts]
byteguardian = "src.__main__:cli"

In order to install our own custom cli package, we can run the following command with the flag —editable to have it in edit mode:

pip install --editable .

Use the pip list command to see the packages installed and look at that! We have byteguardian installed as a package.

Package      Version Editable project location
------------ ------- ----------------------------------------------
byteguardian 0.1.0   /path/to/byteguardian
click        8.1.7
pip          23.2.1
setuptools   68.2.2

Before we try the command we implemented, we should see the help page that is generated for us with Click.

byteguardian --help

This should result in the following:

> byteguardian --help
Usage: byteguardian [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  generate-password

Let’s try using our package now and generate a password using the generate-password command.

> byteguardian generate-password --length 31 --include-uppercase --include-symbols --include-numbers
iOPty7Ppz8ykoF&mEaU[C{sqxqKTZhX

And voila! We have now successfully created a CLI tool that can generate passwords for us. And, of course, the password generated isn’t used by anyone or shouldn’t be used by anyone!

Improve your code with my 3-part code diagnosis framework

Watch my free 30 minutes code diagnosis workshop on how to quickly detect problems in your code and review your code more effectively.

When you sign up, you'll get an email from me regularly with additional free content. You can unsubscribe at any time.

Recent posts