On this page
Bind lifecycle hooks๏
๐ก Intermediate ยท Lesson 40
Observe pre-bind, configure the actual Spell during bind activation, and inspect its completed registration in post-bind. Seed a callback through configuration, add stages on the Book, then clear and re-add through its normal Conduit. These hooks run at bind time, not meld time.
Before you run๏
Use the Intermediate 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/02_intermediate/40_bind_lifecycle_hooks.py
py -3.14t UX_and_AIX_experiences/02_intermediate/40_bind_lifecycle_hooks.py
Public surface๏
md.SpellbookConfiguration.with_bind_hooks, md.Spellbook.add_bind_hooks / find_spell_by_spell_id, md.Conduit.clear_bind_hooks / add_bind_hooks / bind / meld, md.Spell.metadata / tags
Code๏
1"""
2TIER: intermediate (40)
3GOAL: Observe pre-bind, configure the actual Spell during bind activation,
4 and inspect its completed registration in post-bind. Seed a callback
5 through configuration, add stages on the Book, then clear and re-add
6 through its normal Conduit. These hooks run at bind time, not meld time.
7SURFACE EXERCISED: md.SpellbookConfiguration.with_bind_hooks,
8 md.Spellbook.add_bind_hooks / find_spell_by_spell_id,
9 md.Conduit.clear_bind_hooks / add_bind_hooks / bind / meld,
10 md.Spell.metadata / tags
11"""
12import inspect
13
14import melder as md
15
16
17class Report:
18 """Simple application object whose registration will be annotated by hooks."""
19
20 def render(self) -> str:
21 """Return an observable result after normal name-based resolution."""
22 return "report ready"
23
24
25class Metrics:
26 """Separate registration used while all bind hooks are cleared."""
27
28
29class Notifier:
30 """Separate registration proving callbacks can be added again after clear."""
31
32
33def main() -> None:
34 """Exercise the three stages and future-bind updates in one dynamic Book.
35
36 Contract:
37 Assertions distinguish raw class references, unpublished Spells and
38 completed registrations. Final cleanup releases the Book's callbacks.
39 """
40 events: list[str] = []
41
42 def check_reference(reference: object) -> None:
43 """Apply this application's class-only registration policy before reflection."""
44 if not inspect.isclass(reference):
45 raise TypeError("This example accepts class references only.")
46 events.append(f"pre:{reference.__name__}")
47
48 def configure_spell(spell: md.Spell) -> None:
49 """Annotate the actual new Spell before it becomes publicly discoverable."""
50 assert isinstance(spell, md.Spell)
51 assert book.find_spell_by_spell_id(spell.spell_id) is None
52 spell.metadata["reviewed_by"] = "application-bind-policy"
53 spell.tags.append("reviewed")
54 events.append(f"activation:{spell.spell_name}")
55
56 def observe_registration(spell: md.Spell) -> None:
57 """Verify the registered Spell is the same object activation annotated."""
58 assert book.find_spell_by_spell_id(spell.spell_id) is spell
59 assert spell.metadata["reviewed_by"] == "application-bind-policy"
60 events.append(f"post:{spell.spell_name}")
61
62 # Configuration seeds are captured when this Book is constructed.
63 configuration = md.SpellbookConfiguration().with_defaults()
64 configuration.with_bind_hooks(pre=[check_reference])
65 book = md.Spellbook(configuration=configuration)
66 conduit = None
67 try:
68 # Runtime setup appends stages to this Book's captured seed callbacks.
69 book.add_bind_hooks(activation=[configure_spell], post=[observe_registration])
70 report_id = book.bind(spell=Report, existence="unique")
71 assert events == ["pre:Report", "activation:Report", "post:Report"]
72 registered = book.find_spell_by_spell_id(report_id)
73 assert registered is not None
74 assert "reviewed" in registered.tags
75 print("bind order:", " -> ".join(events))
76
77 conduit = book.conjure(dynamic=True, name="bind-hook-basics")
78 assert conduit.meld("Report").render() == "report ready"
79 assert events == ["pre:Report", "activation:Report", "post:Report"]
80 print("meld returned the Report; bind callbacks did not run again")
81
82 # The normal Conduit facades the same Book-owned bind-hook registry.
83 conduit.clear_bind_hooks()
84 metrics_id = conduit.bind(spell=Metrics, existence="unique")
85 metrics_spell = book.find_spell_by_spell_id(metrics_id)
86 assert metrics_spell is not None
87 assert metrics_spell.metadata == {}
88 assert len(events) == 3
89 assert registered.metadata["reviewed_by"] == "application-bind-policy"
90 print("clear affected future binds; the earlier Spell kept its metadata")
91
92 conduit.add_bind_hooks(
93 pre=[check_reference], activation=[configure_spell], post=[observe_registration],
94 )
95 conduit.bind(spell=Notifier, existence="unique")
96 assert events[3:] == ["pre:Notifier", "activation:Notifier", "post:Notifier"]
97 print("re-added hooks:", " -> ".join(events[3:]))
98 finally:
99 if conduit is not None:
100 conduit.cleanup()
101 book.cleanup()
102
103
104if __name__ == "__main__":
105 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.