Site

Demonstrations — Code Helper Tool

Tutorial D1.0  •  AI / Learn / Demonstrations

D1.0 What This Teaches

This tutorial combines file reading, streaming output, code review, and test generation into one production-quality console tool. It covers:

D1.1 Tool Design

The tool runs as:
python code_helper.py <command> <file> [options]
Three commands are supported:
CommandWhat it does
explainStreams an explanation of the file to the terminal
reviewStreams a code review with issues and suggestions
testsGenerates pytest tests and writes them to test_<filename>.py
Options: --model overrides the default model name; --output saves the streamed text to a file.

D1.2 Project Layout

code_helper/
├── code_helper.py      ← main CLI
├── .env                ← ANTHROPIC_API_KEY=sk-ant-...
└── requirements.txt    ← anthropic
                           python-dotenv
python-dotenv reads the .env file and sets the environment variable automatically. This avoids exporting the key in every terminal session.
pip install anthropic python-dotenv

D1.3 The argparse CLI

argparse is the standard library module for building command-line interfaces. It handles argument parsing, type checking, and help text automatically.
# argparse setup for code_helper.py
import argparse

def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="AI-powered code helper: explain, review, or generate tests."
    )
    parser.add_argument(
        "command",
        choices=["explain", "review", "tests"],
        help="What to do with the file",
    )
    parser.add_argument(
        "file",
        help="Path to the source file",
    )
    parser.add_argument(
        "--model",
        default="claude-sonnet-4-6",
        help="Claude model to use (default: claude-sonnet-4-6)",
    )
    parser.add_argument(
        "--output",
        help="Save output to this file in addition to printing",
    )
    return parser
choices=["explain","review","tests"] makes argparse reject any other command with a clear error message before your code runs.

D1.4 The explain Command

The explain command reads the file, streams the explanation to the terminal, and optionally writes the full text to --output.
# explain command implementation.
EXPLAIN_SYSTEM = (
    "You are a code explainer. Structure your response as: "
    "(1) Purpose, (2) Key Components, (3) How It Works, (4) Potential Issues."
)

def cmd_explain(client, args, code: str, language: str):
    prompt = f"Explain this {language} code:\n\n```{language}\n{code}\n```"
    with client.messages.stream(
        model=args.model,
        max_tokens=1024,
        system=EXPLAIN_SYSTEM,
        messages=[{"role": "user", "content": prompt}],
    ) as stream:
        stream_to_output(stream, args.output)

D1.5 The review Command

The review command uses the same streaming pattern but with a code review system prompt. The only difference is the system prompt and the prompt wording.
# review command implementation.
REVIEW_SYSTEM = (
    "You are an expert code reviewer. "
    "Structure your response as: (1) Summary, (2) Issues Found, (3) Suggestions."
)

def cmd_review(client, args, code: str, language: str):
    prompt = f"Review this {language} code:\n\n```{language}\n{code}\n```"
    with client.messages.stream(
        model=args.model,
        max_tokens=1024,
        system=REVIEW_SYSTEM,
        messages=[{"role": "user", "content": prompt}],
    ) as stream:
        stream_to_output(stream, args.output)
Both cmd_explain and cmd_review call the same stream_to_output helper, keeping the streaming and file-writing logic in one place.

D1.6 The tests Command

Test generation does not stream - you want the complete test file before writing it to disk. The output filename defaults to test_<original_name>.py.
# tests command implementation.
TESTS_SYSTEM = (
    "You are a test generator. Generate pytest tests for the given code. "
    "Include at least one test per public function. "
    "Output only the Python test file, no explanation."
)

def cmd_tests(client, args, code: str, language: str):
    from pathlib import Path
    prompt = f"Generate pytest tests for this {language} code:\n\n```{language}\n{code}\n```"

    response = client.messages.create(
        model=args.model,
        max_tokens=2048,
        system=TESTS_SYSTEM,
        messages=[{"role": "user", "content": prompt}],
    )
    test_code = response.content[0].text

    out_path = args.output or f"test_{Path(args.file).stem}.py"
    with open(out_path, "w", encoding="utf-8") as f:
        f.write(test_code)
    print(f"Tests written to: {out_path}")

D1.7 Complete Implementation

# code_helper.py - AI-powered code helper: explain, review, or generate tests.
# Usage: python code_helper.py <command> <file> [--model MODEL] [--output FILE]

import sys
import argparse
from pathlib import Path
from dotenv import load_dotenv
import anthropic

load_dotenv()

EXPLAIN_SYSTEM = (
    "You are a code explainer. Structure your response as: "
    "(1) Purpose, (2) Key Components, (3) How It Works, (4) Potential Issues."
)
REVIEW_SYSTEM = (
    "You are an expert code reviewer. "
    "Structure your response as: (1) Summary, (2) Issues Found, (3) Suggestions."
)
TESTS_SYSTEM = (
    "You are a test generator. Generate pytest tests for the given code. "
    "Include at least one test per public function. "
    "Output only the Python test file, no explanation."
)
EXT_TO_LANG = {
    ".py": "Python", ".js": "JavaScript", ".cpp": "C++",
    ".rs": "Rust",   ".cs": "C#",         ".ts": "TypeScript",
}

def read_source(path: str) -> str:
    p = Path(path)
    if not p.exists():
        print(f"Error: file not found: {path}")
        sys.exit(1)
    content = p.read_text(encoding="utf-8")
    if not content.strip():
        print(f"Error: file is empty: {path}")
        sys.exit(1)
    return content

def detect_language(path: str) -> str:
    return EXT_TO_LANG.get(Path(path).suffix.lower(), "code")

