Error Handling¶
Every failure mode in hexa, what triggers it, and how it surfaces โ from the library (raises) and from the CLI (exits 1 with Error: <message> on stderr).
๐ Failure Modes¶
| Where | Trigger | Outcome |
|---|---|---|
parse_yaml |
Structural error in the YAML spec (bad class body, wrong scalar types) | ValueError raised |
check_matches |
Path whose suffix is neither .yaml/.yml nor .py |
ValueError("Unknown file extension for <path>") |
parse_abc |
Module can't be located / root can't be detected | Raises (import or reflection error) |
build |
Annotated type can't be instantiated | Raises from the constructor path |
| CLI (any subcommand) | Any of the above | Prints Error: <message> to stderr, exits 1 |
๐ YAML Structural Errors¶
parse_yaml performs structural validation only โ it checks the shape of the spec, not the types at runtime. Malformed specs raise ValueError (typically LRU-line-scoped in the source). Common causes:
- A port body that isn't a mapping.
- A nested struct that contains a value that's neither a scalar type expression nor a mapping body.
- A
methods:entry with an invalid shape (missingargs/kwargs, wrong collection types).
The CLI wraps everything, so a bad spec shows as:
Do not confuse "structural" with "runtime": a spec that names a class that doesn't exist at runtime is not a parse error โ YAML specs are contract-only and carry no runtime import.
๐ค check_matches Mismatch Handling¶
check_matches does not raise on disagreement โ it returns a result:
| Result | Meaning |
|---|---|
True |
The two trees are equal. |
list[str] |
Human-readable difference strings (ported from PortNode.diff). |
The CLI renders differences and exits 1:
A list result is truthy in Python, so always compare with is True:
Python
res = check_matches(spec, abc)
if res is True:
print("clean")
else:
print(res) # iterable of diff strings
๐งฐ Library Contracts¶
check_matches(path_a, path_b)โ exactly one YAML file and exactly one.pyfile, in either order. Any other extension raisesValueError.parse_abc(x)โ accepts a.pypath, an import string, or a loaded module. Give it a file path; other forms are for programmatic use.parse_yaml(path)โ acceptsstr | Path.build(cls, *, instances=None, config=None)filters out non-dict config values and skips unclassified annotations (forward references,Any, etc.) โ those are left unset rather than raised.
๐ก Tips¶
- Keep
str/int/boolscalar annotations out of port slots โ they're treated as config fields, andbuildwon't instantiate them as ports. hexa checkis the friendliest failure reporter: never parse trees yourself when you can point two files at it.- Guard CI with
hexa check spec.yaml abc.pyso representation drift never ships silently.