Connect everything with a CLI automation app
Time to weave together class design, exception handling, clean project structure, and tests. This twelfth post wraps up the first half of the series and sets up the advanced language topics ahead. We will build a Typer CLI that automates everyday tasks and prepares you for comprehensions, decorators, and type hints in the next chapters.
Key terms
- Typer: a Python CLI framework that declares commands and options directly from functions and type hints
- Subcommand: a nested command such as
mealbot mealthat organizes CLI features - Exit code: the integer returned to the OS;
0means success, values ≥1 indicate failure
Core ideas
Study notes
- Time: 70–80 minutes
- Prereqs:
requests, file I/O, exception handling, logging, pytest- Goal: build a Typer CLI that unifies error handling, logging, and tests into one command
- A CLI (Command Line Interface) is an app you run via commands.
- Typer reads type hints to generate help text automatically.
typer.Exitcontrols exit codes so schedulers and CI detect failures.
Code walkthrough
Install Typer and define the first command
uv add typer rich
Create src/mealbot/cli.py and start here.
🧭 What is Typer? A Python CLI framework built on top of Click. Decorate functions to declare commands and let type hints produce documentation for you.
from mealbot.fetcher import fetch_meal
from mealbot.notifier import SlackNotifier
app = typer.Typer(help="Automation tools for meals and announcements")
@app.command()
def meal(school_code: str, webhook: str):
"""Fetch today's meal and send it to Slack."""
data = fetch_meal(school_code)
SlackNotifier(webhook).notify(
"Today's menu: " + ", ".join(data["menu"])
)
typer.echo("Delivered")
if __name__ == "__main__":
app()
Wire in logging and error handling
from mealbot.errors import MealFetchError
@app.callback()
def configure_logging(verbose: bool = typer.Option(False, "-v", help="Show debug logs")):
level = logging.DEBUG if verbose else logging.INFO
logging.basicConfig(level=level, format="%(asctime)s %(levelname)s %(message)s")
@app.command()
def meal(...):
try:
...
except MealFetchError:
logging.exception("Meal fetch failed")
raise typer.Exit(code=1)
When a command fails, raise typer.Exit so CI jobs or schedulers see a non-zero exit status.
Expand with subcommands
@app.command()
def notify(status: str, webhook: str):
notifier = SlackNotifier(webhook)
notifier.notify(f"Deploy status: {status}")
@app.command()
def generate_report(days: int = 7):
typer.echo(f"Saving the last {days} days as CSV")
Ops tasks are usually repeated with different parameters. Subcommands group them naturally.
Register a package script
Add a [project.scripts] section to pyproject.toml.
[project.scripts]
mealbot = "mealbot.cli:app"
Now run uv run mealbot meal --school-code demo-school --webhook .... Deploy with pipx or uv tool install to reuse the CLI on any server.
Test and verify before shipping
uv run pytest
uv run mealbot meal --school-code demo-school --webhook $WEBHOOK_URL
Use tests to prove the core logic, then fire the CLI against a staging webhook once. In GitHub Actions you can add a cron workflow that runs the CLI every day to generate reports.
Expected output
A CLI should emit predictable logs for easier debugging and sharing.
:::terminal{title="Example mealbot run", showFinalPrompt="false"}
[
{ "cmd": "uv run mealbot meal --school-code demo-school --webhook https://hooks.test/demo", "output": "2026-03-17 07:30:10 INFO Starting meal fetch\n2026-03-17 07:30:10 INFO menu=['잡곡밥', '된장찌개', '불고기']\n2026-03-17 07:30:11 INFO Slack delivery complete\nDelivered", "delay": 500 },
{ "cmd": "uv run mealbot meal --school-code wrong-school --webhook https://hooks.test/demo", "output": "2026-03-17 07:31:02 ERROR Meal fetch failed\nTraceback (most recent call last): ...\nError: school not found\nExited with code 1", "delay": 500 }
]
:::
- Confirm success paths end with a clear completion message.
- Confirm failure paths log errors and exit with non-zero status.
- Confirm repeated runs print the same format for easier comparison.
Why it matters
A CLI turns repetitive ops into one-liners. Once Typer commands, logging, and error handling are in place, schools or study groups can automate notifications and reports. Registering the script means the same command works everywhere.
Practice
- Follow along: copy the
mealcommand and runuv run mealbot meal ...until you see the success message. - Extend: add a
reportsubcommand that reads JSON, prints a summary, and register it under[project.scripts]. - Debug: intentionally break the webhook URL, note the
typer.Exitcode and log message, then restore it. - Definition of done: log samples, error exit codes, and pytest output live in one note so the next chapter can continue immediately.
Wrap-up
We have passed a turning point for the series. With a CLI skeleton in place, chapters 13–19 will layer advanced language tools—comprehensions, decorators, context managers, type hints, async, and the standard library—on top of it. Leave this CLI intact so the capstone in part 20 can bring the entire series back together.
💬 댓글
이 글에 대한 의견을 남겨주세요