Skip to Content
Course content

335: Practice Exercise: Building a Config-Driven CLI Tool

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've noticed a recurring pattern when developers start building CLI tools: they confuse "having a settings file" with "building a config-driven tool." It's a subtle distinction, but it's the difference between a tool that requires a code commit every time a requirement changes and a tool that can be repurposed by someone who doesn't even know how to use a debugger.

The Myth: A settings.py File is "Config-Driven"

Many of you likely have a habit of creating a settings.py or a constants.py file where you store a list of API endpoints, database names, or folder paths. You might think, "I've decoupled my configuration from my logic," and in a basic sense, you have. But look at this example of a tool meant to backup specific directories:

# settings.py
BACKUP_PATHS = ["/var/log/nginx", "/home/user/docs", "/etc/ssh"]
BACKUP_DESTINATION = "/mnt/backup_drive"

If your boss comes to you tomorrow and says, "We need to add the /var/www/html folder to the backup list," what happens? You open the editor, change the list, commit the code, and redeploy the tool. The "configuration" is still code. You're still in the deployment loop for a simple data change. That's not a config-driven tool; that's just a tool with global variables.

The Reality: The Engine and the Blueprint

A true config-driven CLI separates the Engine (the Python code that knows how to do things) from the Blueprint (a data file—YAML, JSON, or TOML—that tells the engine what to do). In a config-driven architecture, the Python code shouldn't care what the specific paths are; it should only care how to process any list of paths provided to it via an external file.

I prefer YAML for this because it's human-readable, but JSON works just as well. Imagine the same backup tool, but this time the Python script reads a config.yaml file. Now, a system administrator can add a new directory to the backup list by editing a text file on the server. They don't need Git, they don't need a Python environment, and they certainly don't need to touch your beautiful, tested source code.

Here is how I typically structure the "Engine" side of this: I load the config into a dictionary at startup and then use that dictionary to drive the logic. If I'm using argparse, I can even use the config file to dynamically generate the available choices for a command-line argument.

import yaml
import argparse

# The Engine
def main():
    with open("config.yaml", "r") as f:
        config = yaml.safe_load(f)

    parser = argparse.ArgumentParser()
    # We dynamically set the choices based on the config file!
    parser.add_argument(
        "--target", 
        choices=config['targets'].keys(), 
        help="Which target to backup?"
    )
    
    args = parser.parse_args()
    if args.target:
        path = config['targets'][args.target]
        print(f"Backing up {args.target} from {path}...")

if __name__ == "__main__":
    main()

By doing this, you've shifted the power. The Python script is now a generic processor. If you want to change the targets, you edit the YAML. If you want to change how the backup is performed (e.g., adding compression), you edit the Python. That separation is what makes a tool maintainable in a production environment.




📋 Practical Task

Exercise: Build a Dynamic API Health Checker

Your goal is to build a CLI tool that checks the status of various web services. However, the tool must be entirely config-driven. You cannot hardcode the names or URLs of the services in your Python file.

Requirements:

  • Create a file named services.json. This file should contain a dictionary where the keys are friendly names of services (e.g., "Google", "GitHub") and the values are their corresponding URLs.
  • Write a Python script that:
    1. Loads the services.json file.
    2. Uses argparse to accept a --service argument.
    3. The --service argument must have its choices dynamically populated from the keys in the JSON file.
    4. When a service is selected, the tool should use the requests library (or urllib) to send a GET request to the URL and print whether the service is "UP" (status code 200) or "DOWN".

Test your implementation: Add a new service to your JSON file and run the tool again. Notice how the --help output of your CLI automatically updates to include the new service without you changing a single line of Python code.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.