On this page

Protocol crafter the tool that writes๏ƒ

๐Ÿ”ต Expert ยท Lesson 05

THE ONE TOOL THAT WRITES TO DISK. Every surface in this curriculum so far reads: viewers read, crystals read, diffs derive, research records. ProtocolCrafter is the exception, and its own docstring flags that in a heading:

"IT WRITES TO DISK - the unusual part: Most of this codebase READS source; this one MODIFIES it."

It generates Protocol definitions from a live class or object - turning a concrete type into the structural interface that describes it - and then maintains those definitions inside your interface files.

WHY THIS EXISTS Melder resolves by SHAPE (beginner 17: protocols as shapes). So the Protocol is the contract, and hand-writing one for a class you already have is transcription - exactly the work a machine should do, and exactly the work that silently rots when the class changes and the Protocol does not.

THE DESIGN DECISION WORTH STEALING: BOUNDED UPDATES.

"its updates are BOUNDED: it rewrites a DELIMITED REGION rather than a whole file, so HAND-WRITTEN CODE AROUND THE GENERATED BLOCK SURVIVES REGENERATION."

That single choice is what separates a usable code generator from one you run once and then never dare run again. A generator that owns whole files forces you to choose between regenerating and keeping your own edits. A generator that owns a delimited block lets both live in the same file forever.

It is the same instinct as the withheld-section probe at advanced 15: be precise about the boundary of your authority, and say where it ends.

THE SURFACE, IN TWO GROUPS CRAFT - returns code as a string, touches nothing: craft_protocol_code craft_protocol_module_code_from_source_file craft_joined_protocol_module_code WRITE - puts it on disk: write_protocol_module_from_source_file write_joined_protocol_module add_protocol_to_interface_file remove_protocol_from_interface_file

THE SPLIT IS NOT A CLEAN 1:1 PAIRING, and the real shape is better than a pairing would be. Two of the writes DO have craft twins - the ones that generate a whole module from a source file, and the joined variant. The two interface-file writes have no twin at all. That looks like a gap until you read their signatures: add_protocol_to_interface_file(path, PROTOCOL_CODE) The crafted code is the ARGUMENT. You cannot add a protocol you have not crafted, because the crafted string is what you pass in. The preview is not a parallel verb you are trusted to remember to call - it is the input, and there is no route to the file that skips it.

AND THE BOUNDED-BLOCK RULES ARE THE REST OF IT. add refuses by NAME if that protocol is already present rather than appending a second copy, remove deletes one named block and leaves the rest of your file alone, and both RETURN the updated contents so the result is inspectable rather than assumed.

Before you run๏ƒ

Use the Expert guide for prerequisite concepts. Run from a checkout with Melder installed and Python 3.14 free-threading selected. The collection download includes the level's local helper modules.

Run the saved script๏ƒ

python UX_and_AIX_experiences/04_expert/05_protocol_crafter_the_tool_that_writes.py
py -3.14t UX_and_AIX_experiences/04_expert/05_protocol_crafter_the_tool_that_writes.py

Download this collection ยท Source on GitHub

Public surface๏ƒ

md.ProtocolCrafter.craft_protocol_code (twice, for determinism), add_protocol_to_interface_file including its duplicate refusal, and remove_protocol_from_interface_file - all against a temporary directory that removes itself

Code๏ƒ

  1"""
  2TIER: expert (05)
  3GOAL: THE ONE TOOL THAT WRITES TO DISK. Every surface in this curriculum
  4      so far reads: viewers read, crystals read, diffs derive, research
  5      records. ProtocolCrafter is the exception, and its own docstring
  6      flags that in a heading:
  7
  8        "IT WRITES TO DISK - the unusual part:
  9         Most of this codebase READS source; this one MODIFIES it."
 10
 11      It generates Protocol definitions from a live class or object -
 12      turning a concrete type into the structural interface that
 13      describes it - and then maintains those definitions inside your
 14      interface files.
 15
 16      WHY THIS EXISTS
 17      Melder resolves by SHAPE (beginner 17: protocols as shapes). So the
 18      Protocol is the contract, and hand-writing one for a class you
 19      already have is transcription - exactly the work a machine should
 20      do, and exactly the work that silently rots when the class changes
 21      and the Protocol does not.
 22
 23      THE DESIGN DECISION WORTH STEALING: BOUNDED UPDATES.
 24
 25        "its updates are BOUNDED: it rewrites a DELIMITED REGION rather
 26         than a whole file, so HAND-WRITTEN CODE AROUND THE GENERATED
 27         BLOCK SURVIVES REGENERATION."
 28
 29      That single choice is what separates a usable code generator from
 30      one you run once and then never dare run again. A generator that
 31      owns whole files forces you to choose between regenerating and
 32      keeping your own edits. A generator that owns a delimited block
 33      lets both live in the same file forever.
 34
 35      It is the same instinct as the withheld-section probe at advanced
 36      15: be precise about the boundary of your authority, and say where
 37      it ends.
 38
 39      THE SURFACE, IN TWO GROUPS
 40        CRAFT - returns code as a string, touches nothing:
 41          craft_protocol_code
 42          craft_protocol_module_code_from_source_file
 43          craft_joined_protocol_module_code
 44        WRITE - puts it on disk:
 45          write_protocol_module_from_source_file
 46          write_joined_protocol_module
 47          add_protocol_to_interface_file
 48          remove_protocol_from_interface_file
 49
 50      THE SPLIT IS NOT A CLEAN 1:1 PAIRING, and the real shape is better
 51      than a pairing would be. Two of the writes DO have craft twins - the
 52      ones that generate a whole module from a source file, and the joined
 53      variant. The two interface-file writes have no twin at all.
 54      That looks like a gap until you read their signatures:
 55        add_protocol_to_interface_file(path, PROTOCOL_CODE)
 56      The crafted code is the ARGUMENT. You cannot add a protocol you have
 57      not crafted, because the crafted string is what you pass in. The
 58      preview is not a parallel verb you are trusted to remember to call -
 59      it is the input, and there is no route to the file that skips it.
 60
 61      AND THE BOUNDED-BLOCK RULES ARE THE REST OF IT. `add` refuses by
 62      NAME if that protocol is already present rather than appending a
 63      second copy, `remove` deletes one named block and leaves the rest of
 64      your file alone, and both RETURN the updated contents so the result
 65      is inspectable rather than assumed.
 66SURFACE EXERCISED: md.ProtocolCrafter.craft_protocol_code (twice, for
 67                   determinism), add_protocol_to_interface_file including
 68                   its duplicate refusal, and
 69                   remove_protocol_from_interface_file - all against a
 70                   temporary directory that removes itself
 71VERIFY: rewritten 2026-08-05; the write lanes are now exercised against a
 72        throwaway file instead of only being named. Not yet run.
 73"""
 74import tempfile
 75from pathlib import Path
 76
 77import melder as md
 78
 79
 80class PaymentGateway:
 81    """A concrete class - the kind of thing you would want a Protocol for."""
 82
 83    def charge(self, amount: int, currency: str) -> bool:
 84        return True
 85
 86    def refund(self, transaction_id: str) -> bool:
 87        return True
 88
 89
 90def main() -> None:
 91    crafter = md.ProtocolCrafter()
 92    assert isinstance(crafter, md.ProtocolCrafter)
 93    print("crafter:", crafter.id)
 94
 95    # CRAFT A PROTOCOL FROM A LIVE CLASS. Nothing is written; this is a
 96    # string. Look at it before you let anything near your tree.
 97    code = crafter.craft_protocol_code(PaymentGateway)
 98    assert isinstance(code, str) and code.strip()
 99    print()
