Skip to content

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 (missing args/kwargs, wrong collection types).

The CLI wraps everything, so a bad spec shows as:

Text Only
Error: <reason>
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:

Text Only
Differences found:
  <slot> port_cls mismatch: baz vs foo

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 .py file, in either order. Any other extension raises ValueError.
  • parse_abc(x) โ€” accepts a .py path, an import string, or a loaded module. Give it a file path; other forms are for programmatic use.
  • parse_yaml(path) โ€” accepts str | 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/bool scalar annotations out of port slots โ€” they're treated as config fields, and build won't instantiate them as ports.
  • hexa check is the friendliest failure reporter: never parse trees yourself when you can point two files at it.
  • Guard CI with hexa check spec.yaml abc.py so representation drift never ships silently.