def stream_to_output(stream, output_path: str | None):
    collected = []
    for chunk in stream.text_stream:
        print(chunk, end="", flush=True)
        collected.append(chunk)
    print()   # final newline
    if output_path:
        Path(output_path).write_text("".join(collected), encoding="utf-8")
        print(f"\nSaved to: {output_path}")

def cmd_explain(client, args, code: str, language: str):
    prompt = f"Explain this {language} code:\n\n```{language}\n{code}\n```"
    with client.messages.stream(
        model=args.model, max_tokens=1024, system=EXPLAIN_SYSTEM,
        messages=[{"role": "user", "content": prompt}],
    ) as stream:
        stream_to_output(stream, args.output)

def cmd_review(client, args, code: str, language: str):
    prompt = f"Review this {language} code:\n\n```{language}\n{code}\n```"
    with client.messages.stream(
        model=args.model, max_tokens=1024, system=REVIEW_SYSTEM,
        messages=[{"role": "user", "content": prompt}],
    ) as stream:
        stream_to_output(stream, args.output)

def cmd_tests(client, args, code: str, language: str):
    prompt = f"Generate pytest tests for this {language} code:\n\n```{language}\n{code}\n```"
    response = client.messages.create(
        model=args.model, max_tokens=2048, system=TESTS_SYSTEM,
        messages=[{"role": "user", "content": prompt}],
    )
    test_code = response.content[0].text
    out_path  = args.output or f"test_{Path(args.file).stem}.py"
    Path(out_path).write_text(test_code, encoding="utf-8")
    print(f"Tests written to: {out_path}")

def main():
    parser = argparse.ArgumentParser(description="AI-powered code helper.")
    parser.add_argument("command", choices=["explain", "review", "tests"])
    parser.add_argument("file")
    parser.add_argument("--model",  default="claude-sonnet-4-6")
    parser.add_argument("--output", default=None)
    args = parser.parse_args()

    code     = read_source(args.file)
    language = detect_language(args.file)
    client   = anthropic.Anthropic()

    {"explain": cmd_explain, "review": cmd_review, "tests": cmd_tests}[
        args.command
    ](client, args, code, language)

if __name__ == "__main__":
    main()

D1.8 Sample Session

$ python code_helper.py explain mylib.py

(1) Purpose - mylib.py provides utility functions for string normalization and
text processing used across the project.

(2) Key Components
- normalize(s): strips whitespace and converts to lowercase
- word_count(s): returns the number of words in a string
- truncate(s, n): shortens a string to at most n characters

(3) How It Works
Each function takes a string, applies one transformation, and returns the result.
No state is shared between calls.

(4) Potential Issues
- truncate() does not add an ellipsis; callers may not know text was cut.
- word_count() splits on whitespace only; punctuation-attached words count as one.
$ python code_helper.py review mylib.py --output review.txt

(1) Summary - The module is clean and focused. Three functions, each doing one job.

(2) Issues Found
- No type hints on any function; callers cannot see expected types.
- word_count("") returns 0 correctly but is not tested.

(3) Suggestions
- Add type hints: def normalize(s: str) -> str
- Add a docstring to each function.
- Consider adding an ellipsis option to truncate().

Saved to: review.txt
$ python code_helper.py tests mylib.py
Tests written to: test_mylib.py

$ pytest test_mylib.py -v
collected 6 items

test_mylib.py::test_normalize_strips_whitespace  PASSED
test_mylib.py::test_normalize_lowercases         PASSED
test_mylib.py::test_word_count_basic             PASSED
test_mylib.py::test_word_count_empty             PASSED
test_mylib.py::test_truncate_short               PASSED
test_mylib.py::test_truncate_long                PASSED
6 passed in 0.12s

D1.9 Exercise

Exercise Add a compare command that takes two filenames, computes a unified diff, and streams a review of the changes.
  1. Add "compare" to the choices list in the parser.
  2. Add a second positional argument file2 to the parser. Make it optional (nargs="?") so the existing commands still work with one file.
  3. Define cmd_compare(client, args, code1, code2, language). Use difflib.unified_diff() to compute the diff, then stream a review of it.
  4. Usage: python code_helper.py compare old.py new.py
The diff review prompt: "Here is a unified diff between two versions of a Python file. Review the changes: what was fixed, what was added, and are there any concerns?"

D1.10 Common Mistakes

Hard-coding the model name

Writing model="claude-sonnet-4-6" directly in the API call means switching models requires editing the source. The --model argument passes the name through from the command line, so users and scripts can override it without changing the code.

Not handling empty files

If read_source() returns an empty string and you send it to Claude, the model produces a confused response like "I don't see any code to explain." Check if not content.strip() after reading and exit with a clear error message.

Silently overwriting existing output files

When --output points to an existing file, write_text() overwrites it without warning. For a tool used on real projects, check Path(output_path).exists() before writing and either warn the user or add an --overwrite flag.

D1.11 Key Terms

TermMeaning
argparsePython standard library module for parsing command-line arguments and generating help text
commandA positional argument that selects which action to perform (explain, review, tests)
choicesAn argparse parameter that restricts a positional argument to a fixed set of values
streaming outputPrinting tokens to the terminal as they arrive rather than buffering the full response first
modular CLIA CLI where each command is a separate function, making it easy to add or change commands
.envA plain text file containing environment variable assignments; read by python-dotenv at startup
helper functionA small function (like read_source or stream_to_output) shared by multiple commands to avoid duplication
difflibPython standard library module for computing differences between sequences of text