100    print("crafted from a live class -", len(code), "chars:")
101    for line in code.splitlines()[:12]:
102        print("   ", line)
103
104    # PURITY, CHECKED RATHER THAN CLAIMED. Craft is a pure read, so the
105    # same input must produce byte-identical output and leave no trace.
106    again = crafter.craft_protocol_code(PaymentGateway)
107    assert again == code, "craft must be deterministic - it is a pure read"
108    print()
109    print("crafted twice -> byte-identical:", again == code)
110    print("  a verb that wrote something, cached something, or consumed")
111    print("  state would not survive being called twice")
112
113    # The generated shape should describe what the class actually offers.
114    assert "Protocol" in code
115    for method in ("charge", "refund"):
116        assert method in code, f"{method} missing from the crafted protocol"
117    print()
118    print("both public methods appear in the crafted protocol")
119
120    # THE WRITE LANES, ON A THROWAWAY FILE. Nothing here goes near your
121    # tree - the temporary directory removes itself.
122    with tempfile.TemporaryDirectory() as scratch:
123        interface_file = Path(scratch) / "interfaces.py"
124
125        # YOU CANNOT WRITE WHAT YOU HAVE NOT CRAFTED, because the crafted
126        # code IS the argument. That is a stronger guarantee than a
127        # parallel preview verb: there is no path to the file that skips
128        # the string you already looked at.
129        updated = crafter.add_protocol_to_interface_file(interface_file, code)
130        assert "Protocol" in updated
131        assert interface_file.exists()
132        print()
133        print("add_protocol_to_interface_file(path, THE CRAFTED CODE)")
134        print("  ->", len(updated), "chars, and it RETURNS the new contents")
135        print("  the crafted string is the ARGUMENT, so there is no route")
136        print("  to the file that skips the thing you already read")
137
138        # ADDING IT TWICE REFUSES BY NAME. Not a silent second copy.
139        try:
140            crafter.add_protocol_to_interface_file(interface_file, code)
141            raise AssertionError("expected a refusal on a duplicate protocol")
142        except ValueError as duplicate:
143            print()
144            print("adding the same protocol again ->", str(duplicate)[:78])
145            print("  it refuses by NAME rather than appending a second copy")
146
147        # AND REMOVAL IS BY NAME, BOUNDED TO THAT BLOCK.
148        name = next(line.split()[1].split("(")[0]
149                    for line in code.splitlines()
150                    if line.startswith("class "))
151        after_removal = crafter.remove_protocol_from_interface_file(
152            interface_file, name,
153        )
154        assert name not in after_removal, after_removal
155        print()
156        print("remove_protocol_from_interface_file(path, %r) -> gone" % name)
157        print("  it owns a DELIMITED BLOCK, not your file: the rest of the")
158        print("  contents is untouched, which is what makes it safe to")
159        print("  point at a file you did not generate")
160
161    print()
162    print("craft returns code and touches nothing; the write lanes take")
163    print("that code as their argument. The preview is not a parallel")
164    print("verb you are trusted to call - it is the input.")
165
166
167if __name__ == "__main__":
168    main()

Check the outcome๏ƒ

The script contains its own assertions or demonstrated refusal paths. Run it to evaluate those checks against your installed version. The code above is taken directly from the saved file; no run output is invented here.

More expert examples ยท Level guide

API contracts๏ƒ