Core Components¶
A validated reference to the public API of hexa (package root exports from hexa/__init__.py). Signatures match the code exactly. For runnable recipes see the Use Cases. Exact parameter contracts live in the API reference under docs/lib/ — this page is usage-level.
1. The Shared Model — PortNode and MethodSpec¶
Every parser and generator serializes through one neutral tree. It is not tied to any sample — any port tree fits.
1.1 PortNode¶
A node in a hexa port tree. Three kinds:
| Kind | Meaning | Example |
|---|---|---|
parent |
The root of the tree | ExtractionPipeline |
port |
Has nested port slots (children) | TransactionParserPort |
leaf |
No children | NumberPort |
Fields:
| Field | Type | Meaning |
|---|---|---|
name |
str |
Slot name, e.g. "parser", "amount_balance" |
port_cls |
str |
ABC class name that gives this port its shape |
kind |
str |
"leaf" | "port" | "parent" |
attributes |
dict[str, ConfigValue] |
name → type_expr for scalars, or a nested dict of sub-attributes for grouped values |
children |
list[PortNode] |
Nested port slots |
methods |
dict[str, MethodSpec] |
Abstract methods keyed by method name |
Tree helpers (used internally and handy for inspection):
node.find(name) -> PortNode | None— direct child by slot name.node.walk() -> list[PortNode]— all nodes, depth-first, self first.node.diff(other, path="") -> list[str]— human-readable difference strings; empty list means equal.
1.2 MethodSpec¶
Specification of a single abstract method: name, positional args: list[str], and keyword-only kwargs: dict[str, str] (name → type expression). Supports equality (==/!=).
1.3 ConfigValue¶
str | NestedConfig, where NestedConfig = dict[str, ConfigValue]. An attribute is either a scalar type expression (e.g. "str") or a nested structure of sub-attributes. Nesting is recursive.
2. Converters — YAML ⇄ ABC¶
All converters are pure (they operate on the model) and optional: you never have to touch them if you hand-write a single representation.
2.1 parse_yaml(path) -> PortNode¶
Parses a hexa YAML spec into a PortNode tree. The grammar uses three member kinds:
- attribute —
name: typescalar (e.g.source_type: str) - nested struct —
name:mapping of sub-attributes (e.g.source:) - port —
name:mapping whose single key is a class whose value is a body (e.g.ingest: IngestPort: ...)
Disambiguation rule: a mapping with exactly one key whose value is a non-scalar body is a port; any other mapping is a nested struct of attributes. Raises ValueError on structural errors (see Error Handling).
2.2 generate_abc(node, out_path=None) -> str¶
Generates _abc.py source from a PortNode tree. Emits class X(ABC) per node, synthesized @dataclasses for nested structs, port-slot annotations, and @abstractmethod stubs. Deterministic (walk order) so output is idempotent. When out_path is given the source is also written there; the source string is always returned.
2.3 parse_abc(module_or_path) -> PortNode¶
Reflects an ABC Python module into a PortNode tree. Accepts a .py file path, a module import string, or an already-loaded module. ABC classes become nodes; a @dataclass annotation on a port expands into a nested attributes entry. The root is auto-detected (the class not referenced as any other slot, preferring names containing Pipeline/Root/Parent).
2.4 generate_yaml(node, out_path=None) -> str¶
Generates a YAML spec (nested mapping form) from a PortNode tree — the reverse of generate_abc. A deferred/empty slot renders as slot: Class: ....
3. Verification — check_matches(a, b)¶
check_matches(path_a, path_b) -> bool | list[str] parses two representations (one .yaml/.yml and one .py, either direction) and compares the trees with PortNode.diff. Returns True when they describe the same tree, otherwise a list of human-readable difference strings. Inequality checks cover port_cls, kind, attributes (including nested structs), methods (args/kwargs), and children.
4. Container — build¶
build(cls, *, instances=None, config=None) -> T recursively instantiates the port tree implied by a root class's annotations.
Algorithm:
- Merge
__annotations__across the MRO (reverse order) so inherited slot annotations are visible. - Instantiate the parent class (
cls(); if that raisesTypeError, retry passingNonefor every__init__parameter afterself). - For each annotation:
- A name in
instanceswins — the provided object issetattred verbatim, never reconstructed. - Builtin /
typing/ PEP 604-union types are config fields — applied fromconfigby name. - Abstract classes are skipped (but take a
configvalue when provided). - Any other concrete type is a port slot —
buildrecurses and the child issetattred onto the parent. - Config defaults: any annotated attribute still unset is defaulted to its
configvalue (orNone) so methods can safely reference it.
Runtime values (repos, handlers, clients) go in
instances=; plain config values go inconfig=. Both propagate by name across the whole tree.
5. CLI¶
| Command | Behavior |
|---|---|
hexa parse-yaml PATH |
Print the parsed PortNode tree. |
hexa generate-abc PATH [--out FILE] |
Emit ABC source from a YAML spec. |
hexa parse-abc PATH |
Print the tree reflected from an ABC module. |
hexa generate-yaml PATH [--out FILE] |
Emit a YAML spec from an ABC module. |
hexa check PATH_A PATH_B |
Print Matches./Differences found: and exit 0/1. |
Errors print Error: <message> to stderr and exit 1.