Recommended resources
The freely available resources this lesson will direct you to.
Hello! Your inventory command now has a useful boundary: it validates arguments, performs the work, and reports expected file or data errors. But what happens when the code itself is wrong? This lesson gives the CLI a small, reproducible bug. You’ll read its traceback, inspect the running program with Python’s interactive debugger, and make a targeted correction. Allow about 40 minutes, including the videos and hands-on work.
Read the failure before changing code
A traceback tells you two related things: where execution failed and which calls brought it there. Start with its final line for the exception type and message. Then read upward through the frames. The lowest frame usually shows the operation that raised the exception; an earlier frame may show where the wrong value entered that operation.
Watch Python Morsels’ Traceback (most recent call last): Python’s tracebacks explained. Notice why identifying the failing line is not always the same as choosing the right place to fix the bug.
Traceback (most recent call last): Python's tracebacks explained
Python Morsels explains how to read a traceback across multiple function calls and use it to locate a fix.
Watch reading the frames, beginning with the exception message at the bottom. Then watch choosing the fix: the failing operation is inside one function, but the speaker checks its caller before deciding where conversion belongs.
Keep that distinction in mind. A traceback identifies a failure point; debugging establishes the cause.
Give the CLI a reproducible bug
Work from the project root in your virtual environment. In src/repo_assistant/cli.py, add this helper above main():
def progress_message(file_count: int) -> str:
return "Processed " + file_count + " files"
Inside main()’s existing try block, add the following after summary = summarize_inventory(inventory) and before save_summary(summary, args.output):
if args.verbose:
print(progress_message(summary.file_count), file=sys.stderr)
Leave the earlier --verbose reading message in place. This new message should report progress before writing the output, but its helper contains an intentional defect.
Run the command with the sample inventory from the previous lesson. Choose a new, disposable output path so you do not overwrite a report you want to keep:
python -m repo_assistant data/incoming/demo.json --output data/reports/demo-debug-summary.json --verbose
You should see the reading message followed by a traceback rather than a saved summary. Output details vary by Python version, but its final frames should resemble:
File ".../cli.py", line ..., in main
print(progress_message(summary.file_count), file=sys.stderr)
File ".../cli.py", line ..., in progress_message
return "Processed " + file_count + " files"
TypeError: can only concatenate str (not "int") to str
Read this as evidence, not just an instruction to edit the highlighted line. The last line says a string-concatenation operation received an integer. The progress_message frame shows the operation; the main frame shows its argument came from summary.file_count. The type annotation file_count: int describes the intended input—it does not convert that input or make string concatenation valid.
The CLI’s handler catches OSError and ValueError, not TypeError, so this programming error remains visible as a traceback. Adding a broad exception handler to silence it would not correct it. Because the failure occurs before save_summary(), this run should not create the chosen output file.
Inspect the value while the program is running
The traceback gives you a strong hypothesis: file_count is an integer where the helper treats it as text. An interactive debugger lets you verify that hypothesis in the actual execution, including values in the caller.
Watch the selected portions of anthonywritescode’s python debugger crash course: pdb / breakpoint. The demonstration uses a different project; focus on the debugger controls, which work the same way in your CLI.
python debugger crash course: pdb / breakpoint (beginner - intermediate) anthony explains #097
anthonywritescode demonstrates Python’s built-in breakpoint and the pdb commands needed to inspect a failure across call frames.
Watch setting breakpoints for the idea of pausing just before suspicious code. Continue with finding your place for continue, quit, and source listings; inspecting frames for where, up, down, and p; and controlling execution for next and step.
Temporarily put breakpoint() immediately before the helper’s return:
def progress_message(file_count: int) -> str:
breakpoint()
return "Processed " + file_count + " files"
Run the same CLI command again in an interactive terminal. At the (Pdb) prompt, try:
(Pdb) where
(Pdb) p file_count
(Pdb) p type(file_count)
(Pdb) up
(Pdb) p summary.file_count
(Pdb) down
(Pdb) c
where displays the active call stack, including main() and progress_message(). In the helper’s frame, p file_count should show 1 for the sample inventory, and p type(file_count) should show int. The up command selects the caller’s frame so you can inspect summary.file_count; down returns to the helper. Neither command rewinds execution—they change which frame’s variables you are viewing. Finally, c continues execution and reproduces the exception.
The debugger’s marker indicates the line about to run, not one that has already run. You could use n there to execute the return line instead of c; it would encounter the same error. Use q if you need to abandon a debugging session, but note that quitting the debugger is not a successful application run.
Correct the boundary and verify it
You have now checked both sides of the function call: the summary supplies a numeric count, and the progress helper needs to produce text. Keep the count numeric for the application; format it as text at the point where you create the message. Remove the temporary breakpoint and change the helper to:
def progress_message(file_count: int) -> str:
return f"Processed {file_count} files"
Rerun the verbose command. You should now see the reading message, Processed 1 files, and the CLI’s saved-summary message; inspect the output file to confirm it was written. Then run it without --verbose, using another disposable output path. That check matters because the new helper is called only on the verbose path: both paths should still save a summary.
Before moving on, remove any remaining breakpoint() calls. The correction is not “catch TypeError”; it is to preserve the numeric value for computation and format it when producing human-readable output.
What you can carry forward
When a Python command fails unexpectedly, read the exception and its message first, trace the relevant frames, and inspect the values before editing. Here, the traceback located invalid string concatenation; pdb confirmed the integer in both the helper and its caller; an f-string corrected the presentation boundary without changing the underlying data.
In the next lesson, you’ll turn checks like the verbose and non-verbose runs into pytest unit tests, including normal cases, edge cases, and expected exceptions.