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
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.