Putting it together — loading and validating external data

The realistic shape of the problem

Every piece of this lesson has been building toward one specific, extremely common task: taking data that arrived from outside your program — a file someone else wrote or generated — and turning it into something your code can actually trust. That single task touches everything covered so far:

  • Open the file safely, with with — it might not even exist.
  • Parse it as JSON — the text itself might be malformed.
  • Validate the parsed data against a Pydantic BaseModel from the previous lesson — the JSON might be well-formed but still not match what your code expects.
  • Turn whatever went wrong into a clear, specific, custom exception — so calling code can handle “the config was bad” as one coherent kind of failure, regardless of which of the three steps actually failed.
  • Log what happened before it propagates further.

This section builds exactly that pipeline, end to end.

The pieces, assembled

Try it — edit and run

Every piece here is something you’ve already built separately: with open(...) and json.load from earlier this lesson, AgentConfig(BaseModel) from the previous lesson, AgentError/ConfigError from this lesson’s custom exceptions section, raise ... from e chaining, and logger.exception() — this function is genuinely nothing new, just every earlier piece composed into one realistic flow.

Watching each failure mode independently

The value of this structure is that each of the three failure modes — missing file, broken JSON, wrong shape — surfaces as the exact same ConfigError type to whatever calls load_agent_config, while still preserving exactly what actually went wrong underneath, via chaining:

Try it — edit and run
Try it — edit and run

Calling code that only cares about “config loading failed, do something about it” can catch ConfigError alone and never worry about the three different underlying exception types — but code that specifically needs to know why (to show a different message, or retry only for certain failures) can still inspect e.__cause__ to find out.

Check your understanding
1/5

In load_agent_config, why does the function catch three different exception types (FileNotFoundError, json.JSONDecodeError, ValidationError) but raise the same ConfigError type in every case?

Exercise · Graded

Implement load_tool_config following the exact structure demonstrated in this section — open and parse the file, validate against ToolConfig, and catch all three failure modes, each logging via logger.exception() and re-raising as a chained ConfigError with the specified message.