Building the Automation Tool
In this final chapter, we stitch every concept into one flow. The goal is to build an automation tool that collects student study reports, summarizes them, and sends the result. The logic stays simple, but you will touch comprehensions, decorators, generators, context managers, type hints, async code, and packaging. Do not combine everything at once—move stage by stage and reopen the relevant chapter (decorators in part 14, context managers in 16, type hints in 17, async in 18, packaging in 19) whenever you need a refresher.
Key terms
- Pipeline: a data flow that connects input → processing → output in order
- Asynchronous notification: dispatching HTTP or other external sends concurrently via
asyncfunctions
Core ideas
Study notes
- Time required: 90–120 minutes (with per-module practice)
- Prerequisites: hands-on experience from parts 02–19, Typer CLI builds, basic pytest usage
- Goal: connect loader → summary → notifier → CLI into a single pipeline and log testing ideas
- JSONL (JSON Lines) stores one JSON object per line.
- Generators process files line by line to save memory.
- TypedDict documents the summary data shape for collaborators.
- Protocol expresses the async notification contract in code.
- Each stage title includes a reference to earlier chapters so you know where to review.
Code examples
Project overview
- Input: a JSONL file (
reports.jsonl) with per-student progress data - Processing: use a generator to read each line, filter with comprehensions, and log via decorators
- Output: send summaries through an async HTTP API
Project layout:
__init__.py
loader.py
summary.py
notifier.py
cli.py
stage0: "Stage 0
prep data"
stage1: "Stage 1 (Ch16)
loader.py
context + generator"
stage2: "Stage 2 (Ch13·14·17)
summary.py
comprehension + type hints"
stage3: "Stage 3 (Ch18)
notifier.py
async/await"
stage4: "Stage 4 (Ch12·15·19)
cli.py
Typer + packaging"
Listing both the data types and the chapter references next to each module helps you jump back to the right lesson whenever you get stuck.
Stage-by-stage checkpoints
Stage 0. Create JSONL sample data (Core)
reports.jsonl contains one study record per line.
{"student": "민지", "module": "functions", "score": 95, "done": true}
{"student": "준호", "module": "async", "score": 81, "done": false}
- Input prep: 5–10 lines are enough. Generate it with
Path.write_textfrom part 06 if you like. - Checkpoint: remove blank lines or malformed JSON now because Stage 1 will fail fast when
json.loadshits them.
Stage 1. Data loader (generator + context manager, Core)
# loader.py
from collections.abc import Iterator
from pathlib import Path
with path.open("r", encoding="utf-8") as f:
for line in f:
if line.strip():
yield json.loads(line)
- Use
withto open the file and close it automatically. - A generator processes each line without loading everything into memory.
Input → Output
- Input: JSONL file path
- Output:
Iterator[dict]
Line 1 → {'student': '민지', 'module': 'functions', ...}
Line 2 → {'student': '준호', 'module': 'async', ...}
If anything breaks, revisit part 16 (context managers) and part 06 (file I/O) to double-check cleanup.
Stage 2. Summaries (comprehensions + type hints, Core)
# summary.py
from typing import Iterable, TypedDict
class Summary(TypedDict):
student: str
avg_score: float
completed: list[str]
summaries = []
for student, items in group_by_student(records).items():
scores = [item["score"] for item in items]
completed = [item["module"] for item in items if item["done"]]
summaries.append(
{
"student": student,
"avg_score": sum(scores) / len(scores),
"completed": completed,
}
)
return summaries
grouped: dict[str, list[dict]] = {}
for record in records:
grouped.setdefault(record["student"], []).append(record)
return grouped
- Use list comprehensions to pluck conditional values.
- TypedDict documents the output.
Input → Output
- Input:
Iterator[dict]from Stage 1 - Output:
list[Summary]
[
{
"student": "민지",
"avg_score": 94.5,
"completed": ["functions", "decorators"],
},
...
]
If comprehensions feel fuzzy, revisit part 13; if decorators or type hints are rusty, jump back to parts 14 and 17 before comparing with this code.
Stage 2 extension: decorator-based logging (Optional)
# utils.py
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
logging.info(message)
return func(*args, **kwargs)
return wrapper
return decorator
# summary.py
from .utils import log_action
@log_action("Generating summaries")
def summarize(...):
...
Inject a shared log entry before Stage 2 does any work. The pattern mirrors the decorator practice from part 14, so both your CLI and tests emit the same messages.
Stage 3. Async notification module (Core → Plus)
# notifier.py
from typing import Protocol
class Notifier(Protocol):
async def send(self, payload: dict) -> None:
...
class WebhookNotifier:
def __init__(self, endpoint: str):
self.endpoint = endpoint
async def send(self, payload: dict) -> None:
async with httpx.AsyncClient(timeout=5) as client:
await client.post(self.endpoint, json=payload)
async def dispatch_all(notifier: Notifier, summaries):
tasks = [asyncio.create_task(notifier.send(summary)) for summary in summaries]
await asyncio.gather(*tasks)
- Use Protocol to document the interface.
async withserves as an async context manager to close the HTTP session safely.
Input → Output
- Input:
list[Summary], webhook URL, event loop - Output:
Noneon success, exception on failure (with logs)
Summary 1 → POST /webhook (payload 1)
Summary 2 → POST /webhook (payload 2)
If this feels unfamiliar, revisit parts 18 (async), 12 (logging), and 16 (context managers) to remember why async with httpx.AsyncClient matters.
Stage 4. CLI glue (Core)
# cli.py
from pathlib import Path
from .loader import load_reports
from .summary import summarize
from .notifier import WebhookNotifier, dispatch_all
app = typer.Typer(help="Study report summarizer")
@app.command()
def run(path: str, webhook: str):
records = load_reports(Path(path))
summaries = summarize(records)
notifier = WebhookNotifier(webhook)
asyncio.run(dispatch_all(notifier, summaries))
Input → Output
- Input: terminal command
uv run studybot run reports.jsonl https://hooks.slack.com/... - Output: success logs plus Slack messages
$ uv run studybot run reports.jsonl $WEBHOOK
INFO Generating summaries (Stage 2)
INFO Slack dispatch complete (Stage 3)
The CLI reuses part 12’s Typer structure, part 15’s iterator/generator emphasis, and part 19’s packaging flow. Add [project.scripts] studybot = "studybot.cli:app" to your pyproject.toml to ship it.
Testing ideas (Optional)
loader: create a temporary file and confirm the generator yields line by line.summarize: feed fixed input and assert averages plus completion lists.dispatch_all: usehttpx.MockTransportor a fake notifier to ensure the expected number of calls.
Why it matters
- Applying the entire series to one project locks in practical intuition.
- You experience how generators, type hints, async code, and CLIs fit together.
- Writing down test ideas keeps maintenance plans realistic.
- Completing Stages 1–4 reenacts the lessons from chapters 12–19 in order.
- Note the inputs and outputs for each stage so reviews stay lightweight.
Practice
- Follow along: Wire up
load_reports,summarize, anddispatch_all, then run the CLI with local JSONL samples. - Extend: Add the
log_actiondecorator for stage-level logs and accept extra options viatyper.Option, such as an output path. - Debug: Point the webhook to an invalid endpoint, observe the failure, then fix it using exception logs and test doubles.
- Definition of done: Keep CLI output logs plus testing notes, and capture future expansion ideas in your backlog.
Wrap-up
This mini project proves how much you can accomplish with pure Python. Whenever you feel stuck, follow the stage label plus the chapter number to revisit the right lesson. Because it reuses the Typer CLI from part 12 and the packaging checklist from part 19, you naturally review earlier content. Consider extending it in these directions:
- Add data validation with
pydantic. - Integrate with a
FastAPIorDjangobackend. - Polish CLI output via
richand schedule recurring runs withschedule.
The fundamentals you learned hold up regardless of framework. Revisit parts 01–11 for syntax basics and 12–19 for focused features whenever you need to expand this capstone. Instead of forcing every feature in at once, finish each stage, verify the output, and only then move forward.
💬 댓글
이 글에 대한 의견을 남겨주세요