Skip to content

Use Case 1: YAML-First Contract Authoring

Scenario: you design the contract as a YAML spec, generate ABC Python source from it, hand-write concrete implementations, and prove the spec and code agree.


๐Ÿ“ฆ What's New?

Component Description
parse_yaml Reads the spec into the neutral PortNode tree.
generate_abc Emits _abc.py source (contracts only) from the tree.
check_matches Verifies the spec and the ABC module describe the same tree.

๐Ÿš€ Example

Start from the minimal sample spec (samples/minimal/sample.yaml):

YAML
ParentClass:
  config_var1: str
  config_var2: int
  methods:
    run:
      args: [arg1, arg2]
      kwargs: {kwarg1: int, kwarg2: str}
  port1:
    NonHexaChildClass1:
      config_var11: str
      config_var12: int
      methods: {method1: {args: [arg1, arg2], kwargs: {kwarg1: int, kwarg2: str}}}
  port2:
    HexaChildClass2:
      config_var21: str
      config_var22: int
      methods: {method1: {args: [arg1, arg2], kwargs: {kwarg1: int, kwarg2: str}}}
      port1:
        NonHexaPort1:
          config_var11: str
          config_var12: int
          methods: {method1: {args: [arg1, arg2], kwargs: {kwarg1: int, kwarg2: str}}}

Generate the ABC module from the spec:

Bash
hexa generate-abc samples/minimal/sample.yaml --out spec_abc.py

The generated file declares the contracts only โ€” attributes, port slots, and abstract methods:

Python
class ParentClass(ABC):
    config_var1: str
    config_var2: int

    port1: NonHexaChildClass1
    port2: HexaChildClass2
    port3: HexaChildClass3

    @abstractmethod
    def run(self, arg1, arg2, *, kwarg1: int, kwarg2: str): ...

Now write the concrete implementation (hand-written โ€” behavior and slot pinning are human decisions):

Python
class ImplParentClass(ParentClass):
    port1: NonHexaChildClass1
    port2: HexaChildClass2
    port3: HexaChildClass3

    def run(self, arg1, arg2, *, kwarg1: int = 0, kwarg2: str = ""):
        return {
            "port1": self.port1.method1(arg1, arg2, kwarg1=kwarg1, kwarg2=kwarg2),
            "port2": self.port2.method1(arg1, arg2, kwarg1=kwarg1, kwarg2=kwarg2),
            "port3": self.port3.method1(arg1, arg2, kwarg1=kwarg1, kwarg2=kwarg2),
        }

Finally, guard the two representations against drift:

Bash
hexa check samples/minimal/sample.yaml samples/minimal/abc.py
# Matches.   (exit 0)

๐Ÿ’ก Tips

  • generate_abc emits contracts only โ€” it never generates Impl*/specialization files. Slot pinning and method bodies are your call.
  • The YAML grammar distinguishes a port (a mapping whose single key has a non-scalar body) from a nested struct (any other mapping). Keep exactly one class key per port slot.
  • check_matches compares classes, attributes, slots, and @abstractmethod signatures; missing keyword-only params are treated conservatively so minor refactors don't false-negative.
  • All generators are deterministic (walk order), so regenerating a spec from a module and diffing is stable.