When the Outside World Goes Wrong · practice
Catch Where the Failure Has Meaning
The inclusive filter is repaired. The command still has one over-wide boundary inherited from Chapter 8:
try:
document = convert_catalog(input_path, output_path, minimum_quantity)
except (FileNotFoundError, UnicodeDecodeError, csv.Error, ValueError) as exc:
...
That try block spans reading, selection, validation, and writing. Location matters as much as class. A ValueError from the writer’s validation means project code handed its own writer an invalid internal document. Normalizing both would erase that distinction.
Put a boundary around one understood operation
Create run_catalog(input_path, output_path, minimum_quantity). Its first boundary surrounds only the read:
try:
items = read_catalog_csv(input_path)
except (OSError, UnicodeDecodeError, csv.Error, ValueError) as exc:
print(f"Could not read {input_path}: {exc}", file=sys.stderr)
return 1
OSError is the operating-system family that includes missing paths, permission failures, and other failed file operations. Here it has one meaning: the requested input could not be opened or read. UnicodeDecodeError, csv.Error, and ValueError mean the bytes, CSV syntax, or declared schema could not become a catalog.
The message includes the exact input path and the bounded cause. stdout stays empty. Most importantly, the writer has not run, so a pre-existing output file remains byte-for-byte unchanged.
Run selection after that handler, outside every try:
selected = select_items(items, minimum_quantity)
If selection raises KeyError, TypeError, ValueError, RuntimeError, or even OSError, the original exception escapes. Selection does not touch the outside world, so calling its defect a read or write failure would be false.
Give the output operation its own boundary
Surround only the writer:
try:
document = write_catalog_json(selected, output_path)
except OSError as exc:
print(f"Could not write {output_path}: {exc}", file=sys.stderr)
return 1
Only an operating-system write failure is expected here. A ValueError or TypeError from validation is an internal invariant failure and must escape unchanged.
Print the success line only after write_catalog_json returns. “Wrote N items” is a claim about a completed operation, so printing it earlier would create a success-shaped lie.
The honest artifact limit
This project writes directly to the requested path. If the
There is also no automatic retry. Retrying a missing path or permission error without changing anything merely repeats the same case. The diagnostic gives the caller evidence; it does not invent a recovery policy.
Update main(argv=None) so argparse still owns help and grammar errors, then forward its three parsed values to run_catalog. The complete contract is:
| Situation | stdout | stderr | status |
|---|---|---|---|
| success | Wrote N items to PATH. | empty | 0 |
| read/input failure | empty | Could not read INPUT: cause | 1 |
output OSError | empty | Could not write OUTPUT: cause | 1 |
| parser error | empty | argparse usage and error | 2 |
| unexpected defect | none promised | original | nonzero |
Implement the two operation-sized boundaries.
The test of whether you got it right is not whether the program stops crashing. It is whether the same exception class gets different treatment depending on where it came from. A ValueError from the reader should produce a short message; a ValueError from the writer’s own validation should still produce a traceback. If one broad try covers both, you cannot tell those two apart, and neither can the person running your program.
A ValueError reaches your code. Where it came from decides what to do. Why?
Why does select_items run outside every try block?
Task
Refactor the run_catalog(input_path, output_path, minimum_quantity).
Catch
OSError,UnicodeDecodeError,csv.Error, andonly around read_catalog_csv, then printCould not read INPUT_PATH: CAUSEto stderr and return1.Run
select_itemsoutside everytryblock.Catch only
OSErroraroundwrite_catalog_json, then printCould not write OUTPUT_PATH: CAUSEto stderr and return1.After a successful write, print
Wrote N items to OUTPUT_PATH.to stdout and return0.Make
main(argv=None)parseand return run_catalog(...).
Keep argparse help/status 0 and grammar/status 2. Let selection defects and writer validation errors escape unchanged. Do not add retries or rollback claims.