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

Download this collection ยท Source on GitHub

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.

More intermediate examples ยท Level guide

API contracts๏ƒ